diff --git a/.github/ISSUE_TEMPLATE/ISSUE.yml b/.github/ISSUE_TEMPLATE/ISSUE.yml
new file mode 100644
index 000000000..199fb0c85
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/ISSUE.yml
@@ -0,0 +1,31 @@
+name: Issue
+description: An actionable development item, like a bug report or feature request
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thank you for opening an issue! This is for actionable development items like bug reports and feature requests.
+ If you have a question about using Caddy, please [post on our forums](https://caddy.community) instead.
+ - type: textarea
+ id: content
+ attributes:
+ label: Issue Details
+ placeholder: Describe the issue here. Be specific by providing complete logs and minimal instructions to reproduce, or a thoughtful proposal, etc.
+ validations:
+ required: true
+ - type: dropdown
+ id: assistance-disclosure
+ attributes:
+ label: Assistance Disclosure
+ description: "Our project allows assistance by AI/LLM tools as long as it is disclosed and described so we can better respond. Please certify whether you have used any such tooling related to this issue:"
+ options:
+ -
+ - AI used
+ - AI not used
+ validations:
+ required: true
+ - type: input
+ id: assistance-description
+ attributes:
+ label: If AI was used, describe the extent to which it was used.
+ description: 'Examples: "ChatGPT translated from my native language" or "Claude proposed this change/feature"'
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..81df4f1eb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Caddy forum
+ url: https://caddy.community
+ about: If you have questions (or answers!) about using Caddy, please use our forum
\ No newline at end of file
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
index 557b4bac1..6f13031b9 100644
--- a/.github/SECURITY.md
+++ b/.github/SECURITY.md
@@ -48,9 +48,9 @@ We consider publicly-registered domain names to be public information. This nece
It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding.
-When you are ready, please email Matt Holt (the author) directly: matt at dyanim dot com.
+When you are ready, please submit a [new private vulnerability report](https://github.com/caddyserver/caddy/security/advisories/new).
-Please don't encrypt the email body. It only makes the process more complicated.
+Please don't encrypt the message. It only makes the process more complicated.
Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 64284b907..85a85f63a 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -3,5 +3,20 @@ version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
+ open-pull-requests-limit: 1
+ groups:
+ actions-deps:
+ patterns:
+ - "*"
+ schedule:
+ interval: "monthly"
+
+ - package-ecosystem: "gomod"
+ directory: "/"
+ open-pull-requests-limit: 1
+ groups:
+ all-updates:
+ patterns:
+ - "*"
schedule:
interval: "monthly"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..d4ae5a3c2
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,29 @@
+
+
+
+## Assistance Disclosure
+
+
+_This PR is missing an assistance disclosure._
diff --git a/.github/workflows/ai.yml b/.github/workflows/ai.yml
new file mode 100644
index 000000000..0008febba
--- /dev/null
+++ b/.github/workflows/ai.yml
@@ -0,0 +1,30 @@
+name: AI Moderator
+permissions: read-all
+on:
+ issues:
+ types: [opened]
+ issue_comment:
+ types: [created]
+ pull_request_review_comment:
+ types: [created]
+jobs:
+ spam-detection:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ models: read
+ contents: read
+ steps:
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
+ - uses: github/ai-moderator@6bcdb2a79c2e564db8d76d7d4439d91a044c4eb6
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ spam-label: 'spam'
+ ai-label: 'ai-generated'
+ minimize-detected-comments: true
+ # Built-in prompt configuration (all enabled by default)
+ enable-spam-detection: true
+ enable-link-spam-detection: true
+ enable-ai-detection: true
+ # custom-prompt-path: '.github/prompts/my-custom.prompt.yml' # Optional
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5f3e98db6..50501a0f1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,9 +13,13 @@ on:
- 2.*
env:
+ GOFLAGS: '-tags=nobadger,nomysql,nopgx'
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
+permissions:
+ contents: read
+
jobs:
test:
strategy:
@@ -27,13 +31,13 @@ jobs:
- mac
- windows
go:
- - '1.24'
+ - '1.25'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- - go: '1.24'
- GO_SEMVER: '~1.24.1'
+ - go: '1.25'
+ GO_SEMVER: '~1.25.0'
# 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)
@@ -55,13 +59,21 @@ jobs:
SUCCESS: 'True'
runs-on: ${{ matrix.OS_LABEL }}
-
+ permissions:
+ contents: read
+ pull-requests: read
+ actions: write # to allow uploading artifacts and cache
steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
+ with:
+ egress-policy: audit
+
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
@@ -99,7 +111,7 @@ jobs:
env:
CGO_ENABLED: 0
run: |
- go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v
+ go build -trimpath -ldflags="-w -s" -v
- name: Smoke test Caddy
working-directory: ./cmd/caddy
@@ -108,7 +120,7 @@ jobs:
./caddy stop
- name: Publish Build Artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
path: ${{ matrix.CADDY_BIN_PATH }}
@@ -122,7 +134,7 @@ jobs:
# continue-on-error: true
run: |
# (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 -v -coverprofile="cover-profile.out" -short -race ./...
# echo "status=$?" >> $GITHUB_OUTPUT
# Relevant step if we reinvestigate publishing test/coverage reports
@@ -142,12 +154,21 @@ jobs:
s390x-test:
name: test (s390x on IBM Z)
+ permissions:
+ contents: read
+ pull-requests: read
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
+ with:
+ egress-policy: audit
+ allowed-endpoints: ci-s390x.caddyserver.com:22
+
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Run Tests
run: |
set +e
@@ -170,7 +191,7 @@ jobs:
retries=3
exit_code=0
while ((retries > 0)); do
- CGO_ENABLED=0 go test -p 1 -tags nobadger,nomysql,nopgx -v ./...
+ CGO_ENABLED=0 go test -p 1 -v ./...
exit_code=$?
if ((exit_code == 0)); then
break
@@ -194,25 +215,33 @@ jobs:
goreleaser-check:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
+ with:
+ egress-policy: audit
+
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- - uses: goreleaser/goreleaser-action@v6
+ - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: latest
args: check
- name: Install Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
- go-version: "~1.24"
+ go-version: "~1.25"
check-latest: true
- name: Install xcaddy
run: |
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy version
- - uses: goreleaser/goreleaser-action@v6
+ - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: latest
args: build --single-target --snapshot
diff --git a/.github/workflows/cross-build.yml b/.github/workflows/cross-build.yml
index 372cc7652..8aa9eaf59 100644
--- a/.github/workflows/cross-build.yml
+++ b/.github/workflows/cross-build.yml
@@ -11,9 +11,14 @@ on:
- 2.*
env:
+ GOFLAGS: '-tags=nobadger,nomysql,nopgx'
+ CGO_ENABLED: '0'
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
+permissions:
+ contents: read
+
jobs:
build:
strategy:
@@ -31,22 +36,30 @@ jobs:
- 'darwin'
- 'netbsd'
go:
- - '1.24'
+ - '1.25'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- - go: '1.24'
- GO_SEMVER: '~1.24.1'
+ - go: '1.25'
+ GO_SEMVER: '~1.25.0'
runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
continue-on-error: true
steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
+ with:
+ egress-policy: audit
+
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Install Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
@@ -63,11 +76,9 @@ jobs:
- name: Run Build
env:
- CGO_ENABLED: 0
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goos == 'aix' && 'ppc64' || 'amd64' }}
shell: bash
continue-on-error: true
working-directory: ./cmd/caddy
- run: |
- GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
+ run: go build -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index c5c89b502..849188c64 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -44,14 +44,19 @@ jobs:
runs-on: ${{ matrix.OS_LABEL }}
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-go@v5
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
- go-version: '~1.24'
+ egress-policy: audit
+
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
+ with:
+ go-version: '~1.25'
check-latest: true
- name: golangci-lint
- uses: golangci/golangci-lint-action@v6
+ uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
with:
version: latest
@@ -62,10 +67,39 @@ jobs:
# only-new-issues: true
govulncheck:
+ permissions:
+ contents: read
+ pull-requests: read
runs-on: ubuntu-latest
steps:
- - name: govulncheck
- uses: golang/govulncheck-action@v1
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
with:
- go-version-input: '~1.24.1'
+ egress-policy: audit
+
+ - name: govulncheck
+ uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
+ with:
+ go-version-input: '~1.25.0'
check-latest: true
+
+ dependency-review:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
+ with:
+ egress-policy: audit
+
+ - name: 'Checkout Repository'
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ - name: 'Dependency Review'
+ uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0
+ with:
+ comment-summary-in-pr: on-failure
+ # https://github.com/actions/dependency-review-action/issues/430#issuecomment-1468975566
+ base-ref: ${{ github.event.pull_request.base.sha || 'master' }}
+ head-ref: ${{ github.event.pull_request.head.sha || github.ref }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b508ba468..397df5ea2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,6 +9,9 @@ env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
+permissions:
+ contents: read
+
jobs:
release:
name: Release
@@ -17,13 +20,13 @@ jobs:
os:
- ubuntu-latest
go:
- - '1.24'
+ - '1.25'
include:
# Set the minimum Go patch version for the given Go minor
# Usable via ${{ matrix.GO_SEMVER }}
- - go: '1.24'
- GO_SEMVER: '~1.24.1'
+ - go: '1.25'
+ GO_SEMVER: '~1.25.0'
runs-on: ${{ matrix.os }}
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
@@ -35,19 +38,24 @@ jobs:
contents: write
steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
+ with:
+ egress-policy: audit
+
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- name: Install Go
- uses: actions/setup-go@v5
+ uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
with:
go-version: ${{ matrix.GO_SEMVER }}
check-latest: true
# Force fetch upstream tags -- because 65 minutes
- # tl;dr: actions/checkout@v4 runs this line:
+ # tl;dr: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 runs this line:
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
# git fetch --prune --unshallow
@@ -101,11 +109,11 @@ jobs:
git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1
- name: Install Cosign
- uses: sigstore/cosign-installer@main
+ uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # main
- name: Cosign version
run: cosign version
- name: Install Syft
- uses: anchore/sbom-action/download-syft@main
+ uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # main
- name: Syft version
run: syft version
- name: Install xcaddy
@@ -114,7 +122,7 @@ jobs:
xcaddy version
# GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser
- uses: goreleaser/goreleaser-action@v6
+ uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
version: latest
args: release --clean --timeout 60m
diff --git a/.github/workflows/release_published.yml b/.github/workflows/release_published.yml
index 491dae75d..8afc5c35e 100644
--- a/.github/workflows/release_published.yml
+++ b/.github/workflows/release_published.yml
@@ -5,6 +5,9 @@ on:
release:
types: [published]
+permissions:
+ contents: read
+
jobs:
release:
name: Release Published
@@ -13,12 +16,20 @@ jobs:
os:
- ubuntu-latest
runs-on: ${{ matrix.os }}
-
+ permissions:
+ contents: read
+ pull-requests: read
+ actions: write
steps:
# See https://github.com/peter-evans/repository-dispatch
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
+ with:
+ egress-policy: audit
+
- name: Trigger event on caddyserver/dist
- uses: peter-evans/repository-dispatch@v3
+ uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/dist
@@ -26,7 +37,7 @@ jobs:
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
- name: Trigger event on caddyserver/caddy-docker
- uses: peter-evans/repository-dispatch@v3
+ uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0
with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/caddy-docker
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
new file mode 100644
index 000000000..bb49f935d
--- /dev/null
+++ b/.github/workflows/scorecard.yml
@@ -0,0 +1,86 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: OpenSSF Scorecard supply-chain security
+on:
+ # For Branch-Protection check. Only the default branch is supported. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+ branch_protection_rule:
+ # To guarantee Maintained check is occasionally updated. See
+ # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+ schedule:
+ - cron: '20 2 * * 5'
+ push:
+ branches: [ "master", "2.*" ]
+ pull_request:
+ branches: [ "master", "2.*" ]
+
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecard analysis
+ runs-on: ubuntu-latest
+ # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
+ if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
+ permissions:
+ # Needed to upload the results to code-scanning dashboard.
+ security-events: write
+ # Needed to publish results and get a badge (see publish_results below).
+ id-token: write
+ # Uncomment the permissions below if installing in a private repository.
+ # contents: read
+ # actions: read
+
+ steps:
+ - name: Harden the runner (Audit all outbound calls)
+ uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
+ with:
+ egress-policy: audit
+
+ - name: "Checkout code"
+ uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ with:
+ persist-credentials: false
+
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
+ # - you want to enable the Branch-Protection check on a *public* repository, or
+ # - you are installing Scorecard on a *private* repository
+ # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
+ # repo_token: ${{ secrets.SCORECARD_TOKEN }}
+
+ # Public repositories:
+ # - Publish results to OpenSSF REST API for easy access by consumers
+ # - Allows the repository to include the Scorecard badge.
+ # - See https://github.com/ossf/scorecard-action#publishing-results.
+ # For private repositories:
+ # - `publish_results` will always be set to `false`, regardless
+ # of the value entered here.
+ publish_results: true
+
+ # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore
+ # file_mode: git
+
+ # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+ # format to the repository Actions tab.
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ # Upload the results to GitHub's code scanning dashboard (optional).
+ # Commenting out will disable upload of results to your repo's Code Scanning dashboard
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5
+ with:
+ sarif_file: results.sarif
diff --git a/.golangci.yml b/.golangci.yml
index aecff563e..4f4545054 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,27 +1,19 @@
-linters-settings:
- errcheck:
- exclude-functions:
- - fmt.*
- - (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
- - (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
- gci:
- sections:
- - standard # Standard section: captures all standard packages.
- - default # Default section: contains all imports that could not be matched to another section type.
- - prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
- - prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
- # Skip generated files.
- # Default: true
- skip-generated: true
- # Enable custom order of sections.
- # If `true`, make the section order the same as the order of `sections`.
- # Default: false
- custom-order: true
- exhaustive:
- ignore-enum-types: reflect.Kind|svc.Cmd
-
+version: "2"
+run:
+ issues-exit-code: 1
+ tests: false
+ build-tags:
+ - nobadger
+ - nomysql
+ - nopgx
+output:
+ formats:
+ text:
+ path: stdout
+ print-linter-name: true
+ print-issued-lines: true
linters:
- disable-all: true
+ default: none
enable:
- asasalint
- asciicheck
@@ -35,148 +27,96 @@ linters:
- errcheck
- errname
- exhaustive
- - gci
- - gofmt
- - goimports
- - gofumpt
- gosec
- - gosimple
- govet
- - ineffassign
- importas
+ - ineffassign
- misspell
- prealloc
- promlinter
- sloglint
- sqlclosecheck
- staticcheck
- - tenv
- testableexamples
- testifylint
- tparallel
- - typecheck
- unconvert
- unused
- wastedassign
- whitespace
- zerologlint
- # these are implicitly disabled:
- # - containedctx
- # - contextcheck
- # - cyclop
- # - depguard
- # - errchkjson
- # - errorlint
- # - exhaustruct
- # - execinquery
- # - exhaustruct
- # - forbidigo
- # - forcetypeassert
- # - funlen
- # - ginkgolinter
- # - gocheckcompilerdirectives
- # - gochecknoglobals
- # - gochecknoinits
- # - gochecksumtype
- # - gocognit
- # - goconst
- # - gocritic
- # - gocyclo
- # - godot
- # - godox
- # - goerr113
- # - goheader
- # - gomnd
- # - gomoddirectives
- # - gomodguard
- # - goprintffuncname
- # - gosmopolitan
- # - grouper
- # - inamedparam
- # - interfacebloat
- # - ireturn
- # - lll
- # - loggercheck
- # - maintidx
- # - makezero
- # - mirror
- # - musttag
- # - nakedret
- # - nestif
- # - nilerr
- # - nilnil
- # - nlreturn
- # - noctx
- # - nolintlint
- # - nonamedreturns
- # - nosprintfhostport
- # - paralleltest
- # - perfsprint
- # - predeclared
- # - protogetter
- # - reassign
- # - revive
- # - rowserrcheck
- # - stylecheck
- # - tagalign
- # - tagliatelle
- # - testpackage
- # - thelper
- # - unparam
- # - usestdlibvars
- # - varnamelen
- # - wrapcheck
- # - wsl
-
-run:
- # default concurrency is a available CPU number.
- # concurrency: 4 # explicitly omit this value to fully utilize available resources.
- timeout: 5m
- issues-exit-code: 1
- tests: false
-
-# output configuration options
-output:
- formats:
- - format: 'colored-line-number'
- print-issued-lines: true
- print-linter-name: true
-
-issues:
- 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
- - text: 'G107' # G107: Url provided to HTTP request as taint input
- linters:
- - gosec
- # as a web server that's expected to handle any template, this is totally in the hands of the user.
- - text: 'G203' # G203: Use of unescaped data in HTML templates
- linters:
- - gosec
- # we're shelling out to known commands, not relying on user-defined input.
- - text: 'G204' # G204: Audit use of command execution
- linters:
- - gosec
- # the choice of weakrand is deliberate, hence the named import "weakrand"
- - path: modules/caddyhttp/reverseproxy/selectionpolicies.go
- text: 'G404' # G404: Insecure random number source (rand)
- linters:
- - gosec
- - path: modules/caddyhttp/reverseproxy/streaming.go
- text: 'G404' # G404: Insecure random number source (rand)
- linters:
- - gosec
- - path: modules/logging/filters.go
- linters:
- - dupl
- - path: modules/caddyhttp/matchers.go
- linters:
- - dupl
- - path: modules/caddyhttp/vars.go
- linters:
- - dupl
- - path: _test\.go
- linters:
- - errcheck
+ settings:
+ staticcheck:
+ checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1006", "-QF1008"] # default, and exclude 1 more undesired check
+ errcheck:
+ exclude-functions:
+ - fmt.*
+ - (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
+ - (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
+ exhaustive:
+ ignore-enum-types: reflect.Kind|svc.Cmd
+ exclusions:
+ generated: lax
+ presets:
+ - comments
+ - common-false-positives
+ - legacy
+ - std-error-handling
+ rules:
+ - linters:
+ - gosec
+ text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad
+ - linters:
+ - gosec
+ text: G107 # we aren't calling unknown URL
+ - linters:
+ - gosec
+ text: G203 # as a web server that's expected to handle any template, this is totally in the hands of the user.
+ - linters:
+ - gosec
+ text: G204 # we're shelling out to known commands, not relying on user-defined input.
+ - linters:
+ - gosec
+ # the choice of weakrand is deliberate, hence the named import "weakrand"
+ path: modules/caddyhttp/reverseproxy/selectionpolicies.go
+ text: G404
+ - linters:
+ - gosec
+ path: modules/caddyhttp/reverseproxy/streaming.go
+ text: G404
+ - linters:
+ - dupl
+ path: modules/logging/filters.go
+ - linters:
+ - dupl
+ path: modules/caddyhttp/matchers.go
+ - linters:
+ - dupl
+ path: modules/caddyhttp/vars.go
+ - linters:
+ - errcheck
+ path: _test\.go
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
+formatters:
+ enable:
+ - gci
+ - gofmt
+ - gofumpt
+ - goimports
+ settings:
+ gci:
+ sections:
+ - standard # Standard section: captures all standard packages.
+ - default # Default section: contains all imports that could not be matched to another section type.
+ - prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
+ - prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
+ custom-order: true
+ exclusions:
+ generated: lax
+ paths:
+ - third_party$
+ - builtin$
+ - examples$
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 000000000..a38a13da3
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,20 @@
+repos:
+- repo: https://github.com/gitleaks/gitleaks
+ rev: v8.16.3
+ hooks:
+ - id: gitleaks
+- repo: https://github.com/golangci/golangci-lint
+ rev: v1.52.2
+ hooks:
+ - id: golangci-lint-config-verify
+ - id: golangci-lint
+ - id: golangci-lint-fmt
+- repo: https://github.com/jumanjihouse/pre-commit-hooks
+ rev: 3.0.0
+ hooks:
+ - id: shellcheck
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
diff --git a/README.md b/README.md
index 4bebaafdb..4c091f714 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@
Caddy is an extensible server platform that uses TLS by default.
+
@@ -88,7 +89,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
Requirements:
-- [Go 1.24.0 or newer](https://golang.org/dl/)
+- [Go 1.25.0 or newer](https://golang.org/dl/)
### For development
diff --git a/admin.go b/admin.go
index 6df5a23f7..6ccec41e7 100644
--- a/admin.go
+++ b/admin.go
@@ -221,7 +221,8 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Co
if remote {
muxWrap.remoteControl = admin.Remote
} else {
- muxWrap.enforceHost = !addr.isWildcardInterface()
+ // see comment in allowedOrigins() as to why we disable the host check for unix/fd networks
+ muxWrap.enforceHost = !addr.isWildcardInterface() && !addr.IsUnixNetwork() && !addr.IsFdNetwork()
muxWrap.allowedOrigins = admin.allowedOrigins(addr)
muxWrap.enforceOrigin = admin.EnforceOrigin
}
@@ -310,47 +311,43 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
for _, o := range admin.Origins {
uniqueOrigins[o] = struct{}{}
}
- if admin.Origins == nil {
+ // RFC 2616, Section 14.26:
+ // "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
+ // name for the service being requested, then the Host header field MUST
+ // be given with an empty value."
+ //
+ // UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
+ // Understandable, but frustrating. See:
+ // https://github.com/golang/go/issues/60374
+ // See also the discussion here:
+ // https://github.com/golang/go/issues/61431
+ //
+ // We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
+ // in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
+ // bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
+ // security checks, the infosec community assures me that it is secure to do
+ // so, because:
+ //
+ // 1) Browsers do not allow access to unix sockets
+ // 2) DNS is irrelevant to unix sockets
+ //
+ // If either of those two statements ever fail to hold true, it is not the
+ // fault of Caddy.
+ //
+ // Thus, we do not fill out allowed origins and do not enforce Host
+ // requirements for unix sockets. Enforcing it leads to confusion and
+ // frustration, when UDS have their own permissions from the OS.
+ // Enforcing host requirements here is effectively security theater,
+ // and a false sense of security.
+ //
+ // See also the discussion in #6832.
+ if admin.Origins == nil && !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
if addr.isLoopback() {
- if addr.IsUnixNetwork() || addr.IsFdNetwork() {
- // RFC 2616, Section 14.26:
- // "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
- // name for the service being requested, then the Host header field MUST
- // be given with an empty value."
- //
- // UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
- // Understandable, but frustrating. See:
- // https://github.com/golang/go/issues/60374
- // See also the discussion here:
- // https://github.com/golang/go/issues/61431
- //
- // We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
- // in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
- // bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
- // security checks, the infosec community assures me that it is secure to do
- // so, because:
- // 1) Browsers do not allow access to unix sockets
- // 2) DNS is irrelevant to unix sockets
- //
- // I am not quite ready to trust either of those external factors, so instead
- // of disabling Host/Origin checks, we now allow specific Host values when
- // accessing the admin endpoint over unix sockets. I definitely don't trust
- // DNS (e.g. I don't trust 'localhost' to always resolve to the local host),
- // and IP shouldn't even be used, but if it is for some reason, I think we can
- // at least be reasonably assured that 127.0.0.1 and ::1 route to the local
- // machine, meaning that a hypothetical browser origin would have to be on the
- // local machine as well.
- uniqueOrigins[""] = struct{}{}
- uniqueOrigins["127.0.0.1"] = struct{}{}
- uniqueOrigins["::1"] = struct{}{}
- } else {
- uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
- uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
- uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
- }
- }
- if !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
+ uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
+ uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
+ uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
+ } else {
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
}
}
@@ -427,6 +424,13 @@ func replaceLocalAdminServer(cfg *Config, ctx Context) error {
handler := cfg.Admin.newAdminHandler(addr, false, ctx)
+ // run the provisioners for loaded modules to make sure local
+ // state is properly re-initialized in the new admin server
+ err = cfg.Admin.provisionAdminRouters(ctx)
+ if err != nil {
+ return err
+ }
+
ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{})
if err != nil {
return err
@@ -548,6 +552,13 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
// because we are using TLS authentication instead
handler := cfg.Admin.newAdminHandler(addr, true, ctx)
+ // run the provisioners for loaded modules to make sure local
+ // state is properly re-initialized in the new admin server
+ err = cfg.Admin.provisionAdminRouters(ctx)
+ if err != nil {
+ return err
+ }
+
// create client certificate pool for TLS mutual auth, and extract public keys
// so that we can enforce access controls at the application layer
clientCertPool := x509.NewCertPool()
@@ -935,7 +946,7 @@ func (h adminHandler) originAllowed(origin *url.URL) bool {
return false
}
-// etagHasher returns a the hasher we used on the config to both
+// etagHasher returns the hasher we used on the config to both
// produce and verify ETags.
func etagHasher() hash.Hash { return xxhash.New() }
@@ -1018,6 +1029,13 @@ func handleConfig(w http.ResponseWriter, r *http.Request) error {
return err
}
+ // If this request changed the config, clear the last
+ // config info we have stored, if it is different from
+ // the original source.
+ ClearLastConfigIfDifferent(
+ r.Header.Get("Caddy-Config-Source-File"),
+ r.Header.Get("Caddy-Config-Source-Adapter"))
+
default:
return APIError{
HTTPStatus: http.StatusMethodNotAllowed,
diff --git a/admin_test.go b/admin_test.go
index b00cfaae2..92dd43a5c 100644
--- a/admin_test.go
+++ b/admin_test.go
@@ -19,6 +19,7 @@ import (
"crypto/x509"
"encoding/json"
"fmt"
+ "maps"
"net/http"
"net/http/httptest"
"reflect"
@@ -148,11 +149,9 @@ func TestLoadConcurrent(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
- wg.Add(1)
- go func() {
+ wg.Go(func() {
_ = Load(testCfg, true)
- wg.Done()
- }()
+ })
}
wg.Wait()
}
@@ -206,7 +205,7 @@ func TestETags(t *testing.T) {
}
func BenchmarkLoad(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
Load(testCfg, true)
}
}
@@ -335,9 +334,7 @@ func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
func testGetMetricValue(labels map[string]string) float64 {
promLabels := prometheus.Labels{}
- for k, v := range labels {
- promLabels[k] = v
- }
+ maps.Copy(promLabels, labels)
metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels)
if err != nil {
@@ -377,9 +374,7 @@ func (m *mockModule) CaddyModule() ModuleInfo {
func TestNewAdminHandlerRouterRegistration(t *testing.T) {
originalModules := make(map[string]ModuleInfo)
- for k, v := range modules {
- originalModules[k] = v
- }
+ maps.Copy(originalModules, modules)
defer func() {
modules = originalModules
}()
@@ -479,9 +474,7 @@ func TestAdminRouterProvisioning(t *testing.T) {
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
- }
+ maps.Copy(originalModules, modules)
defer func() {
modules = originalModules
}()
@@ -531,6 +524,7 @@ func TestAdminRouterProvisioning(t *testing.T) {
}
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
@@ -543,12 +537,8 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
Network: "unix",
Host: "/tmp/caddy.sock",
},
- origins: nil, // default origins
- expectOrigins: []string{
- "", // empty host as per RFC 2616
- "127.0.0.1",
- "::1",
- },
+ origins: nil, // default origins
+ expectOrigins: []string{},
},
{
name: "unix socket with custom origins",
@@ -578,7 +568,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
},
}
- for _, test := range tests {
+ for i, test := range tests {
t.Run(test.name, func(t *testing.T) {
admin := AdminConfig{
Origins: test.origins,
@@ -592,7 +582,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
}
if len(gotOrigins) != len(test.expectOrigins) {
- t.Errorf("Expected %d origins but got %d", len(test.expectOrigins), len(gotOrigins))
+ t.Errorf("%d: Expected %d origins but got %d", i, len(test.expectOrigins), len(gotOrigins))
return
}
@@ -607,7 +597,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
}
if !reflect.DeepEqual(expectMap, gotMap) {
- t.Errorf("Origins mismatch.\nExpected: %v\nGot: %v", test.expectOrigins, gotOrigins)
+ t.Errorf("%d: Origins mismatch.\nExpected: %v\nGot: %v", i, test.expectOrigins, gotOrigins)
}
})
}
@@ -777,9 +767,7 @@ func (m *mockIssuerModule) CaddyModule() ModuleInfo {
func TestManageIdentity(t *testing.T) {
originalModules := make(map[string]ModuleInfo)
- for k, v := range modules {
- originalModules[k] = v
- }
+ maps.Copy(originalModules, modules)
defer func() {
modules = originalModules
}()
diff --git a/caddy.go b/caddy.go
index 758b0b2f6..5f71d8e8b 100644
--- a/caddy.go
+++ b/caddy.go
@@ -81,13 +81,17 @@ type Config struct {
// associated value.
AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
- apps map[string]App
- storage certmagic.Storage
+ apps map[string]App
+
+ // failedApps is a map of apps that failed to provision with their underlying error.
+ failedApps map[string]error
+ storage certmagic.Storage
+ eventEmitter eventEmitter
cancelFunc context.CancelFunc
- // filesystems is a dict of filesystems that will later be loaded from and added to.
- filesystems FileSystems
+ // fileSystems is a dict of fileSystems that will later be loaded from and added to.
+ fileSystems FileSystems
}
// App is a thing that Caddy runs.
@@ -407,11 +411,23 @@ func run(newCfg *Config, start bool) (Context, error) {
return ctx, nil
}
+ defer func() {
+ // if newCfg fails to start completely, clean up the already provisioned modules
+ // partially copied from provisionContext
+ if err != nil {
+ globalMetrics.configSuccess.Set(0)
+ ctx.cfg.cancelFunc()
+
+ if currentCtx.cfg != nil {
+ certmagic.Default.Storage = currentCtx.cfg.storage
+ }
+ }
+ }()
+
// Provision any admin routers which may need to access
// some of the other apps at runtime
err = ctx.cfg.Admin.provisionAdminRouters(ctx)
if err != nil {
- globalMetrics.configSuccess.Set(0)
return ctx, err
}
@@ -437,14 +453,18 @@ func run(newCfg *Config, start bool) (Context, error) {
return nil
}()
if err != nil {
- globalMetrics.configSuccess.Set(0)
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,
// such as remote admin endpoint, config loader, etc.
- return ctx, finishSettingUp(ctx, ctx.cfg)
+ err = finishSettingUp(ctx, ctx.cfg)
+ return ctx, err
}
// provisionContext creates a new context from the given configuration and provisions
@@ -500,19 +520,12 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
return ctx, err
}
- // start the admin endpoint (and stop any prior one)
- if replaceAdminServer {
- err = replaceLocalAdminServer(newCfg, ctx)
- if err != nil {
- return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
- }
- }
-
// create the new filesystem map
- newCfg.filesystems = &filesystems.FilesystemMap{}
+ newCfg.fileSystems = &filesystems.FileSystemMap{}
// prepare the new config for use
newCfg.apps = make(map[string]App)
+ newCfg.failedApps = make(map[string]error)
// set up global storage and make it CertMagic's default storage, too
err = func() error {
@@ -539,6 +552,14 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
return ctx, err
}
+ // start the admin endpoint (and stop any prior one)
+ if replaceAdminServer {
+ err = replaceLocalAdminServer(newCfg, ctx)
+ if err != nil {
+ return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
+ }
+ }
+
// Load and Provision each app and their submodules
err = func() error {
for appName := range newCfg.AppsRaw {
@@ -696,6 +717,9 @@ func unsyncedStop(ctx Context) {
return
}
+ // TODO: This event is experimental and subject to change.
+ ctx.emitEvent("stopping", nil)
+
// stop each app
for name, a := range ctx.cfg.apps {
err := a.Stop()
@@ -951,11 +975,11 @@ func Version() (simple, full string) {
if CustomVersion != "" {
full = CustomVersion
simple = CustomVersion
- return
+ return simple, full
}
full = "unknown"
simple = "unknown"
- return
+ return simple, full
}
// find the Caddy module in the dependency list
for _, dep := range bi.Deps {
@@ -1035,9 +1059,101 @@ func Version() (simple, full string) {
}
}
- return
+ return simple, full
}
+// 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)
+ var source string
+ if e.Origin() == nil {
+ source = "caddy"
+ } else {
+ source = string(e.Origin().CaddyModule().ID)
+ }
+ return CloudEvent{
+ ID: e.id.String(),
+ Source: source,
+ 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.
// This function is experimental and might be changed
// or removed in the future.
@@ -1081,6 +1197,91 @@ var (
rawCfgMu sync.RWMutex
)
+// lastConfigFile and lastConfigAdapter remember the source config
+// file and adapter used when Caddy was started via the CLI "run" command.
+// These are consulted by the SIGUSR1 handler to attempt reloading from
+// the same source. They are intentionally not set for other entrypoints
+// such as "caddy start" or subcommands like file-server.
+var (
+ lastConfigMu sync.RWMutex
+ lastConfigFile string
+ lastConfigAdapter string
+)
+
+// reloadFromSourceFunc is the type of stored callback
+// which is called when we receive a SIGUSR1 signal.
+type reloadFromSourceFunc func(file, adapter string) error
+
+// reloadFromSourceCallback is the stored callback
+// which is called when we receive a SIGUSR1 signal.
+var reloadFromSourceCallback reloadFromSourceFunc
+
+// errReloadFromSourceUnavailable is returned when no reload-from-source callback is set.
+var errReloadFromSourceUnavailable = errors.New("reload from source unavailable in this process") //nolint:unused
+
+// SetLastConfig records the given source file and adapter as the
+// last-known external configuration source. Intended to be called
+// only when starting via "caddy run --config --adapter ".
+func SetLastConfig(file, adapter string, fn reloadFromSourceFunc) {
+ lastConfigMu.Lock()
+ lastConfigFile = file
+ lastConfigAdapter = adapter
+ reloadFromSourceCallback = fn
+ lastConfigMu.Unlock()
+}
+
+// ClearLastConfigIfDifferent clears the recorded last-config if the provided
+// source file/adapter do not match the recorded last-config. If both srcFile
+// and srcAdapter are empty, the last-config is cleared.
+func ClearLastConfigIfDifferent(srcFile, srcAdapter string) {
+ if (srcFile != "" || srcAdapter != "") && lastConfigMatches(srcFile, srcAdapter) {
+ return
+ }
+ SetLastConfig("", "", nil)
+}
+
+// getLastConfig returns the last-known config file and adapter.
+func getLastConfig() (file, adapter string, fn reloadFromSourceFunc) {
+ lastConfigMu.RLock()
+ f, a, cb := lastConfigFile, lastConfigAdapter, reloadFromSourceCallback
+ lastConfigMu.RUnlock()
+ return f, a, cb
+}
+
+// lastConfigMatches returns true if the provided source file and/or adapter
+// matches the recorded last-config. Matching rules (in priority order):
+// 1. If srcAdapter is provided and differs from the recorded adapter, no match.
+// 2. If srcFile exactly equals the recorded file, match.
+// 3. If both sides can be made absolute and equal, match.
+// 4. If basenames are equal, match.
+func lastConfigMatches(srcFile, srcAdapter string) bool {
+ lf, la, _ := getLastConfig()
+
+ // If adapter is provided, it must match.
+ if srcAdapter != "" && srcAdapter != la {
+ return false
+ }
+
+ // Quick equality check.
+ if srcFile == lf {
+ return true
+ }
+
+ // Try absolute path comparison.
+ sAbs, sErr := filepath.Abs(srcFile)
+ lAbs, lErr := filepath.Abs(lf)
+ if sErr == nil && lErr == nil && sAbs == lAbs {
+ return true
+ }
+
+ // Final fallback: basename equality.
+ if filepath.Base(srcFile) == filepath.Base(lf) {
+ return true
+ }
+
+ return false
+}
+
// errSameConfig is returned if the new config is the same
// as the old one. This isn't usually an actual, actionable
// error; it's mostly a sentinel value.
diff --git a/caddy_test.go b/caddy_test.go
index adf14350e..08fa5c0d0 100644
--- a/caddy_test.go
+++ b/caddy_test.go
@@ -15,6 +15,7 @@
package caddy
import (
+ "context"
"testing"
"time"
)
@@ -72,3 +73,21 @@ func TestParseDuration(t *testing.T) {
}
}
}
+
+func TestEvent_CloudEvent_NilOrigin(t *testing.T) {
+ ctx, _ := NewContext(Context{Context: context.Background()}) // module will be nil by default
+ event, err := NewEvent(ctx, "started", nil)
+ if err != nil {
+ t.Fatalf("NewEvent() error = %v", err)
+ }
+
+ // This should not panic
+ ce := event.CloudEvent()
+
+ if ce.Source != "caddy" {
+ t.Errorf("Expected CloudEvent Source to be 'caddy', got '%s'", ce.Source)
+ }
+ if ce.Type != "started" {
+ t.Errorf("Expected CloudEvent Type to be 'started', got '%s'", ce.Type)
+ }
+}
diff --git a/caddyconfig/caddyfile/adapter.go b/caddyconfig/caddyfile/adapter.go
index da4f98337..449370dc6 100644
--- a/caddyconfig/caddyfile/adapter.go
+++ b/caddyconfig/caddyfile/adapter.go
@@ -68,7 +68,7 @@ func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconf
// TODO: also perform this check on imported files
func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
// replace windows-style newlines to normalize comparison
- normalizedBody := bytes.Replace(body, []byte("\r\n"), []byte("\n"), -1)
+ normalizedBody := bytes.ReplaceAll(body, []byte("\r\n"), []byte("\n"))
formatted := Format(normalizedBody)
if bytes.Equal(formatted, normalizedBody) {
diff --git a/caddyconfig/caddyfile/dispenser.go b/caddyconfig/caddyfile/dispenser.go
index 325bb54d3..d95196e48 100644
--- a/caddyconfig/caddyfile/dispenser.go
+++ b/caddyconfig/caddyfile/dispenser.go
@@ -308,9 +308,9 @@ func (d *Dispenser) CountRemainingArgs() int {
}
// RemainingArgs loads any more arguments (tokens on the same line)
-// into a slice and returns them. Open curly brace tokens also indicate
-// the end of arguments, and the curly brace is not included in
-// the return value nor is it loaded.
+// into a slice of strings and returns them. Open curly brace tokens
+// also indicate the end of arguments, and the curly brace is not
+// included in the return value nor is it loaded.
func (d *Dispenser) RemainingArgs() []string {
var args []string
for d.NextArg() {
@@ -320,9 +320,9 @@ func (d *Dispenser) RemainingArgs() []string {
}
// RemainingArgsRaw loads any more arguments (tokens on the same line,
-// retaining quotes) into a slice and returns them. Open curly brace
-// tokens also indicate the end of arguments, and the curly brace is
-// not included in the return value nor is it loaded.
+// retaining quotes) into a slice of strings and returns them.
+// Open curly brace tokens also indicate the end of arguments,
+// and the curly brace is not included in the return value nor is it loaded.
func (d *Dispenser) RemainingArgsRaw() []string {
var args []string
for d.NextArg() {
@@ -331,6 +331,18 @@ func (d *Dispenser) RemainingArgsRaw() []string {
return args
}
+// RemainingArgsAsTokens loads any more arguments (tokens on the same line)
+// into a slice of Token-structs and returns them. Open curly brace tokens
+// also indicate the end of arguments, and the curly brace is not included
+// in the return value nor is it loaded.
+func (d *Dispenser) RemainingArgsAsTokens() []Token {
+ var args []Token
+ for d.NextArg() {
+ args = append(args, d.Token())
+ }
+ return args
+}
+
// NewFromNextSegment returns a new dispenser with a copy of
// the tokens from the current token until the end of the
// "directive" whether that be to the end of the line or
diff --git a/caddyconfig/caddyfile/dispenser_test.go b/caddyconfig/caddyfile/dispenser_test.go
index 0f6ee5043..f5d226005 100644
--- a/caddyconfig/caddyfile/dispenser_test.go
+++ b/caddyconfig/caddyfile/dispenser_test.go
@@ -274,6 +274,66 @@ func TestDispenser_RemainingArgs(t *testing.T) {
}
}
+func TestDispenser_RemainingArgsAsTokens(t *testing.T) {
+ input := `dir1 arg1 arg2 arg3
+ dir2 arg4 arg5
+ dir3 arg6 { arg7
+ dir4`
+ d := NewTestDispenser(input)
+
+ d.Next() // dir1
+
+ args := d.RemainingArgsAsTokens()
+
+ tokenTexts := make([]string, 0, len(args))
+ for _, arg := range args {
+ tokenTexts = append(tokenTexts, arg.Text)
+ }
+
+ if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(tokenTexts, expected) {
+ t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
+ }
+
+ d.Next() // dir2
+
+ args = d.RemainingArgsAsTokens()
+
+ tokenTexts = tokenTexts[:0]
+ for _, arg := range args {
+ tokenTexts = append(tokenTexts, arg.Text)
+ }
+
+ if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(tokenTexts, expected) {
+ t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
+ }
+
+ d.Next() // dir3
+
+ args = d.RemainingArgsAsTokens()
+ tokenTexts = tokenTexts[:0]
+ for _, arg := range args {
+ tokenTexts = append(tokenTexts, arg.Text)
+ }
+
+ if expected := []string{"arg6"}; !reflect.DeepEqual(tokenTexts, expected) {
+ t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
+ }
+
+ d.Next() // {
+ d.Next() // arg7
+ d.Next() // dir4
+
+ args = d.RemainingArgsAsTokens()
+ tokenTexts = tokenTexts[:0]
+ for _, arg := range args {
+ tokenTexts = append(tokenTexts, arg.Text)
+ }
+
+ if len(args) != 0 {
+ t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", []string{}, tokenTexts)
+ }
+}
+
func TestDispenser_ArgErr_Err(t *testing.T) {
input := `dir1 {
}
diff --git a/caddyconfig/caddyfile/formatter.go b/caddyconfig/caddyfile/formatter.go
index d35f0ac6b..8a757ea58 100644
--- a/caddyconfig/caddyfile/formatter.go
+++ b/caddyconfig/caddyfile/formatter.go
@@ -61,7 +61,8 @@ func Format(input []byte) []byte {
heredocMarker []rune
heredocClosingMarker []rune
- nesting int // indentation level
+ nesting int // indentation level
+ withinBackquote bool
)
write := func(ch rune) {
@@ -88,9 +89,12 @@ func Format(input []byte) []byte {
}
panic(err)
}
+ if ch == '`' {
+ withinBackquote = !withinBackquote
+ }
// detect whether we have the start of a heredoc
- if !quoted && !(heredoc != heredocClosed || heredocEscaped) &&
+ if !quoted && (heredoc == heredocClosed && !heredocEscaped) &&
space && last == '<' && ch == '<' {
write(ch)
heredoc = heredocOpening
@@ -220,7 +224,7 @@ func Format(input []byte) []byte {
openBrace = false
if beginningOfLine {
indent()
- } else if !openBraceSpace {
+ } else if !openBraceSpace || !unicode.IsSpace(last) {
write(' ')
}
write('{')
@@ -236,14 +240,23 @@ func Format(input []byte) []byte {
switch {
case ch == '{':
openBrace = true
- openBraceWritten = false
openBraceSpace = spacePrior && !beginningOfLine
- if openBraceSpace {
+ if openBraceSpace && newLines == 0 {
write(' ')
}
+ openBraceWritten = false
+ if withinBackquote {
+ write('{')
+ openBraceWritten = true
+ continue
+ }
continue
case ch == '}' && (spacePrior || !openBrace):
+ if withinBackquote {
+ write('}')
+ continue
+ }
if last != '\n' {
nextLine()
}
diff --git a/caddyconfig/caddyfile/formatter_test.go b/caddyconfig/caddyfile/formatter_test.go
index 6eec822fe..29b910ff1 100644
--- a/caddyconfig/caddyfile/formatter_test.go
+++ b/caddyconfig/caddyfile/formatter_test.go
@@ -432,6 +432,31 @@ block2 {
heredoc \< 1 && string(val[:2]) == "<<" {
// a space means it's just a regular token and not a heredoc
if ch == ' ' {
@@ -323,7 +323,8 @@ func (l *lexer) finalizeHeredoc(val []rune, marker string) ([]rune, error) {
// if the padding doesn't match exactly at the start then we can't safely strip
if index != 0 {
- return nil, fmt.Errorf("mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker", marker, l.line+lineNum+1, lineText, paddingToStrip)
+ cleanLineText := strings.TrimRight(lineText, "\r\n")
+ return nil, fmt.Errorf("mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker", marker, l.line+lineNum+1, cleanLineText, paddingToStrip)
}
// strip, then append the line, with the newline, to the output.
diff --git a/caddyconfig/caddyfile/parse.go b/caddyconfig/caddyfile/parse.go
index d04a1ac46..8439f3731 100644
--- a/caddyconfig/caddyfile/parse.go
+++ b/caddyconfig/caddyfile/parse.go
@@ -379,28 +379,23 @@ func (p *parser) doImport(nesting int) error {
if len(blockTokens) > 0 {
// use such tokens to create a new dispenser, and then use it to parse each block
bd := NewDispenser(blockTokens)
+
+ // one iteration processes one sub-block inside the import
for bd.Next() {
- // see if we can grab a key
- var currentMappingKey string
- if bd.Val() == "{" {
+ currentMappingKey := bd.Val()
+
+ if currentMappingKey == "{" {
return p.Err("anonymous blocks are not supported")
}
- currentMappingKey = bd.Val()
- currentMappingTokens := []Token{}
- // read all args until end of line / {
- if bd.NextArg() {
+
+ // load up all arguments (if there even are any)
+ currentMappingTokens := bd.RemainingArgsAsTokens()
+
+ // load up the entire block
+ for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
currentMappingTokens = append(currentMappingTokens, bd.Token())
- for bd.NextArg() {
- currentMappingTokens = append(currentMappingTokens, bd.Token())
- }
- // TODO(elee1766): we don't enter another mapping here because it's annoying to extract the { and } properly.
- // maybe someone can do that in the future
- } else {
- // attempt to enter a block and add tokens to the currentMappingTokens
- for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
- currentMappingTokens = append(currentMappingTokens, bd.Token())
- }
}
+
blockMapping[currentMappingKey] = currentMappingTokens
}
}
@@ -538,29 +533,24 @@ func (p *parser) doImport(nesting int) error {
}
// if it is {block}, we substitute with all tokens in the block
// if it is {blocks.*}, we substitute with the tokens in the mapping for the *
- var skip bool
var tokensToAdd []Token
+ foundBlockDirective := false
switch {
case token.Text == "{block}":
+ foundBlockDirective = true
tokensToAdd = blockTokens
case strings.HasPrefix(token.Text, "{blocks.") && strings.HasSuffix(token.Text, "}"):
+ foundBlockDirective = true
// {blocks.foo.bar} will be extracted to key `foo.bar`
blockKey := strings.TrimPrefix(strings.TrimSuffix(token.Text, "}"), "{blocks.")
val, ok := blockMapping[blockKey]
if ok {
tokensToAdd = val
}
- default:
- skip = true
}
- if !skip {
- if len(tokensToAdd) == 0 {
- // if there is no content in the snippet block, don't do any replacement
- // this allows snippets which contained {block}/{block.*} before this change to continue functioning as normal
- tokensCopy = append(tokensCopy, token)
- } else {
- tokensCopy = append(tokensCopy, tokensToAdd...)
- }
+
+ if foundBlockDirective {
+ tokensCopy = append(tokensCopy, tokensToAdd...)
continue
}
diff --git a/caddyconfig/caddyfile/parse_test.go b/caddyconfig/caddyfile/parse_test.go
index d3fada4e0..bf149e635 100644
--- a/caddyconfig/caddyfile/parse_test.go
+++ b/caddyconfig/caddyfile/parse_test.go
@@ -18,6 +18,7 @@ import (
"bytes"
"os"
"path/filepath"
+ "strings"
"testing"
)
@@ -884,6 +885,51 @@ func TestRejectsGlobalMatcher(t *testing.T) {
}
}
+func TestRejectAnonymousImportBlock(t *testing.T) {
+ p := testParser(`
+ (site) {
+ http://{args[0]} https://{args[0]} {
+ {block}
+ }
+ }
+
+ import site test.domain {
+ {
+ header_up Host {host}
+ header_up X-Real-IP {remote_host}
+ }
+ }
+ `)
+ _, err := p.parseAll()
+ if err == nil {
+ t.Fatal("Expected an error, but got nil")
+ }
+ expected := "anonymous blocks are not supported"
+ if !strings.HasPrefix(err.Error(), "anonymous blocks are not supported") {
+ t.Errorf("Expected error to start with '%s' but got '%v'", expected, err)
+ }
+}
+
+func TestAcceptSiteImportWithBraces(t *testing.T) {
+ p := testParser(`
+ (site) {
+ http://{args[0]} https://{args[0]} {
+ {block}
+ }
+ }
+
+ import site test.domain {
+ reverse_proxy http://192.168.1.1:8080 {
+ header_up Host {host}
+ }
+ }
+ `)
+ _, err := p.parseAll()
+ if err != nil {
+ t.Errorf("Expected error to be nil but got '%v'", err)
+ }
+}
+
func testParser(input string) parser {
return parser{Dispenser: NewTestDispenser(input)}
}
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 3fc08b2c8..061aaa48b 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -15,6 +15,7 @@
package httpcaddyfile
import (
+ "encoding/json"
"fmt"
"html"
"net/http"
@@ -90,7 +91,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// curves
// client_auth {
// mode [request|require|verify_if_given|require_and_verify]
-// trust_pool [...]
+// trust_pool [...]
// trusted_leaf_cert
// trusted_leaf_cert_file
// }
@@ -129,6 +130,9 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
var reusePrivateKeys bool
var forceAutomate bool
+ // Track which DNS challenge options are set
+ var dnsOptionsSet []string
+
firstLine := h.RemainingArgs()
switch len(firstLine) {
case 0:
@@ -349,6 +353,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
+ dnsOptionsSet = append(dnsOptionsSet, "resolvers")
acmeIssuer.Challenges.DNS.Resolvers = args
case "propagation_delay":
@@ -370,6 +375,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
+ dnsOptionsSet = append(dnsOptionsSet, "propagation_delay")
acmeIssuer.Challenges.DNS.PropagationDelay = caddy.Duration(delay)
case "propagation_timeout":
@@ -397,6 +403,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
+ dnsOptionsSet = append(dnsOptionsSet, "propagation_timeout")
acmeIssuer.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout)
case "dns_ttl":
@@ -418,6 +425,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
+ dnsOptionsSet = append(dnsOptionsSet, "dns_ttl")
acmeIssuer.Challenges.DNS.TTL = caddy.Duration(ttl)
case "dns_challenge_override_domain":
@@ -434,6 +442,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
if acmeIssuer.Challenges.DNS == nil {
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
}
+ dnsOptionsSet = append(dnsOptionsSet, "dns_challenge_override_domain")
acmeIssuer.Challenges.DNS.OverrideDomain = arg[0]
case "ca_root":
@@ -469,6 +478,18 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
}
}
+ // Validate DNS challenge config: any DNS challenge option except "dns" requires a DNS provider
+ if acmeIssuer != nil && acmeIssuer.Challenges != nil && acmeIssuer.Challenges.DNS != nil {
+ dnsCfg := acmeIssuer.Challenges.DNS
+ providerSet := dnsCfg.ProviderRaw != nil || h.Option("dns") != nil || h.Option("acme_dns") != nil
+ if len(dnsOptionsSet) > 0 && !providerSet {
+ return nil, h.Errf(
+ "setting DNS challenge options [%s] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option)",
+ strings.Join(dnsOptionsSet, ", "),
+ )
+ }
+ }
+
// a naked tls directive is not allowed
if len(firstLine) == 0 && !hasBlock {
return nil, h.ArgErr()
@@ -843,13 +864,18 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
return nil, h.Errf("segment was not parsed as a subroute")
}
+ // wrap the subroutes
+ wrappingRoute := caddyhttp.Route{
+ HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)},
+ }
+ subroute = &caddyhttp.Subroute{
+ Routes: []caddyhttp.Route{wrappingRoute},
+ }
if expression != "" {
statusMatcher := caddy.ModuleMap{
"expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}),
}
- for i := range subroute.Routes {
- subroute.Routes[i].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
- }
+ subroute.Routes[0].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
}
return []ConfigValue{
{
@@ -1160,6 +1186,11 @@ func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
if h.NextArg() {
return nil, h.ArgErr()
}
+
+ if h.NextBlock(0) {
+ return nil, h.Err("log_skip directive does not accept blocks")
+ }
+
return caddyhttp.VarsMiddleware{"log_skip": true}, nil
}
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index f0687a7e9..eac7f5dc2 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -16,6 +16,7 @@ package httpcaddyfile
import (
"encoding/json"
+ "maps"
"net"
"slices"
"sort"
@@ -173,10 +174,12 @@ func RegisterDirectiveOrder(dir string, position Positional, standardDir string)
if d != standardDir {
continue
}
- if position == Before {
+ switch position {
+ case Before:
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
- } else if position == After {
+ case After:
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
+ case First, Last:
}
break
}
@@ -365,9 +368,7 @@ func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
// copy existing matcher definitions so we can augment
// new ones that are defined only in this scope
matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
- for key, val := range h.matcherDefs {
- matcherDefs[key] = val
- }
+ maps.Copy(matcherDefs, h.matcherDefs)
// find and extract any embedded matcher definitions in this scope
for i := 0; i < len(segments); i++ {
@@ -483,12 +484,29 @@ func sortRoutes(routes []ConfigValue) {
// we can only confidently compare path lengths if both
// directives have a single path to match (issue #5037)
if iPathLen > 0 && jPathLen > 0 {
+ // trim the trailing wildcard if there is one
+ iPathTrimmed := strings.TrimSuffix(iPM[0], "*")
+ jPathTrimmed := strings.TrimSuffix(jPM[0], "*")
+
// if both paths are the same except for a trailing wildcard,
// sort by the shorter path first (which is more specific)
- if strings.TrimSuffix(iPM[0], "*") == strings.TrimSuffix(jPM[0], "*") {
+ if iPathTrimmed == jPathTrimmed {
return iPathLen < jPathLen
}
+ // we use the trimmed length to compare the paths
+ // https://github.com/caddyserver/caddy/issues/7012#issuecomment-2870142195
+ // credit to https://github.com/Hellio404
+ // for sorts with many items, mixing matchers w/ and w/o wildcards will confuse the sort and result in incorrect orders
+ iPathLen = len(iPathTrimmed)
+ jPathLen = len(jPathTrimmed)
+
+ // if both paths have the same length, sort lexically
+ // https://github.com/caddyserver/caddy/pull/7015#issuecomment-2871993588
+ if iPathLen == jPathLen {
+ return iPathTrimmed < jPathTrimmed
+ }
+
// sort most-specific (longest) path first
return iPathLen > jPathLen
}
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index ae6f5ddee..3dcd3ea5b 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -633,12 +633,6 @@ func (st *ServerType) serversFromPairings(
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
}
srv.AutoHTTPS.IgnoreLoadedCerts = true
-
- case "prefer_wildcard":
- if srv.AutoHTTPS == nil {
- srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
- }
- srv.AutoHTTPS.PreferWildcard = true
}
}
@@ -706,16 +700,6 @@ func (st *ServerType) serversFromPairings(
return specificity(iLongestHost) > specificity(jLongestHost)
})
- // collect all hosts that have a wildcard in them
- wildcardHosts := []string{}
- for _, sblock := range p.serverBlocks {
- for _, addr := range sblock.parsedKeys {
- if strings.HasPrefix(addr.Host, "*.") {
- wildcardHosts = append(wildcardHosts, addr.Host[2:])
- }
- }
- }
-
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
@@ -801,7 +785,13 @@ func (st *ServerType) serversFromPairings(
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
+ // 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)
hasCatchAllTLSConnPolicy = len(hosts) == 0
@@ -841,18 +831,6 @@ func (st *ServerType) serversFromPairings(
addressQualifiesForTLS = true
}
- // If prefer wildcard is enabled, then we add hosts that are
- // already covered by the wildcard to the skip list
- if addressQualifiesForTLS && srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard {
- baseDomain := addr.Host
- if idx := strings.Index(baseDomain, "."); idx != -1 {
- baseDomain = baseDomain[idx+1:]
- }
- if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
- srv.AutoHTTPS.SkipCerts = append(srv.AutoHTTPS.SkipCerts, addr.Host)
- }
- }
-
// predict whether auto-HTTPS will add the conn policy for us; if so, we
// may not need to add one for this server
autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&
@@ -1083,11 +1061,40 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
// if they're exactly equal in every way, just keep one of them
if reflect.DeepEqual(cps[i], cps[j]) {
- cps = append(cps[:j], cps[j+1:]...)
+ cps = slices.Delete(cps, j, j+1)
i--
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
// be identical, or we have to be able to combine them safely
if reflect.DeepEqual(cps[i].MatchersRaw, cps[j].MatchersRaw) {
@@ -1189,12 +1196,13 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
}
}
- cps = append(cps[:j], cps[j+1:]...)
+ cps = slices.Delete(cps, j, j+1)
i--
break
}
}
}
+
return cps, nil
}
diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go
index e48a52577..336c6999f 100644
--- a/caddyconfig/httpcaddyfile/options.go
+++ b/caddyconfig/httpcaddyfile/options.go
@@ -458,8 +458,6 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
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'")
}
@@ -557,8 +555,14 @@ func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
d.Next() // consume option name
+ optName := d.Val()
- if !d.Next() { // get DNS module name
+ // get DNS module name
+ if !d.Next() {
+ // this is allowed if this is the "acme_dns" option since it may refer to the globally-configured "dns" option's value
+ if optName == "acme_dns" {
+ return nil, nil
+ }
return nil, d.ArgErr()
}
modID := "dns.providers." + d.Val()
diff --git a/caddyconfig/httpcaddyfile/pkiapp.go b/caddyconfig/httpcaddyfile/pkiapp.go
index c57263baf..25b6c221c 100644
--- a/caddyconfig/httpcaddyfile/pkiapp.go
+++ b/caddyconfig/httpcaddyfile/pkiapp.go
@@ -15,6 +15,8 @@
package httpcaddyfile
import (
+ "slices"
+
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
@@ -178,6 +180,15 @@ func (st ServerType) buildPKIApp(
if _, ok := options["skip_install_trust"]; ok {
skipInstallTrust = true
}
+
+ // check if auto_https is off - in that case we should not create
+ // any PKI infrastructure even with skip_install_trust directive
+ autoHTTPS := []string{}
+ if ah, ok := options["auto_https"].([]string); ok {
+ autoHTTPS = ah
+ }
+ autoHTTPSOff := slices.Contains(autoHTTPS, "off")
+
falseBool := false
// Load the PKI app configured via global options
@@ -218,7 +229,8 @@ func (st ServerType) buildPKIApp(
// if there was no CAs defined in any of the servers,
// and we were requested to not install trust, then
// add one for the default/local CA to do so
- if len(pkiApp.CAs) == 0 && skipInstallTrust {
+ // only if auto_https is not completely disabled
+ if len(pkiApp.CAs) == 0 && skipInstallTrust && !autoHTTPSOff {
ca := new(caddypki.CA)
ca.ID = caddypki.DefaultCAID
ca.InstallTrust = &falseBool
diff --git a/caddyconfig/httpcaddyfile/serveroptions.go b/caddyconfig/httpcaddyfile/serveroptions.go
index d60ce51a9..9431f1aed 100644
--- a/caddyconfig/httpcaddyfile/serveroptions.go
+++ b/caddyconfig/httpcaddyfile/serveroptions.go
@@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"slices"
+ "strconv"
"github.com/dustin/go-humanize"
@@ -42,12 +43,15 @@ type serverOptions struct {
WriteTimeout caddy.Duration
IdleTimeout caddy.Duration
KeepAliveInterval caddy.Duration
+ KeepAliveIdle caddy.Duration
+ KeepAliveCount int
MaxHeaderBytes int
EnableFullDuplex bool
Protocols []string
StrictSNIHost *bool
TrustedProxiesRaw json.RawMessage
TrustedProxiesStrict int
+ TrustedProxiesUnix bool
ClientIPHeaders []string
ShouldLogCredentials bool
Metrics *caddyhttp.Metrics
@@ -142,6 +146,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
}
}
+
case "keepalive_interval":
if !d.NextArg() {
return nil, d.ArgErr()
@@ -152,6 +157,26 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
}
serverOpts.KeepAliveInterval = caddy.Duration(dur)
+ case "keepalive_idle":
+ if !d.NextArg() {
+ return nil, d.ArgErr()
+ }
+ dur, err := caddy.ParseDuration(d.Val())
+ if err != nil {
+ return nil, d.Errf("parsing keepalive idle duration: %v", err)
+ }
+ serverOpts.KeepAliveIdle = caddy.Duration(dur)
+
+ case "keepalive_count":
+ if !d.NextArg() {
+ return nil, d.ArgErr()
+ }
+ cnt, err := strconv.ParseInt(d.Val(), 10, 32)
+ if err != nil {
+ return nil, d.Errf("parsing keepalive count int: %v", err)
+ }
+ serverOpts.KeepAliveCount = int(cnt)
+
case "max_header_size":
var sizeStr string
if !d.AllArgs(&sizeStr) {
@@ -227,6 +252,12 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
}
serverOpts.TrustedProxiesStrict = 1
+ case "trusted_proxies_unix":
+ if d.NextArg() {
+ return nil, d.ArgErr()
+ }
+ serverOpts.TrustedProxiesUnix = true
+
case "client_ip_headers":
headers := d.RemainingArgs()
for _, header := range headers {
@@ -309,6 +340,8 @@ func applyServerOptions(
server.WriteTimeout = opts.WriteTimeout
server.IdleTimeout = opts.IdleTimeout
server.KeepAliveInterval = opts.KeepAliveInterval
+ server.KeepAliveIdle = opts.KeepAliveIdle
+ server.KeepAliveCount = opts.KeepAliveCount
server.MaxHeaderBytes = opts.MaxHeaderBytes
server.EnableFullDuplex = opts.EnableFullDuplex
server.Protocols = opts.Protocols
@@ -316,6 +349,7 @@ func applyServerOptions(
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
server.ClientIPHeaders = opts.ClientIPHeaders
server.TrustedProxiesStrict = opts.TrustedProxiesStrict
+ server.TrustedProxiesUnix = opts.TrustedProxiesUnix
server.Metrics = opts.Metrics
if opts.ShouldLogCredentials {
if server.Logs == nil {
diff --git a/caddyconfig/httpcaddyfile/shorthands.go b/caddyconfig/httpcaddyfile/shorthands.go
index ca6e4f92c..bf612e092 100644
--- a/caddyconfig/httpcaddyfile/shorthands.go
+++ b/caddyconfig/httpcaddyfile/shorthands.go
@@ -64,10 +64,13 @@ func placeholderShorthands() []string {
"{orig_?query}", "{http.request.orig_uri.prefixed_query}",
"{method}", "{http.request.method}",
"{uri}", "{http.request.uri}",
+ "{%uri}", "{http.request.uri_escaped}",
"{path}", "{http.request.uri.path}",
+ "{%path}", "{http.request.uri.path_escaped}",
"{dir}", "{http.request.uri.path.dir}",
"{file}", "{http.request.uri.path.file}",
"{query}", "{http.request.uri.query}",
+ "{%query}", "{http.request.uri.query_escaped}",
"{?query}", "{http.request.uri.prefixed_query}",
"{remote}", "{http.request.remote}",
"{remote_host}", "{http.request.remote.host}",
diff --git a/caddyconfig/httpcaddyfile/tlsapp.go b/caddyconfig/httpcaddyfile/tlsapp.go
index 8a21ca038..30948f84f 100644
--- a/caddyconfig/httpcaddyfile/tlsapp.go
+++ b/caddyconfig/httpcaddyfile/tlsapp.go
@@ -92,11 +92,9 @@ func (st ServerType) buildTLSApp(
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
}
- // collect all hosts that have a wildcard in them, and arent HTTP
- wildcardHosts := []string{}
- // hosts that have been explicitly marked to be automated,
- // even if covered by another wildcard
- forcedAutomatedNames := make(map[string]struct{})
+ 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 {
@@ -153,7 +151,7 @@ func (st ServerType) buildTLSApp(
ap.OnDemand = true
}
- // collect hosts that are forced to be automated
+ // 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{}{}
@@ -340,7 +338,7 @@ func (st ServerType) buildTLSApp(
combined = reflect.New(reflect.TypeOf(cl)).Elem()
}
clVal := reflect.ValueOf(cl)
- for i := 0; i < clVal.Len(); i++ {
+ for i := range clVal.Len() {
combined = reflect.Append(combined, clVal.Index(i))
}
loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
@@ -375,7 +373,9 @@ func (st ServerType) buildTLSApp(
return nil, warnings, err
}
for _, cfg := range ech.Configs {
- ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
+ if cfg.PublicName != "" {
+ ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
+ }
}
if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig)
@@ -464,12 +464,12 @@ func (st ServerType) buildTLSApp(
globalEmail := options["email"]
globalACMECA := options["acme_ca"]
globalACMECARoot := options["acme_ca_root"]
- globalACMEDNS := options["acme_dns"]
+ _, globalACMEDNS := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
globalACMEEAB := options["acme_eab"]
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 || globalACMEEAB != nil || globalPreferredChains != nil
if hasGlobalACMEDefaults {
- for i := 0; i < len(tlsApp.Automation.Policies); i++ {
+ for i := range tlsApp.Automation.Policies {
ap := tlsApp.Automation.Policies[i]
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
// for public names, create default issuers which will later be filled in with configured global defaults
@@ -549,11 +549,12 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
globalEmail := options["email"]
globalACMECA := options["acme_ca"]
globalACMECARoot := options["acme_ca_root"]
- globalACMEDNS := options["acme_dns"]
+ globalACMEDNS, globalACMEDNSok := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
globalACMEEAB := options["acme_eab"]
globalPreferredChains := options["preferred_chains"]
globalCertLifetime := options["cert_lifetime"]
globalHTTPPort, globalHTTPSPort := options["http_port"], options["https_port"]
+ globalDefaultBind := options["default_bind"]
if globalEmail != nil && acmeIssuer.Email == "" {
acmeIssuer.Email = globalEmail.(string)
@@ -564,11 +565,21 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
}
- if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
- acmeIssuer.Challenges = &caddytls.ChallengesConfig{
- DNS: &caddytls.DNSChallengeConfig{
- ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil),
- },
+ if globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil || acmeIssuer.Challenges.DNS.ProviderRaw == nil) {
+ globalDNS := options["dns"]
+ if globalDNS == nil && globalACMEDNS == nil {
+ return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
+ }
+ if acmeIssuer.Challenges == nil {
+ acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
+ }
+ if acmeIssuer.Challenges.DNS == nil {
+ acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
+ }
+ // If global `dns` is set, do NOT set provider in issuer, just set empty dns config
+ if globalDNS == nil && acmeIssuer.Challenges.DNS.ProviderRaw == nil {
+ // Set a global DNS provider if `acme_dns` is set and `dns` is NOT set
+ acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
}
}
if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
@@ -596,6 +607,20 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
}
acmeIssuer.Challenges.TLSALPN.AlternatePort = globalHTTPSPort.(int)
}
+ // If BindHost is still unset, fall back to the first default_bind address if set
+ // This avoids binding the automation policy to the wildcard socket, which is unexpected behavior when a more selective socket is specified via default_bind
+ // In BSD it is valid to bind to the wildcard socket even though a more selective socket is already open (still unexpected behavior by the caller though)
+ // In Linux the same call will error with EADDRINUSE whenever the listener for the automation policy is opened
+ if acmeIssuer.Challenges == nil || (acmeIssuer.Challenges.DNS == nil && acmeIssuer.Challenges.BindHost == "") {
+ if defBinds, ok := globalDefaultBind.([]ConfigValue); ok && len(defBinds) > 0 {
+ if abp, ok := defBinds[0].Value.(addressesWithProtocols); ok && len(abp.addresses) > 0 {
+ if acmeIssuer.Challenges == nil {
+ acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
+ }
+ acmeIssuer.Challenges.BindHost = abp.addresses[0]
+ }
+ }
+ }
if globalCertLifetime != nil && acmeIssuer.CertificateLifetime == 0 {
acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration)
}
@@ -616,12 +641,18 @@ func newBaseAutomationPolicy(
_, hasLocalCerts := options["local_certs"]
keyType, hasKeyType := options["key_type"]
ocspStapling, hasOCSPStapling := options["ocsp_stapling"]
-
hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling
+ globalACMECA := options["acme_ca"]
+ globalACMECARoot := options["acme_ca_root"]
+ _, globalACMEDNS := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
+ globalACMEEAB := options["acme_eab"]
+ globalPreferredChains := options["preferred_chains"]
+ hasGlobalACMEDefaults := globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS || globalACMEEAB != nil || globalPreferredChains != nil
+
// if there are no global options related to automation policies
// set, then we can just return right away
- if !hasGlobalAutomationOpts {
+ if !hasGlobalAutomationOpts && !hasGlobalACMEDefaults {
if always {
return new(caddytls.AutomationPolicy), nil
}
@@ -643,6 +674,14 @@ func newBaseAutomationPolicy(
ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
}
+ if hasGlobalACMEDefaults {
+ for i := range ap.Issuers {
+ if err := fillInGlobalACMEDefaults(ap.Issuers[i], options); err != nil {
+ return nil, fmt.Errorf("filling in global issuer defaults for issuer %d: %v", i, err)
+ }
+ }
+ }
+
if hasOCSPStapling {
ocspConfig := ocspStapling.(certmagic.OCSPConfig)
ap.DisableOCSPStapling = ocspConfig.DisableStapling
diff --git a/caddyconfig/load.go b/caddyconfig/load.go
index 9f5cda905..9422d2fbb 100644
--- a/caddyconfig/load.go
+++ b/caddyconfig/load.go
@@ -121,6 +121,13 @@ func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
}
}
+ // If this request changed the config, clear the last
+ // config info we have stored, if it is different from
+ // the original source.
+ caddy.ClearLastConfigIfDifferent(
+ r.Header.Get("Caddy-Config-Source-File"),
+ r.Header.Get("Caddy-Config-Source-Adapter"))
+
caddy.Log().Named("admin.api").Info("load complete")
return nil
diff --git a/caddytest/caddytest.go b/caddytest/caddytest.go
index 623c45e5e..7b56bb281 100644
--- a/caddytest/caddytest.go
+++ b/caddytest/caddytest.go
@@ -281,7 +281,7 @@ func validateTestPrerequisites(tc *Tester) error {
tc.t.Cleanup(func() {
os.Remove(f.Name())
})
- if _, err := f.WriteString(fmt.Sprintf(initConfig, tc.config.AdminPort)); err != nil {
+ if _, err := fmt.Fprintf(f, initConfig, tc.config.AdminPort); err != nil {
return err
}
diff --git a/caddytest/integration/acme_test.go b/caddytest/integration/acme_test.go
index d7e4c296d..f10aef6a8 100644
--- a/caddytest/integration/acme_test.go
+++ b/caddytest/integration/acme_test.go
@@ -12,13 +12,14 @@ import (
"strings"
"testing"
- "github.com/caddyserver/caddy/v2"
- "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
smallstepacme "github.com/smallstep/certificates/acme"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddytest"
)
const acmeChallengePort = 9081
diff --git a/caddytest/integration/acmeserver_test.go b/caddytest/integration/acmeserver_test.go
index ca5845f87..d6a9ba005 100644
--- a/caddytest/integration/acmeserver_test.go
+++ b/caddytest/integration/acmeserver_test.go
@@ -9,11 +9,12 @@ import (
"strings"
"testing"
- "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
+
+ "github.com/caddyserver/caddy/v2/caddytest"
)
func TestACMEServerDirectory(t *testing.T) {
diff --git a/caddytest/integration/caddyfile_adapt/acme_dns_configured.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_dns_configured.caddyfiletest
new file mode 100644
index 000000000..3f43a082a
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/acme_dns_configured.caddyfiletest
@@ -0,0 +1,69 @@
+{
+ acme_dns mock foo
+}
+
+example.com {
+ respond "Hello World"
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "example.com"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Hello World",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "tls": {
+ "automation": {
+ "policies": [
+ {
+ "issuers": [
+ {
+ "challenges": {
+ "dns": {
+ "provider": {
+ "argument": "foo",
+ "name": "mock"
+ }
+ }
+ },
+ "module": "acme"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/acme_dns_naked_use_dns_defaults.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_dns_naked_use_dns_defaults.caddyfiletest
new file mode 100644
index 000000000..750ba22a4
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/acme_dns_naked_use_dns_defaults.caddyfiletest
@@ -0,0 +1,53 @@
+{
+ dns mock
+ acme_dns
+}
+
+example.com {
+
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "example.com"
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "tls": {
+ "automation": {
+ "policies": [
+ {
+ "issuers": [
+ {
+ "challenges": {
+ "dns": {}
+ },
+ "module": "acme"
+ }
+ ]
+ }
+ ]
+ },
+ "dns": {
+ "name": "mock"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/acme_dns_naked_without_dns.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_dns_naked_without_dns.caddyfiletest
new file mode 100644
index 000000000..e171c5493
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/acme_dns_naked_without_dns.caddyfiletest
@@ -0,0 +1,9 @@
+{
+ acme_dns
+}
+
+example.com {
+ respond "Hello World"
+}
+----------
+acme_dns specified without DNS provider config, but no provider specified with 'dns' global option
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/acme_server_policy-allow.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_policy-allow.caddyfiletest
new file mode 100644
index 000000000..5d1d8a3bc
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/acme_server_policy-allow.caddyfiletest
@@ -0,0 +1,72 @@
+{
+ pki {
+ ca custom-ca {
+ name "Custom CA"
+ }
+ }
+}
+
+acme.example.com {
+ acme_server {
+ ca custom-ca
+ allow {
+ domains host-1.internal.example.com host-2.internal.example.com
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "acme.example.com"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "ca": "custom-ca",
+ "handler": "acme_server",
+ "policy": {
+ "allow": {
+ "domains": [
+ "host-1.internal.example.com",
+ "host-2.internal.example.com"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "pki": {
+ "certificate_authorities": {
+ "custom-ca": {
+ "name": "Custom CA"
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/acme_server_policy-both.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_policy-both.caddyfiletest
new file mode 100644
index 000000000..15cdbba90
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/acme_server_policy-both.caddyfiletest
@@ -0,0 +1,80 @@
+{
+ pki {
+ ca custom-ca {
+ name "Custom CA"
+ }
+ }
+}
+
+acme.example.com {
+ acme_server {
+ ca custom-ca
+ allow {
+ domains host-1.internal.example.com host-2.internal.example.com
+ }
+ deny {
+ domains dc.internal.example.com
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "acme.example.com"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "ca": "custom-ca",
+ "handler": "acme_server",
+ "policy": {
+ "allow": {
+ "domains": [
+ "host-1.internal.example.com",
+ "host-2.internal.example.com"
+ ]
+ },
+ "deny": {
+ "domains": [
+ "dc.internal.example.com"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "pki": {
+ "certificate_authorities": {
+ "custom-ca": {
+ "name": "Custom CA"
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/acme_server_policy-deny.caddyfiletest b/caddytest/integration/caddyfile_adapt/acme_server_policy-deny.caddyfiletest
new file mode 100644
index 000000000..0478088c9
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/acme_server_policy-deny.caddyfiletest
@@ -0,0 +1,71 @@
+{
+ pki {
+ ca custom-ca {
+ name "Custom CA"
+ }
+ }
+}
+
+acme.example.com {
+ acme_server {
+ ca custom-ca
+ deny {
+ domains dc.internal.example.com
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "acme.example.com"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "ca": "custom-ca",
+ "handler": "acme_server",
+ "policy": {
+ "deny": {
+ "domains": [
+ "dc.internal.example.com"
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "pki": {
+ "certificate_authorities": {
+ "custom-ca": {
+ "name": "Custom CA"
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/ambiguous_site_definition.caddyfiletest b/caddytest/integration/caddyfile_adapt/ambiguous_site_definition.caddyfiletest
new file mode 100644
index 000000000..bd62d3c4e
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/ambiguous_site_definition.caddyfiletest
@@ -0,0 +1,12 @@
+example.com
+handle {
+ respond "one"
+}
+
+example.com
+handle {
+ respond "two"
+}
+----------
+Caddyfile:6: unrecognized directive: example.com
+Did you mean to define a second site? If so, you must use curly braces around each site to separate their configurations.
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/ambiguous_site_definition_duplicate_key.caddyfiletest b/caddytest/integration/caddyfile_adapt/ambiguous_site_definition_duplicate_key.caddyfiletest
new file mode 100644
index 000000000..d182b8ffd
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/ambiguous_site_definition_duplicate_key.caddyfiletest
@@ -0,0 +1,9 @@
+:8080 {
+ respond "one"
+}
+
+:8080 {
+ respond "two"
+}
+----------
+ambiguous site definition: :8080
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest b/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest
deleted file mode 100644
index 04f2c4665..000000000
--- a/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard.caddyfiletest
+++ /dev/null
@@ -1,109 +0,0 @@
-{
- auto_https prefer_wildcard
-}
-
-*.example.com {
- tls {
- dns mock
- }
- respond "fallback"
-}
-
-foo.example.com {
- respond "foo"
-}
-----------
-{
- "apps": {
- "http": {
- "servers": {
- "srv0": {
- "listen": [
- ":443"
- ],
- "routes": [
- {
- "match": [
- {
- "host": [
- "foo.example.com"
- ]
- }
- ],
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "body": "foo",
- "handler": "static_response"
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- },
- {
- "match": [
- {
- "host": [
- "*.example.com"
- ]
- }
- ],
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "body": "fallback",
- "handler": "static_response"
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- }
- ],
- "automatic_https": {
- "skip_certificates": [
- "foo.example.com"
- ],
- "prefer_wildcard": true
- }
- }
- }
- },
- "tls": {
- "automation": {
- "policies": [
- {
- "subjects": [
- "*.example.com"
- ],
- "issuers": [
- {
- "challenges": {
- "dns": {
- "provider": {
- "name": "mock"
- }
- }
- },
- "module": "acme"
- }
- ]
- }
- ]
- }
- }
- }
-}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard_multi.caddyfiletest b/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard_multi.caddyfiletest
deleted file mode 100644
index 4f8c26a5d..000000000
--- a/caddytest/integration/caddyfile_adapt/auto_https_prefer_wildcard_multi.caddyfiletest
+++ /dev/null
@@ -1,268 +0,0 @@
-{
- auto_https prefer_wildcard
-}
-
-# Covers two domains
-*.one.example.com {
- tls {
- dns mock
- }
- respond "one fallback"
-}
-
-# Is covered, should not get its own AP
-foo.one.example.com {
- respond "foo one"
-}
-
-# This one has its own tls config so it doesn't get covered (escape hatch)
-bar.one.example.com {
- respond "bar one"
- tls bar@bar.com
-}
-
-# Covers nothing but AP gets consolidated with the first
-*.two.example.com {
- tls {
- dns mock
- }
- respond "two fallback"
-}
-
-# Is HTTP so it should not cover
-http://*.three.example.com {
- respond "three fallback"
-}
-
-# Has no wildcard coverage so it gets an AP
-foo.three.example.com {
- respond "foo three"
-}
-----------
-{
- "apps": {
- "http": {
- "servers": {
- "srv0": {
- "listen": [
- ":443"
- ],
- "routes": [
- {
- "match": [
- {
- "host": [
- "foo.three.example.com"
- ]
- }
- ],
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "body": "foo three",
- "handler": "static_response"
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- },
- {
- "match": [
- {
- "host": [
- "foo.one.example.com"
- ]
- }
- ],
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "body": "foo one",
- "handler": "static_response"
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- },
- {
- "match": [
- {
- "host": [
- "bar.one.example.com"
- ]
- }
- ],
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "body": "bar one",
- "handler": "static_response"
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- },
- {
- "match": [
- {
- "host": [
- "*.one.example.com"
- ]
- }
- ],
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "body": "one fallback",
- "handler": "static_response"
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- },
- {
- "match": [
- {
- "host": [
- "*.two.example.com"
- ]
- }
- ],
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "body": "two fallback",
- "handler": "static_response"
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- }
- ],
- "automatic_https": {
- "skip_certificates": [
- "foo.one.example.com",
- "bar.one.example.com"
- ],
- "prefer_wildcard": true
- }
- },
- "srv1": {
- "listen": [
- ":80"
- ],
- "routes": [
- {
- "match": [
- {
- "host": [
- "*.three.example.com"
- ]
- }
- ],
- "handle": [
- {
- "handler": "subroute",
- "routes": [
- {
- "handle": [
- {
- "body": "three fallback",
- "handler": "static_response"
- }
- ]
- }
- ]
- }
- ],
- "terminal": true
- }
- ],
- "automatic_https": {
- "prefer_wildcard": true
- }
- }
- }
- },
- "tls": {
- "automation": {
- "policies": [
- {
- "subjects": [
- "foo.three.example.com"
- ]
- },
- {
- "subjects": [
- "bar.one.example.com"
- ],
- "issuers": [
- {
- "email": "bar@bar.com",
- "module": "acme"
- },
- {
- "ca": "https://acme.zerossl.com/v2/DV90",
- "email": "bar@bar.com",
- "module": "acme"
- }
- ]
- },
- {
- "subjects": [
- "*.one.example.com",
- "*.two.example.com"
- ],
- "issuers": [
- {
- "challenges": {
- "dns": {
- "provider": {
- "name": "mock"
- }
- }
- },
- "module": "acme"
- }
- ]
- }
- ]
- }
- }
- }
-}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/directive_as_site_address.caddyfiletest b/caddytest/integration/caddyfile_adapt/directive_as_site_address.caddyfiletest
new file mode 100644
index 000000000..7245fd984
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/directive_as_site_address.caddyfiletest
@@ -0,0 +1,5 @@
+handle
+
+respond "should not work"
+----------
+Caddyfile:1: parsed 'handle' as a site address, but it is a known directive; directives must appear in a site block
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/duplicate_listener_address_global.caddyfiletest b/caddytest/integration/caddyfile_adapt/duplicate_listener_address_global.caddyfiletest
new file mode 100644
index 000000000..5287557b3
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/duplicate_listener_address_global.caddyfiletest
@@ -0,0 +1,12 @@
+{
+ servers {
+ srv0 {
+ listen :8080
+ }
+ srv1 {
+ listen :8080
+ }
+ }
+}
+----------
+parsing caddyfile tokens for 'servers': unrecognized servers option 'srv0', at Caddyfile:3
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest
index bd42aee55..6f3059ab2 100644
--- a/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest
@@ -106,20 +106,29 @@ example.com {
"handler": "subroute",
"routes": [
{
- "group": "group0",
"handle": [
{
- "handler": "rewrite",
- "uri": "/{http.error.status_code}.html"
- }
- ]
- },
- {
- "handle": [
- {
- "handler": "file_server",
- "hide": [
- "./Caddyfile"
+ "handler": "subroute",
+ "routes": [
+ {
+ "group": "group0",
+ "handle": [
+ {
+ "handler": "rewrite",
+ "uri": "/{http.error.status_code}.html"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "file_server",
+ "hide": [
+ "./Caddyfile"
+ ]
+ }
+ ]
+ }
]
}
]
diff --git a/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest
index 0e84a13c2..1bec4b3e8 100644
--- a/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest
@@ -165,8 +165,17 @@ bar.localhost {
{
"handle": [
{
- "body": "404 or 410 error",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -178,8 +187,17 @@ bar.localhost {
{
"handle": [
{
- "body": "Error In range [500 .. 599]",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error In range [500 .. 599]",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -208,8 +226,17 @@ bar.localhost {
{
"handle": [
{
- "body": "404 or 410 error from second site",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error from second site",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -221,8 +248,17 @@ bar.localhost {
{
"handle": [
{
- "body": "Error In range [500 .. 599] from second site",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error In range [500 .. 599] from second site",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
diff --git a/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest
index 46b70c8e3..299abc5f1 100644
--- a/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest
@@ -96,8 +96,17 @@ localhost:3010 {
{
"handle": [
{
- "body": "Error in the [400 .. 499] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
diff --git a/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest
index 70158830c..9d4d2645c 100644
--- a/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest
@@ -116,8 +116,17 @@ localhost:2099 {
{
"handle": [
{
- "body": "Error in the [400 .. 499] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -129,8 +138,17 @@ localhost:2099 {
{
"handle": [
{
- "body": "Error code is equal to 500 or in the [300..399] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error code is equal to 500 or in the [300..399] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
diff --git a/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest
index 5ac5863e3..29a51ffad 100644
--- a/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest
@@ -96,8 +96,17 @@ localhost:3010 {
{
"handle": [
{
- "body": "404 or 410 error",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
diff --git a/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest
index 63701cccb..1faf9b3bd 100644
--- a/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest
@@ -116,8 +116,17 @@ localhost:2099 {
{
"handle": [
{
- "body": "Error in the [400 .. 499] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -129,8 +138,17 @@ localhost:2099 {
{
"handle": [
{
- "body": "Fallback route: code outside the [400..499] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Fallback route: code outside the [400..499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
]
}
diff --git a/caddytest/integration/caddyfile_adapt/error_subhandlers.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_subhandlers.caddyfiletest
new file mode 100644
index 000000000..54429a73e
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/error_subhandlers.caddyfiletest
@@ -0,0 +1,260 @@
+{
+ http_port 2099
+}
+localhost:2099 {
+ root * /var/www/
+ file_server
+
+ handle_errors 404 {
+ handle /en/* {
+ respond "not found" 404
+ }
+ handle /es/* {
+ respond "no encontrado"
+ }
+ handle {
+ respond "default not found"
+ }
+ }
+ handle_errors {
+ handle /en/* {
+ respond "English error"
+ }
+ handle /es/* {
+ respond "Spanish error"
+ }
+ handle {
+ respond "Default error"
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "http_port": 2099,
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":2099"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "vars",
+ "root": "/var/www/"
+ },
+ {
+ "handler": "file_server",
+ "hide": [
+ "./Caddyfile"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "errors": {
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "group": "group3",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "not found",
+ "handler": "static_response",
+ "status_code": 404
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/en/*"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "group3",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "no encontrado",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/es/*"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "group3",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "default not found",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} in [404]"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "group": "group8",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "English error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/en/*"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "group8",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Spanish error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/es/*"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "group8",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Default error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.caddyfiletest
index 1f5d0093e..e910a3d7e 100644
--- a/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/global_options_preferred_chains.caddyfiletest
@@ -31,9 +31,6 @@ example.com
"automation": {
"policies": [
{
- "subjects": [
- "example.com"
- ],
"issuers": [
{
"module": "acme",
diff --git a/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest b/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest
index 2f3306fd9..6b2ffaec4 100644
--- a/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/global_server_options_single.caddyfiletest
@@ -18,6 +18,9 @@
trusted_proxies static private_ranges
client_ip_headers Custom-Real-Client-IP X-Forwarded-For
client_ip_headers A-Third-One
+ keepalive_interval 20s
+ keepalive_idle 20s
+ keepalive_count 10
}
}
@@ -45,6 +48,9 @@ foo.com {
"read_header_timeout": 30000000000,
"write_timeout": 30000000000,
"idle_timeout": 30000000000,
+ "keepalive_interval": 20000000000,
+ "keepalive_idle": 20000000000,
+ "keepalive_count": 10,
"max_header_bytes": 100000000,
"enable_full_duplex": true,
"routes": [
@@ -89,4 +95,4 @@ foo.com {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/caddytest/integration/caddyfile_adapt/header_placeholder_search.caddyfiletest b/caddytest/integration/caddyfile_adapt/header_placeholder_search.caddyfiletest
new file mode 100644
index 000000000..9a9e46b62
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/header_placeholder_search.caddyfiletest
@@ -0,0 +1,64 @@
+:80 {
+ header Test-Static ":443" "STATIC-WORKS"
+ header Test-Dynamic ":{http.request.local.port}" "DYNAMIC-WORKS"
+ header Test-Complex "port-{http.request.local.port}-end" "COMPLEX-{http.request.method}"
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":80"
+ ],
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "headers",
+ "response": {
+ "replace": {
+ "Test-Static": [
+ {
+ "replace": "STATIC-WORKS",
+ "search_regexp": ":443"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "handler": "headers",
+ "response": {
+ "replace": {
+ "Test-Dynamic": [
+ {
+ "replace": "DYNAMIC-WORKS",
+ "search_regexp": ":{http.request.local.port}"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "handler": "headers",
+ "response": {
+ "replace": {
+ "Test-Complex": [
+ {
+ "replace": "COMPLEX-{http.request.method}",
+ "search_regexp": "port-{http.request.local.port}-end"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/heredoc_extra_indentation.caddyfiletest b/caddytest/integration/caddyfile_adapt/heredoc_extra_indentation.caddyfiletest
new file mode 100644
index 000000000..1b71b91fc
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/heredoc_extra_indentation.caddyfiletest
@@ -0,0 +1,41 @@
+:80
+
+handle {
+ respond <headers>Authorization regexp "Bearer\s+([A-Za-z0-9_-]+)" "Bearer [REDACTED]"
+ request>headers>Authorization regexp "Basic\s+([A-Za-z0-9+/=]+)" "Basic [REDACTED]"
+ request>headers>Authorization regexp "token=([^&\s]+)" "token=[REDACTED]"
+
+ # Single regexp filter - this should continue to work as before
+ request>headers>Cookie regexp "sessionid=[^;]+" "sessionid=[REDACTED]"
+
+ # Mixed filters (non-regexp) - these should work normally
+ request>headers>Server delete
+ request>remote_ip ip_mask {
+ ipv4 24
+ ipv6 32
+ }
+ }
+}
+----------
+{
+ "logging": {
+ "logs": {
+ "default": {
+ "exclude": [
+ "http.log.access.log0"
+ ]
+ },
+ "log0": {
+ "writer": {
+ "output": "stdout"
+ },
+ "encoder": {
+ "fields": {
+ "request\u003eheaders\u003eAuthorization": {
+ "filter": "multi_regexp",
+ "operations": [
+ {
+ "regexp": "Bearer\\s+([A-Za-z0-9_-]+)",
+ "value": "Bearer [REDACTED]"
+ },
+ {
+ "regexp": "Basic\\s+([A-Za-z0-9+/=]+)",
+ "value": "Basic [REDACTED]"
+ },
+ {
+ "regexp": "token=([^\u0026\\s]+)",
+ "value": "token=[REDACTED]"
+ }
+ ]
+ },
+ "request\u003eheaders\u003eCookie": {
+ "filter": "regexp",
+ "regexp": "sessionid=[^;]+",
+ "value": "sessionid=[REDACTED]"
+ },
+ "request\u003eheaders\u003eServer": {
+ "filter": "delete"
+ },
+ "request\u003eremote_ip": {
+ "filter": "ip_mask",
+ "ipv4_cidr": 24,
+ "ipv6_cidr": 32
+ }
+ },
+ "format": "filter",
+ "wrap": {
+ "format": "console"
+ }
+ },
+ "include": [
+ "http.log.access.log0"
+ ]
+ }
+ }
+ },
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":80"
+ ],
+ "logs": {
+ "default_logger_name": "log0"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/matcher_outside_site_block.caddyfiletest b/caddytest/integration/caddyfile_adapt/matcher_outside_site_block.caddyfiletest
new file mode 100644
index 000000000..04590c6c2
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/matcher_outside_site_block.caddyfiletest
@@ -0,0 +1,9 @@
+@foo {
+ path /foo
+}
+
+handle {
+ respond "should not work"
+}
+----------
+request matchers may not be defined globally, they must be in a site block; found @foo, at Caddyfile:1
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_forward_proxy_url.txt b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_forward_proxy_url.txt
new file mode 100644
index 000000000..9fc445283
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_forward_proxy_url.txt
@@ -0,0 +1,41 @@
+: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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_none_proxy.txt b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_none_proxy.txt
new file mode 100644
index 000000000..3805448d9
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_none_proxy.txt
@@ -0,0 +1,40 @@
+: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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_url_proxy.txt b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_url_proxy.txt
new file mode 100644
index 000000000..9397458e9
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_http_transport_url_proxy.txt
@@ -0,0 +1,41 @@
+: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"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_trusted_proxies_unix.caddyfiletest b/caddytest/integration/caddyfile_adapt/reverse_proxy_trusted_proxies_unix.caddyfiletest
new file mode 100644
index 000000000..8f7175124
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_trusted_proxies_unix.caddyfiletest
@@ -0,0 +1,59 @@
+{
+ servers {
+ trusted_proxies_unix
+ }
+}
+
+example.com {
+ reverse_proxy https://local:8080
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "example.com"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "reverse_proxy",
+ "transport": {
+ "protocol": "http",
+ "tls": {}
+ },
+ "upstreams": [
+ {
+ "dial": "local:8080"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "trusted_proxies_unix": true
+ }
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/site_address_invalid_port.caddyfiletest b/caddytest/integration/caddyfile_adapt/site_address_invalid_port.caddyfiletest
new file mode 100644
index 000000000..3b8e2f596
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/site_address_invalid_port.caddyfiletest
@@ -0,0 +1,7 @@
+:70000
+
+handle {
+ respond "should not work"
+}
+----------
+port 70000 is out of range
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/site_address_negative_port.caddyfiletest b/caddytest/integration/caddyfile_adapt/site_address_negative_port.caddyfiletest
new file mode 100644
index 000000000..b67849868
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/site_address_negative_port.caddyfiletest
@@ -0,0 +1,7 @@
+:-1
+
+handle {
+ respond "should not work"
+}
+----------
+port -1 is out of range
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/site_address_unsupported_scheme.caddyfiletest b/caddytest/integration/caddyfile_adapt/site_address_unsupported_scheme.caddyfiletest
new file mode 100644
index 000000000..8616504e5
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/site_address_unsupported_scheme.caddyfiletest
@@ -0,0 +1,7 @@
+foo://example.com
+
+handle {
+ respond "hello"
+}
+----------
+unsupported URL scheme foo://
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/site_address_wss_invalid_port.caddyfiletest b/caddytest/integration/caddyfile_adapt/site_address_wss_invalid_port.caddyfiletest
new file mode 100644
index 000000000..4a9060988
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/site_address_wss_invalid_port.caddyfiletest
@@ -0,0 +1,7 @@
+wss://example.com:70000
+
+handle {
+ respond "should not work"
+}
+----------
+port 70000 is out of range
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/site_address_wss_scheme.caddyfiletest b/caddytest/integration/caddyfile_adapt/site_address_wss_scheme.caddyfiletest
new file mode 100644
index 000000000..d1051c071
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/site_address_wss_scheme.caddyfiletest
@@ -0,0 +1,7 @@
+wss://example.com
+
+handle {
+ respond "hello"
+}
+----------
+the scheme wss:// is only supported in browsers; use https:// instead
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest
index 4eb6c4f1c..623bafd70 100644
--- a/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/tls_automation_wildcard_force_automate.caddyfiletest
@@ -131,13 +131,7 @@ shadowed.example.com {
{
"match": {
"sni": [
- "automated1.example.com"
- ]
- }
- },
- {
- "match": {
- "sni": [
+ "automated1.example.com",
"automated2.example.com"
]
}
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_block.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_block.caddyfiletest
new file mode 100644
index 000000000..bc71456b8
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_block.caddyfiletest
@@ -0,0 +1,87 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf {
+ file ../caddy.ca.cer
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "files": [
+ "../caddy.ca.cer"
+ ],
+ "loader": "file"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_inline.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_inline.caddyfiletest
new file mode 100644
index 000000000..baca2433a
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_inline.caddyfiletest
@@ -0,0 +1,85 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf file ../caddy.ca.cer
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "files": [
+ "../caddy.ca.cer"
+ ],
+ "loader": "file"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_multi-in-block.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_multi-in-block.caddyfiletest
new file mode 100644
index 000000000..1fe22a317
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_multi-in-block.caddyfiletest
@@ -0,0 +1,94 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf {
+ file ../caddy.ca.cer
+ file ../caddy.ca.cer
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "files": [
+ "../caddy.ca.cer"
+ ],
+ "loader": "file"
+ },
+ {
+ "files": [
+ "../caddy.ca.cer"
+ ],
+ "loader": "file"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_block.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_block.caddyfiletest
new file mode 100644
index 000000000..ee9be71aa
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_block.caddyfiletest
@@ -0,0 +1,87 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf {
+ folder ../
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "folders": [
+ "../"
+ ],
+ "loader": "folder"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_inline.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_inline.caddyfiletest
new file mode 100644
index 000000000..b6c4b8727
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_inline.caddyfiletest
@@ -0,0 +1,85 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf folder ../
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "folders": [
+ "../"
+ ],
+ "loader": "folder"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_multi-in-block.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_multi-in-block.caddyfiletest
new file mode 100644
index 000000000..dd5663d8d
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_multi-in-block.caddyfiletest
@@ -0,0 +1,94 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf {
+ folder ../
+ folder ../
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "folders": [
+ "../"
+ ],
+ "loader": "folder"
+ },
+ {
+ "folders": [
+ "../"
+ ],
+ "loader": "folder"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_multiple_options_without_provider.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_multiple_options_without_provider.caddyfiletest
new file mode 100644
index 000000000..235089c39
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_dns_multiple_options_without_provider.caddyfiletest
@@ -0,0 +1,9 @@
+localhost
+
+tls {
+ propagation_delay 10s
+ dns_ttl 5m
+}
+
+----------
+parsing caddyfile tokens for 'tls': setting DNS challenge options [propagation_delay, dns_ttl] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option), at Caddyfile:6
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_override_acme_dns.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_override_acme_dns.caddyfiletest
new file mode 100644
index 000000000..2f7c6096a
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_dns_override_acme_dns.caddyfiletest
@@ -0,0 +1,79 @@
+{
+ acme_dns mock foo
+}
+
+localhost {
+ tls {
+ dns mock bar
+ resolvers 8.8.8.8 8.8.4.4
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "tls": {
+ "automation": {
+ "policies": [
+ {
+ "subjects": [
+ "localhost"
+ ],
+ "issuers": [
+ {
+ "challenges": {
+ "dns": {
+ "provider": {
+ "argument": "bar",
+ "name": "mock"
+ },
+ "resolvers": [
+ "8.8.8.8",
+ "8.8.4.4"
+ ]
+ }
+ },
+ "module": "acme"
+ }
+ ]
+ },
+ {
+ "issuers": [
+ {
+ "challenges": {
+ "dns": {
+ "provider": {
+ "argument": "foo",
+ "name": "mock"
+ }
+ }
+ },
+ "module": "acme"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_override_global_dns.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_override_global_dns.caddyfiletest
new file mode 100644
index 000000000..fba67b566
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_dns_override_global_dns.caddyfiletest
@@ -0,0 +1,68 @@
+{
+ dns mock foo
+}
+
+localhost {
+ tls {
+ dns mock bar
+ resolvers 8.8.8.8 8.8.4.4
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "tls": {
+ "automation": {
+ "policies": [
+ {
+ "subjects": [
+ "localhost"
+ ],
+ "issuers": [
+ {
+ "challenges": {
+ "dns": {
+ "provider": {
+ "argument": "bar",
+ "name": "mock"
+ },
+ "resolvers": [
+ "8.8.8.8",
+ "8.8.4.4"
+ ]
+ }
+ },
+ "module": "acme"
+ }
+ ]
+ }
+ ]
+ },
+ "dns": {
+ "argument": "foo",
+ "name": "mock"
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_propagation_timeout_without_provider.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_propagation_timeout_without_provider.caddyfiletest
new file mode 100644
index 000000000..48602135a
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_dns_propagation_timeout_without_provider.caddyfiletest
@@ -0,0 +1,7 @@
+:443 {
+ tls {
+ propagation_timeout 30s
+ }
+}
+----------
+parsing caddyfile tokens for 'tls': setting DNS challenge options [propagation_timeout] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option), at Caddyfile:4
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_propagation_without_provider.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_propagation_without_provider.caddyfiletest
new file mode 100644
index 000000000..6ab6e3236
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_dns_propagation_without_provider.caddyfiletest
@@ -0,0 +1,7 @@
+:443 {
+ tls {
+ propagation_delay 30s
+ }
+}
+----------
+parsing caddyfile tokens for 'tls': setting DNS challenge options [propagation_delay] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option), at Caddyfile:4
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_resolvers_with_global_provider.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_resolvers_with_global_provider.caddyfiletest
new file mode 100644
index 000000000..0292e8d07
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_dns_resolvers_with_global_provider.caddyfiletest
@@ -0,0 +1,76 @@
+{
+ acme_dns mock
+}
+
+localhost {
+ tls {
+ resolvers 8.8.8.8 8.8.4.4
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ },
+ "tls": {
+ "automation": {
+ "policies": [
+ {
+ "subjects": [
+ "localhost"
+ ],
+ "issuers": [
+ {
+ "challenges": {
+ "dns": {
+ "provider": {
+ "name": "mock"
+ },
+ "resolvers": [
+ "8.8.8.8",
+ "8.8.4.4"
+ ]
+ }
+ },
+ "module": "acme"
+ }
+ ]
+ },
+ {
+ "issuers": [
+ {
+ "challenges": {
+ "dns": {
+ "provider": {
+ "name": "mock"
+ }
+ }
+ },
+ "module": "acme"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/caddytest/integration/caddyfile_adapt/tls_dns_ttl.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_dns_ttl.caddyfiletest
index c452bf79f..6d7c007bd 100644
--- a/caddytest/integration/caddyfile_adapt/tls_dns_ttl.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/tls_dns_ttl.caddyfiletest
@@ -2,6 +2,7 @@ localhost
respond "hello from localhost"
tls {
+ dns mock
dns_ttl 5m10s
}
----------
@@ -54,6 +55,9 @@ tls {
{
"challenges": {
"dns": {
+ "provider": {
+ "name": "mock"
+ },
"ttl": 310000000000
}
},
diff --git a/caddytest/integration/caddyfile_adapt/tls_propagation_options.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_propagation_options.caddyfiletest
index 43ec9774b..f45959b5c 100644
--- a/caddytest/integration/caddyfile_adapt/tls_propagation_options.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/tls_propagation_options.caddyfiletest
@@ -2,6 +2,7 @@ localhost
respond "hello from localhost"
tls {
+ dns mock
propagation_delay 5m10s
propagation_timeout 10m20s
}
@@ -56,7 +57,10 @@ tls {
"challenges": {
"dns": {
"propagation_delay": 310000000000,
- "propagation_timeout": 620000000000
+ "propagation_timeout": 620000000000,
+ "provider": {
+ "name": "mock"
+ }
}
},
"module": "acme"
diff --git a/caddytest/integration/caddyfile_adapt_test.go b/caddytest/integration/caddyfile_adapt_test.go
index 0d9f0fa47..9bc6af4b6 100644
--- a/caddytest/integration/caddyfile_adapt_test.go
+++ b/caddytest/integration/caddyfile_adapt_test.go
@@ -9,8 +9,8 @@ import (
"strings"
"testing"
+ "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddytest"
-
_ "github.com/caddyserver/caddy/v2/internal/testmocks"
)
@@ -28,30 +28,48 @@ func TestCaddyfileAdaptToJSON(t *testing.T) {
if f.IsDir() {
continue
}
-
- // read the test file
filename := f.Name()
- data, err := os.ReadFile("./caddyfile_adapt/" + filename)
- if err != nil {
- t.Errorf("failed to read %s dir: %s", filename, err)
- }
- // split the Caddyfile (first) and JSON (second) parts
- // (append newline to Caddyfile to match formatter expectations)
- parts := strings.Split(string(data), "----------")
- caddyfile, json := strings.TrimSpace(parts[0])+"\n", strings.TrimSpace(parts[1])
+ // run each file as a subtest, so that we can see which one fails more easily
+ t.Run(filename, func(t *testing.T) {
+ // read the test file
+ data, err := os.ReadFile("./caddyfile_adapt/" + filename)
+ if err != nil {
+ t.Errorf("failed to read %s dir: %s", filename, err)
+ }
- // replace windows newlines in the json with unix newlines
- json = winNewlines.ReplaceAllString(json, "\n")
+ // split the Caddyfile (first) and JSON (second) parts
+ // (append newline to Caddyfile to match formatter expectations)
+ parts := strings.Split(string(data), "----------")
+ caddyfile, expected := strings.TrimSpace(parts[0])+"\n", strings.TrimSpace(parts[1])
- // replace os-specific default path for file_server's hide field
- replacePath, _ := jsonMod.Marshal(fmt.Sprint(".", string(filepath.Separator), "Caddyfile"))
- json = strings.ReplaceAll(json, `"./Caddyfile"`, string(replacePath))
+ // replace windows newlines in the json with unix newlines
+ expected = winNewlines.ReplaceAllString(expected, "\n")
- // run the test
- ok := caddytest.CompareAdapt(t, filename, caddyfile, "caddyfile", json)
- if !ok {
- t.Errorf("failed to adapt %s", filename)
- }
+ // replace os-specific default path for file_server's hide field
+ replacePath, _ := jsonMod.Marshal(fmt.Sprint(".", string(filepath.Separator), "Caddyfile"))
+ expected = strings.ReplaceAll(expected, `"./Caddyfile"`, string(replacePath))
+
+ // if the expected output is JSON, compare it
+ if len(expected) > 0 && expected[0] == '{' {
+ ok := caddytest.CompareAdapt(t, filename, caddyfile, "caddyfile", expected)
+ if !ok {
+ t.Errorf("failed to adapt %s", filename)
+ }
+ return
+ }
+
+ // otherwise, adapt the Caddyfile and check for errors
+ cfgAdapter := caddyconfig.GetAdapter("caddyfile")
+ _, _, err = cfgAdapter.Adapt([]byte(caddyfile), nil)
+ if err == nil {
+ t.Errorf("expected error for %s but got none", filename)
+ } else {
+ normalizedErr := winNewlines.ReplaceAllString(err.Error(), "\n")
+ if !strings.Contains(normalizedErr, expected) {
+ t.Errorf("expected error for %s to contain:\n%s\nbut got:\n%s", filename, expected, normalizedErr)
+ }
+ }
+ })
}
}
diff --git a/caddytest/integration/caddyfile_test.go b/caddytest/integration/caddyfile_test.go
index 11ffc08ae..d45d5a5e9 100644
--- a/caddytest/integration/caddyfile_test.go
+++ b/caddytest/integration/caddyfile_test.go
@@ -615,7 +615,6 @@ func TestReplaceWithReplacementPlaceholder(t *testing.T) {
respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz")
-
}
func TestReplaceWithKeyPlaceholder(t *testing.T) {
@@ -783,6 +782,46 @@ func TestHandleErrorRangeAndCodes(t *testing.T) {
tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range")
}
+func TestHandleErrorSubHandlers(t *testing.T) {
+ tester := caddytest.NewTester(t)
+ tester.InitServer(`{
+ admin localhost:2999
+ http_port 9080
+ }
+ localhost:9080 {
+ root * /srv
+ file_server
+ error /*/internalerr* "Internal Server Error" 500
+
+ handle_errors 404 {
+ handle /en/* {
+ respond "not found" 404
+ }
+ handle /es/* {
+ respond "no encontrado" 404
+ }
+ handle {
+ respond "default not found"
+ }
+ }
+ handle_errors {
+ handle {
+ respond "Default error"
+ }
+ handle /en/* {
+ respond "English error"
+ }
+ }
+ }
+ `, "caddyfile")
+ // act and assert
+ tester.AssertGetResponse("http://localhost:9080/en/notfound", 404, "not found")
+ tester.AssertGetResponse("http://localhost:9080/es/notfound", 404, "no encontrado")
+ tester.AssertGetResponse("http://localhost:9080/notfound", 404, "default not found")
+ tester.AssertGetResponse("http://localhost:9080/es/internalerr", 500, "Default error")
+ tester.AssertGetResponse("http://localhost:9080/en/internalerr", 500, "English error")
+}
+
func TestInvalidSiteAddressesAsDirectives(t *testing.T) {
type testCase struct {
config, expectedError string
diff --git a/caddytest/integration/h2listener_test.go b/caddytest/integration/h2listener_test.go
new file mode 100644
index 000000000..451c925ba
--- /dev/null
+++ b/caddytest/integration/h2listener_test.go
@@ -0,0 +1,129 @@
+package integration
+
+import (
+ "fmt"
+ "net/http"
+ "slices"
+ "strings"
+ "testing"
+
+ "github.com/caddyserver/caddy/v2/caddytest"
+)
+
+func newH2ListenerWithVersionsWithTLSTester(t *testing.T, serverVersions []string, clientVersions []string) *caddytest.Tester {
+ const baseConfig = `
+ {
+ skip_install_trust
+ admin localhost:2999
+ http_port 9080
+ https_port 9443
+ servers :9443 {
+ protocols %s
+ }
+ }
+ localhost {
+ respond "{http.request.tls.proto} {http.request.proto}"
+ }
+ `
+ tester := caddytest.NewTester(t)
+ tester.InitServer(fmt.Sprintf(baseConfig, strings.Join(serverVersions, " ")), "caddyfile")
+
+ tr := tester.Client.Transport.(*http.Transport)
+ tr.TLSClientConfig.NextProtos = clientVersions
+ tr.Protocols = new(http.Protocols)
+ if slices.Contains(clientVersions, "h2") {
+ tr.ForceAttemptHTTP2 = true
+ tr.Protocols.SetHTTP2(true)
+ }
+ if !slices.Contains(clientVersions, "http/1.1") {
+ tr.Protocols.SetHTTP1(false)
+ }
+
+ return tester
+}
+
+func TestH2ListenerWithTLS(t *testing.T) {
+ tests := []struct {
+ serverVersions []string
+ clientVersions []string
+ expectedBody string
+ failed bool
+ }{
+ {[]string{"h2"}, []string{"h2"}, "h2 HTTP/2.0", false},
+ {[]string{"h2"}, []string{"http/1.1"}, "", true},
+ {[]string{"h1"}, []string{"http/1.1"}, "http/1.1 HTTP/1.1", false},
+ {[]string{"h1"}, []string{"h2"}, "", true},
+ {[]string{"h2", "h1"}, []string{"h2"}, "h2 HTTP/2.0", false},
+ {[]string{"h2", "h1"}, []string{"http/1.1"}, "http/1.1 HTTP/1.1", false},
+ }
+ for _, tc := range tests {
+ tester := newH2ListenerWithVersionsWithTLSTester(t, tc.serverVersions, tc.clientVersions)
+ t.Logf("running with server versions %v and client versions %v:", tc.serverVersions, tc.clientVersions)
+ if tc.failed {
+ resp, err := tester.Client.Get("https://localhost:9443")
+ if err == nil {
+ t.Errorf("unexpected response: %d", resp.StatusCode)
+ }
+ } else {
+ tester.AssertGetResponse("https://localhost:9443", 200, tc.expectedBody)
+ }
+ }
+}
+
+func newH2ListenerWithVersionsWithoutTLSTester(t *testing.T, serverVersions []string, clientVersions []string) *caddytest.Tester {
+ const baseConfig = `
+ {
+ skip_install_trust
+ admin localhost:2999
+ http_port 9080
+ servers :9080 {
+ protocols %s
+ }
+ }
+ http://localhost {
+ respond "{http.request.proto}"
+ }
+ `
+ tester := caddytest.NewTester(t)
+ tester.InitServer(fmt.Sprintf(baseConfig, strings.Join(serverVersions, " ")), "caddyfile")
+
+ tr := tester.Client.Transport.(*http.Transport)
+ tr.Protocols = new(http.Protocols)
+ if slices.Contains(clientVersions, "h2c") {
+ tr.Protocols.SetHTTP1(false)
+ tr.Protocols.SetUnencryptedHTTP2(true)
+ } else if slices.Contains(clientVersions, "http/1.1") {
+ tr.Protocols.SetHTTP1(true)
+ tr.Protocols.SetUnencryptedHTTP2(false)
+ }
+
+ return tester
+}
+
+func TestH2ListenerWithoutTLS(t *testing.T) {
+ tests := []struct {
+ serverVersions []string
+ clientVersions []string
+ expectedBody string
+ failed bool
+ }{
+ {[]string{"h2c"}, []string{"h2c"}, "HTTP/2.0", false},
+ {[]string{"h2c"}, []string{"http/1.1"}, "", true},
+ {[]string{"h1"}, []string{"http/1.1"}, "HTTP/1.1", false},
+ {[]string{"h1"}, []string{"h2c"}, "", true},
+ {[]string{"h2c", "h1"}, []string{"h2c"}, "HTTP/2.0", false},
+ {[]string{"h2c", "h1"}, []string{"http/1.1"}, "HTTP/1.1", false},
+ }
+ for _, tc := range tests {
+ tester := newH2ListenerWithVersionsWithoutTLSTester(t, tc.serverVersions, tc.clientVersions)
+ t.Logf("running with server versions %v and client versions %v:", tc.serverVersions, tc.clientVersions)
+ if tc.failed {
+ resp, err := tester.Client.Get("http://localhost:9080")
+ if err == nil {
+ t.Errorf("unexpected response: %d", resp.StatusCode)
+ }
+ } else {
+ tester.AssertGetResponse("http://localhost:9080", 200, tc.expectedBody)
+ }
+ }
+}
diff --git a/caddytest/integration/mockdns_test.go b/caddytest/integration/mockdns_test.go
index 615116a3a..e55a6df58 100644
--- a/caddytest/integration/mockdns_test.go
+++ b/caddytest/integration/mockdns_test.go
@@ -3,10 +3,11 @@ package integration
import (
"context"
- "github.com/caddyserver/caddy/v2"
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/certmagic"
"github.com/libdns/libdns"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
func init() {
@@ -14,7 +15,9 @@ func init() {
}
// MockDNSProvider is a mock DNS provider, for testing config with DNS modules.
-type MockDNSProvider struct{}
+type MockDNSProvider struct {
+ Argument string `json:"argument,omitempty"` // optional argument useful for testing
+}
// CaddyModule returns the Caddy module information.
func (MockDNSProvider) CaddyModule() caddy.ModuleInfo {
@@ -30,7 +33,15 @@ func (MockDNSProvider) Provision(ctx caddy.Context) error {
}
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
-func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+func (p *MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ d.Next() // consume directive name
+
+ if d.NextArg() {
+ p.Argument = d.Val()
+ }
+ if d.NextArg() {
+ return d.Errf("unexpected argument '%s'", d.Val())
+ }
return nil
}
@@ -55,7 +66,9 @@ func (MockDNSProvider) SetRecords(ctx context.Context, zone string, recs []libdn
}
// Interface guard
-var _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil)
-var _ certmagic.DNSProvider = (*MockDNSProvider)(nil)
-var _ caddy.Provisioner = (*MockDNSProvider)(nil)
-var _ caddy.Module = (*MockDNSProvider)(nil)
+var (
+ _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil)
+ _ certmagic.DNSProvider = (*MockDNSProvider)(nil)
+ _ caddy.Provisioner = (*MockDNSProvider)(nil)
+ _ caddy.Module = (*MockDNSProvider)(nil)
+)
diff --git a/caddytest/integration/stream_test.go b/caddytest/integration/stream_test.go
index d2f2fd79b..57231a527 100644
--- a/caddytest/integration/stream_test.go
+++ b/caddytest/integration/stream_test.go
@@ -13,9 +13,10 @@ import (
"testing"
"time"
- "github.com/caddyserver/caddy/v2/caddytest"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
+
+ "github.com/caddyserver/caddy/v2/caddytest"
)
// (see https://github.com/caddyserver/caddy/issues/3556 for use case)
diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go
index 5127c0f90..75d114992 100644
--- a/cmd/commandfuncs.go
+++ b/cmd/commandfuncs.go
@@ -24,6 +24,7 @@ import (
"io"
"io/fs"
"log"
+ "maps"
"net"
"net/http"
"os"
@@ -171,9 +172,19 @@ func cmdStart(fl Flags) (int, error) {
func cmdRun(fl Flags) (int, error) {
caddy.TrapSignals()
- logger := caddy.Log()
+ // set up buffered logging for early startup
+ // so that we can hold onto logs until after
+ // the config is loaded (or fails to load)
+ // so that we can write the logs to the user's
+ // configured output. we must be sure to flush
+ // on any error before the config is loaded.
+ logger, defaultLogger, logBuffer := caddy.BufferedLog()
+
undoMaxProcs := setResourceLimits(logger)
defer undoMaxProcs()
+ // release the local reference to the undo function so it can be GC'd;
+ // the deferred call above has already captured the actual function value.
+ undoMaxProcs = nil //nolint:ineffassign,wastedassign
configFlag := fl.String("config")
configAdapterFlag := fl.String("adapter")
@@ -186,6 +197,7 @@ func cmdRun(fl Flags) (int, error) {
// load all additional envs as soon as possible
err := handleEnvFileFlag(fl)
if err != nil {
+ logBuffer.FlushTo(defaultLogger)
return caddy.ExitCodeFailedStartup, err
}
@@ -203,6 +215,7 @@ func cmdRun(fl Flags) (int, error) {
logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
resumeFlag = false
} else if err != nil {
+ logBuffer.FlushTo(defaultLogger)
return caddy.ExitCodeFailedStartup, err
} else {
if configFlag == "" {
@@ -218,9 +231,11 @@ func cmdRun(fl Flags) (int, error) {
}
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
var configFile string
+ var adapterUsed string
if !resumeFlag {
- config, configFile, err = LoadConfig(configFlag, configAdapterFlag)
+ config, configFile, adapterUsed, err = LoadConfig(configFlag, configAdapterFlag)
if err != nil {
+ logBuffer.FlushTo(defaultLogger)
return caddy.ExitCodeFailedStartup, err
}
}
@@ -235,11 +250,35 @@ func cmdRun(fl Flags) (int, error) {
}
}
+ // If we have a source config file (we're running via 'caddy run --config ...'),
+ // record it so SIGUSR1 can reload from the same file. Also provide a callback
+ // that knows how to load/adapt that source when requested by the main process.
+ if configFile != "" {
+ caddy.SetLastConfig(configFile, adapterUsed, func(file, adapter string) error {
+ cfg, _, _, err := LoadConfig(file, adapter)
+ if err != nil {
+ return err
+ }
+ return caddy.Load(cfg, true)
+ })
+ }
+
// run the initial config
err = caddy.Load(config, true)
if err != nil {
+ logBuffer.FlushTo(defaultLogger)
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
}
+ // release the reference to the config so it can be GC'd
+ config = nil //nolint:ineffassign,wastedassign
+
+ // at this stage the config will have replaced the
+ // default logger to the configured one, so we can
+ // log normally, now that the config is running.
+ // also clear our ref to the buffer so it can get GC'd
+ logger = caddy.Log()
+ defaultLogger = nil //nolint:ineffassign,wastedassign
+ logBuffer = nil //nolint:wastedassign,ineffassign
logger.Info("serving initial configuration")
// if we are to report to another process the successful start
@@ -255,18 +294,22 @@ func cmdRun(fl Flags) (int, error) {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("dialing confirmation address: %v", err)
}
- defer conn.Close()
_, err = conn.Write(confirmationBytes)
if err != nil {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("writing confirmation bytes to %s: %v", pingbackFlag, err)
}
+ // close (non-defer because we `select {}` below)
+ // and release references so they can be GC'd
+ conn.Close()
+ confirmationBytes = nil //nolint:ineffassign,wastedassign
+ conn = nil //nolint:wastedassign,ineffassign
}
// if enabled, reload config file automatically on changes
// (this better only be used in dev!)
if watchFlag {
- go watchConfigFile(configFile, configAdapterFlag)
+ go watchConfigFile(configFile, adapterUsed)
}
// warn if the environment does not provide enough information about the disk
@@ -288,6 +331,9 @@ func cmdRun(fl Flags) (int, error) {
}
}
+ // release the last local logger reference
+ logger = nil //nolint:wastedassign,ineffassign
+
select {}
}
@@ -318,7 +364,7 @@ func cmdReload(fl Flags) (int, error) {
forceFlag := fl.Bool("force")
// get the config in caddy's native format
- config, configFile, err := LoadConfig(configFlag, configAdapterFlag)
+ config, configFile, adapterUsed, err := LoadConfig(configFlag, configAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -336,6 +382,10 @@ func cmdReload(fl Flags) (int, error) {
if forceFlag {
headers.Set("Cache-Control", "must-revalidate")
}
+ // Provide the source file/adapter to the running process so it can
+ // preserve its last-config knowledge if this reload came from the same source.
+ headers.Set("Caddy-Config-Source-File", configFile)
+ headers.Set("Caddy-Config-Source-Adapter", adapterUsed)
resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config))
if err != nil {
@@ -440,16 +490,20 @@ func cmdEnviron(fl Flags) (int, error) {
}
func cmdAdaptConfig(fl Flags) (int, error) {
- inputFlag := fl.String("config")
+ configFlag := fl.String("config")
adapterFlag := fl.String("adapter")
prettyFlag := fl.Bool("pretty")
validateFlag := fl.Bool("validate")
var err error
- inputFlag, err = configFileWithRespectToDefault(caddy.Log(), inputFlag)
+ configFlag, err = configFileWithRespectToDefault(caddy.Log(), configFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
+ if configFlag == "" {
+ return caddy.ExitCodeFailedStartup,
+ fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)")
+ }
// load all additional envs as soon as possible
err = handleEnvFileFlag(fl)
@@ -468,13 +522,19 @@ func cmdAdaptConfig(fl Flags) (int, error) {
fmt.Errorf("unrecognized config adapter: %s", adapterFlag)
}
- input, err := os.ReadFile(inputFlag)
+ var input []byte
+ // read from stdin if the file name is "-"
+ if configFlag == "-" {
+ input, err = io.ReadAll(os.Stdin)
+ } else {
+ input, err = os.ReadFile(configFlag)
+ }
if err != nil {
return caddy.ExitCodeFailedStartup,
fmt.Errorf("reading input file: %v", err)
}
- opts := map[string]any{"filename": inputFlag}
+ opts := map[string]any{"filename": configFlag}
adaptedConfig, warnings, err := cfgAdapter.Adapt(input, opts)
if err != nil {
@@ -540,7 +600,7 @@ func cmdValidateConfig(fl Flags) (int, error) {
fmt.Errorf("input file required when there is no Caddyfile in current directory (use --config flag)")
}
- input, _, err := LoadConfig(configFlag, adapterFlag)
+ input, _, _, err := LoadConfig(configFlag, adapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -703,9 +763,7 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
- for k, v := range headers {
- req.Header[k] = v
- }
+ maps.Copy(req.Header, headers)
// make an HTTP client that dials our network type, since admin
// endpoints aren't always TCP, which is what the default transport
@@ -757,7 +815,7 @@ func DetermineAdminAPIAddress(address string, config []byte, configFile, configA
loadedConfig := config
if len(loadedConfig) == 0 {
// get the config in caddy's native format
- loadedConfig, loadedConfigFile, err = LoadConfig(configFile, configAdapter)
+ loadedConfig, loadedConfigFile, _, err = LoadConfig(configFile, configAdapter)
if err != nil {
return "", err
}
diff --git a/cmd/commands.go b/cmd/commands.go
index 259dd358f..c9ea636b9 100644
--- a/cmd/commands.go
+++ b/cmd/commands.go
@@ -20,6 +20,7 @@ import (
"os"
"regexp"
"strings"
+ "sync"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
@@ -80,10 +81,16 @@ type CommandFunc func(Flags) (int, error)
// Commands returns a list of commands initialised by
// RegisterCommand
func Commands() map[string]Command {
+ commandsMu.RLock()
+ defer commandsMu.RUnlock()
+
return commands
}
-var commands = make(map[string]Command)
+var (
+ commandsMu sync.RWMutex
+ commands = make(map[string]Command)
+)
func init() {
RegisterCommand(Command{
@@ -286,6 +293,8 @@ zero exit status will be returned.
If --envfile is specified, an environment file with environment variables
in the KEY=VALUE format will be loaded into the Caddy process.
+
+If you wish to use stdin instead of a regular file, use - as the path.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("config", "c", "", "Configuration file to adapt (required)")
@@ -383,7 +392,7 @@ lines will be prefixed with '-' and '+' where they differ. Note that
unchanged lines are prefixed with two spaces for alignment, and that this
is not a valid patch format.
-If you wish you use stdin instead of a regular file, use - as the path.
+If you wish to use stdin instead of a regular file, use - as the path.
When reading from stdin, the --overwrite flag has no effect: the result
is always printed to stdout.
`,
@@ -441,7 +450,7 @@ EXPERIMENTAL: May be changed or removed.
})
defaultFactory.Use(func(rootCmd *cobra.Command) {
- rootCmd.AddCommand(caddyCmdToCobra(Command{
+ manpageCommand := Command{
Name: "manpage",
Usage: "--directory ",
Short: "Generates the manual pages for Caddy commands",
@@ -471,11 +480,12 @@ argument of --directory. If the directory does not exist, it will be created.
return caddy.ExitCodeSuccess, nil
})
},
- }))
+ }
- // source: https://github.com/spf13/cobra/blob/main/shell_completions.md
- rootCmd.AddCommand(&cobra.Command{
- Use: "completion [bash|zsh|fish|powershell]",
+ // source: https://github.com/spf13/cobra/blob/6dec1ae26659a130bdb4c985768d1853b0e1bc06/site/content/completions/_index.md
+ completionCommand := Command{
+ Name: "completion",
+ Usage: "[bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions:
@@ -516,24 +526,37 @@ argument of --directory. If the directory does not exist, it will be created.
PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile.
`, rootCmd.Root().Name()),
- DisableFlagsInUseLine: true,
- ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
- Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
- RunE: func(cmd *cobra.Command, args []string) error {
- switch args[0] {
- case "bash":
- return cmd.Root().GenBashCompletion(os.Stdout)
- case "zsh":
- return cmd.Root().GenZshCompletion(os.Stdout)
- case "fish":
- return cmd.Root().GenFishCompletion(os.Stdout, true)
- case "powershell":
- return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
- default:
- return fmt.Errorf("unrecognized shell: %s", args[0])
+ CobraFunc: func(cmd *cobra.Command) {
+ cmd.DisableFlagsInUseLine = true
+ cmd.ValidArgs = []string{"bash", "zsh", "fish", "powershell"}
+ cmd.Args = cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs)
+ cmd.RunE = func(cmd *cobra.Command, args []string) error {
+ switch args[0] {
+ case "bash":
+ return cmd.Root().GenBashCompletion(os.Stdout)
+ case "zsh":
+ return cmd.Root().GenZshCompletion(os.Stdout)
+ case "fish":
+ return cmd.Root().GenFishCompletion(os.Stdout, true)
+ case "powershell":
+ return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
+ default:
+ return fmt.Errorf("unrecognized shell: %s", args[0])
+ }
}
},
- })
+ }
+
+ rootCmd.AddCommand(caddyCmdToCobra(manpageCommand))
+ rootCmd.AddCommand(caddyCmdToCobra(completionCommand))
+
+ // add manpage and completion commands to the map of
+ // available commands, because they're not registered
+ // through RegisterCommand.
+ commandsMu.Lock()
+ commands[manpageCommand.Name] = manpageCommand
+ commands[completionCommand.Name] = completionCommand
+ commandsMu.Unlock()
})
}
@@ -552,6 +575,9 @@ argument of --directory. If the directory does not exist, it will be created.
//
// This function should be used in init().
func RegisterCommand(cmd Command) {
+ commandsMu.Lock()
+ defer commandsMu.Unlock()
+
if cmd.Name == "" {
panic("command name is required")
}
@@ -570,6 +596,7 @@ func RegisterCommand(cmd Command) {
defaultFactory.Use(func(rootCmd *cobra.Command) {
rootCmd.AddCommand(caddyCmdToCobra(cmd))
})
+ commands[cmd.Name] = cmd
}
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
diff --git a/cmd/commands_test.go b/cmd/commands_test.go
new file mode 100644
index 000000000..085a9d789
--- /dev/null
+++ b/cmd/commands_test.go
@@ -0,0 +1,39 @@
+package caddycmd
+
+import (
+ "maps"
+ "reflect"
+ "slices"
+ "testing"
+)
+
+func TestCommandsAreAvailable(t *testing.T) {
+ // trigger init, and build the default factory, so that
+ // all commands from this package are available
+ cmd := defaultFactory.Build()
+ if cmd == nil {
+ t.Fatal("default factory failed to build")
+ }
+
+ // check that the default factory has 17 commands; it doesn't
+ // include the commands registered through calls to init in
+ // other packages
+ cmds := Commands()
+ if len(cmds) != 17 {
+ t.Errorf("expected 17 commands, got %d", len(cmds))
+ }
+
+ commandNames := slices.Collect(maps.Keys(cmds))
+ slices.Sort(commandNames)
+
+ expectedCommandNames := []string{
+ "adapt", "add-package", "build-info", "completion",
+ "environ", "fmt", "list-modules", "manpage",
+ "reload", "remove-package", "run", "start",
+ "stop", "storage", "upgrade", "validate", "version",
+ }
+
+ if !reflect.DeepEqual(expectedCommandNames, commandNames) {
+ t.Errorf("expected %v, got %v", expectedCommandNames, commandNames)
+ }
+}
diff --git a/cmd/main.go b/cmd/main.go
index 87fa9fb95..411f4545d 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -100,7 +100,12 @@ func handlePingbackConn(conn net.Conn, expect []byte) error {
// there is no config available. It prints any warnings to stderr,
// and returns the resulting JSON config bytes along with
// the name of the loaded config file (if any).
-func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
+// The return values are:
+// - config bytes (nil if no config)
+// - config file used ("" if none)
+// - adapter used ("" if none)
+// - error, if any
+func LoadConfig(configFile, adapterName string) ([]byte, string, string, error) {
return loadConfigWithLogger(caddy.Log(), configFile, adapterName)
}
@@ -138,7 +143,7 @@ func isCaddyfile(configFile, adapterName string) (bool, error) {
return false, nil
}
-func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([]byte, string, error) {
+func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([]byte, string, string, error) {
// if no logger is provided, use a nop logger
// just so we don't have to check for nil
if logger == nil {
@@ -147,7 +152,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
// specifying an adapter without a config file is ambiguous
if adapterName != "" && configFile == "" {
- return nil, "", fmt.Errorf("cannot adapt config without config file (use --config)")
+ return nil, "", "", fmt.Errorf("cannot adapt config without config file (use --config)")
}
// load initial config and adapter
@@ -158,13 +163,13 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
if configFile == "-" {
config, err = io.ReadAll(os.Stdin)
if err != nil {
- return nil, "", fmt.Errorf("reading config from stdin: %v", err)
+ return nil, "", "", fmt.Errorf("reading config from stdin: %v", err)
}
logger.Info("using config from stdin")
} else {
config, err = os.ReadFile(configFile)
if err != nil {
- return nil, "", fmt.Errorf("reading config from file: %v", err)
+ return nil, "", "", fmt.Errorf("reading config from file: %v", err)
}
logger.Info("using config from file", zap.String("file", configFile))
}
@@ -179,7 +184,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
cfgAdapter = nil
} else if err != nil {
// default Caddyfile exists, but error reading it
- return nil, "", fmt.Errorf("reading default Caddyfile: %v", err)
+ return nil, "", "", fmt.Errorf("reading default Caddyfile: %v", err)
} else {
// success reading default Caddyfile
configFile = "Caddyfile"
@@ -191,14 +196,14 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
if yes, err := isCaddyfile(configFile, adapterName); yes {
adapterName = "caddyfile"
} else if err != nil {
- return nil, "", err
+ return nil, "", "", err
}
// load config adapter
if adapterName != "" {
cfgAdapter = caddyconfig.GetAdapter(adapterName)
if cfgAdapter == nil {
- return nil, "", fmt.Errorf("unrecognized config adapter: %s", adapterName)
+ return nil, "", "", fmt.Errorf("unrecognized config adapter: %s", adapterName)
}
}
@@ -208,7 +213,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
"filename": configFile,
})
if err != nil {
- return nil, "", fmt.Errorf("adapting config using %s: %v", adapterName, err)
+ return nil, "", "", fmt.Errorf("adapting config using %s: %v", adapterName, err)
}
logger.Info("adapted config to JSON", zap.String("adapter", adapterName))
for _, warn := range warnings {
@@ -226,11 +231,11 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
// validate that the config is at least valid JSON
err = json.Unmarshal(config, new(any))
if err != nil {
- return nil, "", fmt.Errorf("config is not valid JSON: %v; did you mean to use a config adapter (the --adapter flag)?", err)
+ return nil, "", "", fmt.Errorf("config is not valid JSON: %v; did you mean to use a config adapter (the --adapter flag)?", err)
}
}
- return config, configFile, nil
+ return config, configFile, adapterName, nil
}
// watchConfigFile watches the config file at filename for changes
@@ -256,7 +261,7 @@ func watchConfigFile(filename, adapterName string) {
}
// get current config
- lastCfg, _, err := loadConfigWithLogger(nil, filename, adapterName)
+ lastCfg, _, _, err := loadConfigWithLogger(nil, filename, adapterName)
if err != nil {
logger().Error("unable to load latest config", zap.Error(err))
return
@@ -268,7 +273,7 @@ func watchConfigFile(filename, adapterName string) {
//nolint:staticcheck
for range time.Tick(1 * time.Second) {
// get current config
- newCfg, _, err := loadConfigWithLogger(nil, filename, adapterName)
+ newCfg, _, _, err := loadConfigWithLogger(nil, filename, adapterName)
if err != nil {
logger().Error("unable to load latest config", zap.Error(err))
return
@@ -418,7 +423,7 @@ func parseEnvFile(envInput io.Reader) (map[string]string, error) {
// quoted value: support newlines
if strings.HasPrefix(val, `"`) || strings.HasPrefix(val, "'") {
quote := string(val[0])
- for !(strings.HasSuffix(line, quote) && !strings.HasSuffix(line, `\`+quote)) {
+ for !strings.HasSuffix(line, quote) || strings.HasSuffix(line, `\`+quote) {
val = strings.ReplaceAll(val, `\`+quote, quote)
if !scanner.Scan() {
break
diff --git a/cmd/main_test.go b/cmd/main_test.go
index 3b2412c57..bff34f443 100644
--- a/cmd/main_test.go
+++ b/cmd/main_test.go
@@ -235,7 +235,6 @@ func Test_isCaddyfile(t *testing.T) {
wantErr: false,
},
{
-
name: "json is not caddyfile but not error",
args: args{
configFile: "./Caddyfile.json",
@@ -245,7 +244,6 @@ func Test_isCaddyfile(t *testing.T) {
wantErr: false,
},
{
-
name: "prefix of Caddyfile and ./ with any extension is Caddyfile",
args: args{
configFile: "./Caddyfile.prd",
@@ -255,7 +253,6 @@ func Test_isCaddyfile(t *testing.T) {
wantErr: false,
},
{
-
name: "prefix of Caddyfile without ./ with any extension is Caddyfile",
args: args{
configFile: "Caddyfile.prd",
diff --git a/cmd/packagesfuncs.go b/cmd/packagesfuncs.go
index 695232001..4d0ff0680 100644
--- a/cmd/packagesfuncs.go
+++ b/cmd/packagesfuncs.go
@@ -62,7 +62,7 @@ func splitModule(arg string) (module, version string, err error) {
err = fmt.Errorf("module name is required")
}
- return
+ return module, version, err
}
func cmdAddPackage(fl Flags) (int, error) {
@@ -84,7 +84,7 @@ func cmdAddPackage(fl Flags) (int, error) {
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) {
+ if _, ok := pluginPkgs[module]; ok && (version == "" || pluginPkgs[module].Version == version) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
}
pluginPkgs[module] = pluginPackage{Version: version, Path: module}
@@ -217,7 +217,7 @@ func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
bi, ok := debug.ReadBuildInfo()
if !ok {
err = fmt.Errorf("no build info")
- return
+ return standard, nonstandard, unknown, err
}
for _, modID := range caddy.Modules() {
@@ -260,7 +260,7 @@ func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
nonstandard = append(nonstandard, caddyModGoMod)
}
}
- return
+ return standard, nonstandard, unknown, err
}
func listModules(path string) error {
diff --git a/cmd/storagefuncs.go b/cmd/storagefuncs.go
index 3c4219719..5606fe4ae 100644
--- a/cmd/storagefuncs.go
+++ b/cmd/storagefuncs.go
@@ -36,7 +36,7 @@ type storVal struct {
// determineStorage returns the top-level storage module from the given config.
// It may return nil even if no error.
func determineStorage(configFile string, configAdapter string) (*storVal, error) {
- cfg, _, err := LoadConfig(configFile, configAdapter)
+ cfg, _, _, err := LoadConfig(configFile, configAdapter)
if err != nil {
return nil, err
}
diff --git a/context.go b/context.go
index 94623df72..4c1139936 100644
--- a/context.go
+++ b/context.go
@@ -91,14 +91,14 @@ func (ctx *Context) OnCancel(f func()) {
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.
-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 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
@@ -277,6 +277,14 @@ func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error)
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.
// 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).
@@ -385,6 +393,8 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
return nil, fmt.Errorf("module value cannot be null")
}
+ var err error
+
// 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
@@ -394,12 +404,17 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
// module has been configured for DNS challenges)
if appModule, ok := val.(App); ok {
ctx.cfg.apps[id] = appModule
+ defer func() {
+ if err != nil {
+ ctx.cfg.failedApps[id] = err
+ }
+ }()
}
ctx.ancestry = append(ctx.ancestry, val)
if prov, ok := val.(Provisioner); ok {
- err := prov.Provision(ctx)
+ err = prov.Provision(ctx)
if err != nil {
// incomplete provisioning could have left state
// dangling, so make sure it gets cleaned up
@@ -414,7 +429,7 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
}
if validator, ok := val.(Validator); ok {
- err := validator.Validate()
+ err = validator.Validate()
if err != nil {
// since the module was already provisioned, make sure we clean up
if cleanerUpper, ok := val.(CleanerUpper); ok {
@@ -429,6 +444,14 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
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
}
@@ -471,6 +494,10 @@ func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.
// or stop App modules. The caller is expected to assert to the
// concrete type.
func (ctx Context) App(name string) (any, error) {
+ // if the app failed to load before, return the cached error
+ if err, ok := ctx.cfg.failedApps[name]; ok {
+ return nil, fmt.Errorf("loading %s app module: %v", name, err)
+ }
if app, ok := ctx.cfg.apps[name]; ok {
return app, nil
}
@@ -495,6 +522,10 @@ func (ctx Context) AppIfConfigured(name string) (any, error) {
if ctx.cfg == nil {
return nil, fmt.Errorf("app module %s: %w", name, ErrNotConfigured)
}
+ // if the app failed to load before, return the cached error
+ if err, ok := ctx.cfg.failedApps[name]; ok {
+ return nil, fmt.Errorf("loading %s app module: %v", name, err)
+ }
if app, ok := ctx.cfg.apps[name]; ok {
return app, nil
}
@@ -561,11 +592,11 @@ func (ctx Context) Slogger() *slog.Logger {
if err != nil {
panic("config missing, unable to create dev logger: " + err.Error())
}
- return slog.New(zapslog.NewHandler(l.Core(), nil))
+ return slog.New(zapslog.NewHandler(l.Core()))
}
mod := ctx.Module()
if mod == nil {
- return slog.New(zapslog.NewHandler(Log().Core(), nil))
+ return slog.New(zapslog.NewHandler(Log().Core()))
}
return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(),
zapslog.WithName(string(mod.CaddyModule().ID)),
@@ -600,3 +631,11 @@ func (ctx *Context) WithValue(key, value any) Context {
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
+}
diff --git a/go.mod b/go.mod
index 6bd8743e7..70f85aed9 100644
--- a/go.mod
+++ b/go.mod
@@ -1,84 +1,101 @@
module github.com/caddyserver/caddy/v2
-go 1.24
+go 1.25
require (
- github.com/BurntSushi/toml v1.4.0
- github.com/KimMachineGun/automemlimit v0.7.1
+ github.com/BurntSushi/toml v1.5.0
+ github.com/DeRuina/timberjack v1.3.8
+ github.com/KimMachineGun/automemlimit v0.7.4
github.com/Masterminds/sprig/v3 v3.3.0
- github.com/alecthomas/chroma/v2 v2.15.0
+ github.com/alecthomas/chroma/v2 v2.20.0
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
- github.com/caddyserver/certmagic v0.22.0
+ github.com/caddyserver/certmagic v0.25.0
github.com/caddyserver/zerossl v0.1.3
- github.com/cloudflare/circl v1.6.0
+ github.com/cloudflare/circl v1.6.1
github.com/dustin/go-humanize v1.0.1
- github.com/go-chi/chi/v5 v5.2.1
- github.com/google/cel-go v0.24.1
+ github.com/go-chi/chi/v5 v5.2.3
+ github.com/google/cel-go v0.26.1
github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.18.0
- github.com/klauspost/cpuid/v2 v2.2.10
- github.com/mholt/acmez/v3 v3.1.0
- github.com/prometheus/client_golang v1.19.1
- github.com/quic-go/quic-go v0.50.0
- github.com/smallstep/certificates v0.26.1
- github.com/smallstep/nosql v0.6.1
+ github.com/klauspost/cpuid/v2 v2.3.0
+ github.com/mholt/acmez/v3 v3.1.4
+ github.com/prometheus/client_golang v1.23.2
+ github.com/quic-go/quic-go v0.55.0
+ github.com/smallstep/certificates v0.28.4
+ github.com/smallstep/nosql v0.7.0
github.com/smallstep/truststore v0.13.0
- github.com/spf13/cobra v1.9.1
- github.com/spf13/pflag v1.0.6
- github.com/stretchr/testify v1.10.0
+ github.com/spf13/cobra v1.10.1
+ github.com/spf13/pflag v1.0.9
+ github.com/stretchr/testify v1.11.1
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53
- github.com/yuin/goldmark v1.7.8
+ github.com/yuin/goldmark v1.7.13
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/propagators/autoprop v0.42.0
- go.opentelemetry.io/otel v1.31.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0
- go.opentelemetry.io/otel/sdk v1.31.0
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0
+ go.opentelemetry.io/contrib/propagators/autoprop v0.63.0
+ go.opentelemetry.io/otel v1.38.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
+ go.opentelemetry.io/otel/sdk v1.38.0
go.uber.org/automaxprocs v1.6.0
go.uber.org/zap v1.27.0
go.uber.org/zap/exp v0.3.0
- golang.org/x/crypto v0.36.0
- golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810
- golang.org/x/net v0.37.0
- golang.org/x/sync v0.12.0
- golang.org/x/term v0.30.0
- golang.org/x/time v0.11.0
- gopkg.in/natefinch/lumberjack.v2 v2.2.1
+ golang.org/x/crypto v0.43.0
+ golang.org/x/crypto/x509roots/fallback v0.0.0-20250927194341-2beaa59a3c99
+ golang.org/x/net v0.46.0
+ golang.org/x/sync v0.17.0
+ golang.org/x/term v0.36.0
+ golang.org/x/time v0.14.0
gopkg.in/yaml.v3 v3.0.1
)
require (
- cel.dev/expr v0.19.1 // indirect
+ cel.dev/expr v0.24.0 // indirect
+ cloud.google.com/go/auth v0.16.4 // indirect
+ cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
+ cloud.google.com/go/compute/metadata v0.8.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
+ github.com/ccoveille/go-safecast v1.6.1 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
+ github.com/coreos/go-oidc/v3 v3.14.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.8.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
- github.com/go-kit/log v0.2.1 // indirect
+ github.com/go-jose/go-jose/v4 v4.1.1 // 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.5 // indirect
github.com/google/go-tspi v0.3.0 // indirect
- github.com/google/pprof v0.0.0-20231212022811-ec68065c825e // indirect
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
- github.com/onsi/ginkgo/v2 v2.13.2 // indirect
+ github.com/google/s2a-go v0.1.9 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
+ github.com/googleapis/gax-go/v2 v2.15.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
+ github.com/jackc/pgx/v5 v5.6.0 // indirect
+ github.com/jackc/puddle/v2 v2.2.1 // indirect
+ github.com/kylelemons/godebug v1.1.0 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
- github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
- github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
- github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
+ github.com/smallstep/cli-utils v0.12.1 // indirect
+ github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca // indirect
+ github.com/smallstep/linkedca v0.23.0 // indirect
+ github.com/smallstep/pkcs7 v0.2.1 // indirect
+ github.com/smallstep/scep v0.0.0-20240926084937-8cf1ca453101 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
- go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect
- go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
- go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
- go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
- go.uber.org/mock v0.5.0 // 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/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/contrib/propagators/aws v1.38.0 // indirect
+ go.opentelemetry.io/contrib/propagators/b3 v1.38.0 // indirect
+ go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect
+ go.opentelemetry.io/contrib/propagators/ot v1.38.0 // indirect
+ go.yaml.in/yaml/v2 v2.4.3 // indirect
+ golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
+ golang.org/x/oauth2 v0.31.0 // indirect
+ google.golang.org/api v0.247.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect
+ google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
)
require (
@@ -87,72 +104,60 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0
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.7 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.2.0 // 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.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
- github.com/go-kit/kit v0.13.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.3 // indirect
github.com/go-logr/stdr v1.2.2 // 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-sql-driver/mysql v1.8.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/jackc/chunkreader/v2 v2.0.1 // indirect
- github.com/jackc/pgconn v1.14.3 // indirect
- github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
- github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
- github.com/jackc/pgtype v1.14.0 // indirect
- github.com/jackc/pgx/v4 v4.18.3 // indirect
- github.com/libdns/libdns v0.2.3
+ github.com/libdns/libdns v1.1.1
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // 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.68 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
- github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964
+ github.com/pires/go-proxyproto v0.8.1
github.com/pkg/errors v0.9.1 // indirect
- github.com/prometheus/client_model v0.5.0
- github.com/prometheus/common v0.48.0 // indirect
- github.com/prometheus/procfs v0.12.0 // indirect
- github.com/rs/xid v1.5.0 // indirect
+ github.com/prometheus/client_model v0.6.2
+ github.com/prometheus/common v0.67.1 // indirect
+ github.com/prometheus/procfs v0.17.0 // indirect
+ github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
- github.com/slackhq/nebula v1.6.1 // indirect
+ github.com/slackhq/nebula v1.9.5 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
- github.com/urfave/cli v1.22.14 // indirect
- go.etcd.io/bbolt v1.3.9 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect
- go.opentelemetry.io/otel/metric v1.31.0 // indirect
- go.opentelemetry.io/otel/trace v1.31.0
- go.opentelemetry.io/proto/otlp v1.3.1 // indirect
- go.step.sm/cli-utils v0.9.0 // indirect
- go.step.sm/crypto v0.45.0
- go.step.sm/linkedca v0.20.1 // indirect
+ github.com/urfave/cli v1.22.17 // indirect
+ go.etcd.io/bbolt v1.3.10 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/metric v1.38.0 // indirect
+ go.opentelemetry.io/otel/trace v1.38.0
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
+ go.step.sm/crypto v0.70.0
go.uber.org/multierr v1.11.0 // indirect
- golang.org/x/mod v0.24.0 // indirect
- golang.org/x/sys v0.31.0
- golang.org/x/text v0.23.0 // indirect
- golang.org/x/tools v0.31.0 // indirect
- google.golang.org/grpc v1.67.1 // indirect
- google.golang.org/protobuf v1.35.1 // indirect
+ golang.org/x/mod v0.29.0 // indirect
+ golang.org/x/sys v0.37.0
+ golang.org/x/text v0.30.0 // indirect
+ golang.org/x/tools v0.38.0 // indirect
+ google.golang.org/grpc v1.75.1 // indirect
+ google.golang.org/protobuf v1.36.10 // indirect
howett.net/plist v1.0.0 // indirect
)
diff --git a/go.sum b/go.sum
index 6cf21753d..5c4a2975b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,23 +1,23 @@
-cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
-cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
+cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
+cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
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/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
-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/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
-cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
-cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
-cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
-cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
-cloud.google.com/go/iam v1.1.8/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/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
-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 v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
+cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
+cloud.google.com/go/auth v0.16.4 h1:fXOAIQmkApVvcIn7Pc2+5J8QTMVbUGLscnSVNl11su8=
+cloud.google.com/go/auth v0.16.4/go.mod h1:j10ncYwjX/g3cdX7GpEzsdM+d+ZNsXAbb6qXA7p1Y5M=
+cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
+cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
+cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
+cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
+cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
+cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
+cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk=
+cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8=
+cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
+cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
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=
@@ -30,14 +30,14 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-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/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
+github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/DeRuina/timberjack v1.3.8 h1:lLxmRExvZygKSbb27Vp9hS0Tv8mL0WmFbwfRF29nY0Q=
+github.com/DeRuina/timberjack v1.3.8/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
+github.com/KimMachineGun/automemlimit v0.7.4 h1:UY7QYOIfrr3wjjOAqahFmC3IaQCLWvur9nmfIn6LnWk=
+github.com/KimMachineGun/automemlimit v0.7.4/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
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/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.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
@@ -49,56 +49,58 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
-github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
-github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
+github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
+github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
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/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
+github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
+github.com/alecthomas/repr v0.5.1/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/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
-github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
-github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
-github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A=
-github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
-github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0=
-github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ=
-github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs=
-github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
-github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns=
-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/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
+github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU=
+github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
+github.com/aws/aws-sdk-go-v2/config v1.31.0 h1:9yH0xiY5fUnVNLRWO0AtayqwU1ndriZdN78LlhruJR4=
+github.com/aws/aws-sdk-go-v2/config v1.31.0/go.mod h1:VeV3K72nXnhbe4EuxxhzsDc/ByrCSlZwUnWH52Nde/I=
+github.com/aws/aws-sdk-go-v2/credentials v1.18.4 h1:IPd0Algf1b+Qy9BcDp0sCUcIWdCQPSzDoMK3a8pcbUM=
+github.com/aws/aws-sdk-go-v2/credentials v1.18.4/go.mod h1:nwg78FjH2qvsRM1EVZlX9WuGUJOL5od+0qvm0adEzHk=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 h1:GicIdnekoJsjq9wqnvyi2elW6CGMSYKhdozE7/Svh78=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3/go.mod h1:R7BIi6WNC5mc1kfRM7XM/VHC3uRWkjc396sfabq4iOo=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 h1:o9RnO+YZ4X+kt5Z7Nvcishlz0nksIt2PIzDglLMP0vA=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3/go.mod h1:+6aLJzOG1fvMOyzIySYjOFjcguGvVRL68R+uoRencN4=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 h1:joyyUFhiTQQmVK6ImzNU9TQSNRNeD9kOklqTzyk5v6s=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3/go.mod h1:+vNIyZQP3b3B1tSLI0lxvrU9cfM7gpdRXMFfm67ZcPc=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 h1:ieRzyHXypu5ByllM7Sp4hC5f/1Fy5wqxqY0yB85hC7s=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3/go.mod h1:O5ROz8jHiOAKAwx179v+7sHMhfobFVi6nZt8DEyiYoM=
+github.com/aws/aws-sdk-go-v2/service/kms v1.44.0 h1:Z95XCqqSnwXr0AY7PgsiOUBhUG2GoDM5getw6RfD1Lg=
+github.com/aws/aws-sdk-go-v2/service/kms v1.44.0/go.mod h1:DqcSngL7jJeU1fOzh5Ll5rSvX/MlMV6OZlE4mVdFAQc=
+github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 h1:Mc/MKBf2m4VynyJkABoVEN+QzkfLqGj0aiJuEe7cMeM=
+github.com/aws/aws-sdk-go-v2/service/sso v1.28.0/go.mod h1:iS5OmxEcN4QIPXARGhavH7S8kETNL11kym6jhoS7IUQ=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 h1:6csaS/aJmqZQbKhi1EyEMM7yBW653Wy/B9hnBofW+sw=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0/go.mod h1:59qHWaY5B+Rs7HGTuVGaC32m0rdpQ68N8QCN3khYiqs=
+github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 h1:MG9VFW43M4A8BYeAfaJJZWrroinxeTi2r3+SnmLQfSA=
+github.com/aws/aws-sdk-go-v2/service/sts v1.37.0/go.mod h1:JdeBDPgpJfuS6rU/hNglmOigKhyEZtBmbraLE4GK1J8=
+github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
+github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
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/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/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
-github.com/caddyserver/certmagic v0.22.0 h1:hi2skv2jouUw9uQUEyYSTTmqPZPHgf61dOANSIVCLOw=
-github.com/caddyserver/certmagic v0.22.0/go.mod h1:Vc0msarAPhOagbDc/SU6M2zbzdwVuZ0lkTh2EqtH4vs=
+github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
+github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
-github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q=
+github.com/ccoveille/go-safecast v1.6.1/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
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/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -113,21 +115,18 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
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/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/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
+github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
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-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
+github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
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-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
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.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
-github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
+github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -144,55 +143,41 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA
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.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
-github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
+github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
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/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
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/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
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/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.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
+github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
-github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
-github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
+github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
+github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
-github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
-github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
-github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
-github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
-github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
-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-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
+github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
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.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
-github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
-github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
-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/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
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/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/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=
@@ -206,44 +191,41 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
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/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
-github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI=
-github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8=
+github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
+github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
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/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
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/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/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
+github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
+github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
+github.com/google/go-tpm-tools v0.4.5 h1:3fhthtyMDbIZFR5/0y1hvUoZ1Kf4i1eZ7C73R4Pvd+k=
+github.com/google/go-tpm-tools v0.4.5/go.mod h1:ktjTNq8yZFD6TzdBFefUfen96rF3NpYwpSb2d8bc+Y8=
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/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/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
-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/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
+github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
+github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
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/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/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
+github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
+github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
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/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
+github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
+github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
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/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
@@ -252,53 +234,14 @@ github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
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/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
-github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
-github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
-github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
-github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
-github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
-github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
-github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
-github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
-github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
-github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
-github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
-github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
-github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
-github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
-github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
-github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
-github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
-github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
-github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
-github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
-github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
-github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
-github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
-github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
-github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
-github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
-github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
-github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
-github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
-github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
-github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
-github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
-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 v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
+github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
+github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
+github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
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/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -307,51 +250,39 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
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.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
-github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
-github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
-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/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
+github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
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.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-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.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
-github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
-github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
+github.com/libdns/libdns v1.1.1/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/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/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.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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/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/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
-github.com/mholt/acmez/v3 v3.1.0 h1:RlOx2SSZ8dIAM5GfkMe8TdaxjjkiHTGorlMUt8GeMzg=
-github.com/mholt/acmez/v3 v3.1.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
+github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ=
+github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
-github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
-github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
+github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
+github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
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/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -362,20 +293,18 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
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/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
-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/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/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/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964 h1:ct/vxNBgHpASQ4sT8NaBX9LtsEtluZqaUJydLG50U3E=
-github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
+github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
+github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -384,38 +313,31 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
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/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
+github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
+github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
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/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
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/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
+github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
+github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
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/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
-github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
-github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
+github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
-github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
-github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
-github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
-github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
+github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
+github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-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/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 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=
@@ -442,25 +364,28 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
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.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
-github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI=
+github.com/slackhq/nebula v1.9.5 h1:ZrxcvP/lxwFglaijmiwXLuCSkybZMJnqSYI1S8DtGnY=
+github.com/slackhq/nebula v1.9.5/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
-github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o=
-github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis=
-github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA=
-github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
-github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y=
-github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y=
-github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg=
-github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y=
-github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw=
-github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
+github.com/smallstep/certificates v0.28.4 h1:JTU6/A5Xes6m+OsR6fw1RACSA362vJc9SOFVG7poBEw=
+github.com/smallstep/certificates v0.28.4/go.mod h1:LUqo+7mKZE7FZldlTb0zhU4A0bq4G4+akieFMcTaWvA=
+github.com/smallstep/cli-utils v0.12.1 h1:D9QvfbFqiKq3snGZ2xDcXEFrdFJ1mQfPHZMq/leerpE=
+github.com/smallstep/cli-utils v0.12.1/go.mod h1:skV2Neg8qjiKPu2fphM89H9bIxNpKiiRTnX9Q6Lc+20=
+github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4=
+github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
+github.com/smallstep/linkedca v0.23.0 h1:5W/7EudlK1HcCIdZM68dJlZ7orqCCCyv6bm2l/0JmLU=
+github.com/smallstep/linkedca v0.23.0/go.mod h1:7cyRM9soAYySg9ag65QwytcgGOM+4gOlkJ/YA58A9E8=
+github.com/smallstep/nosql v0.7.0 h1:YiWC9ZAHcrLCrayfaF+QJUv16I2bZ7KdLC3RpJcnAnE=
+github.com/smallstep/nosql v0.7.0/go.mod h1:H5VnKMCbeq9QA6SRY5iqPylfxLfYcLwvUff3onQ8+HU=
+github.com/smallstep/pkcs7 v0.0.0-20240911091500-b1cae6277023/go.mod h1:CM5KrX7rxWgwDdMj9yef/pJB2OPgy/56z4IEx2UIbpc=
+github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA=
+github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0=
+github.com/smallstep/scep v0.0.0-20240926084937-8cf1ca453101 h1:LyZqn24/ZiVg8v9Hq07K6mx6RqPtpDeK+De5vf4QEY4=
+github.com/smallstep/scep v0.0.0-20240926084937-8cf1ca453101/go.mod h1:EuKQjYGQwhUa1mgD21zxIgOgUYLsqikJmvxNscxpS/Y=
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/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
@@ -473,37 +398,36 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
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.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
+github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
+github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
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.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
-github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
+github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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/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/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.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
+github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
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=
@@ -511,8 +435,8 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY
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.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
-github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
+github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
+github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
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/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
@@ -521,102 +445,86 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
-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/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
+go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
+go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
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/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/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.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
-go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w=
-go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/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/go.mod h1:pAlCYRWff4uGqRXOVn3WP8pDZ5E0K56bEoG7a1VSL4k=
-go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo=
-go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc=
-go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWEl3a6Xvd4tw/3xxGO1i05Y=
-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/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk=
-go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
-go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
-go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
-go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
-go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
-go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
-go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
-go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
-go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
-go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
-go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
-go.step.sm/cli-utils v0.9.0/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/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY=
-go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU=
-go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw=
-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.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
+go.opentelemetry.io/contrib/propagators/autoprop v0.63.0 h1:S3+4UwR3Y1tUKklruMwOacAFInNvtuOexz4ZTmJNAyw=
+go.opentelemetry.io/contrib/propagators/autoprop v0.63.0/go.mod h1:qpIuOggbbw2T9nKRaO1je/oTRKd4zslAcJonN8LYbTg=
+go.opentelemetry.io/contrib/propagators/aws v1.38.0 h1:eRZ7asSbLc5dH7+TBzL6hFKb1dabz0IV51uUUwYRZts=
+go.opentelemetry.io/contrib/propagators/aws v1.38.0/go.mod h1:wXqc9NTGcXapBExHBDVLEZlByu6quiQL8w7Tjgv8TCg=
+go.opentelemetry.io/contrib/propagators/b3 v1.38.0 h1:uHsCCOSKl0kLrV2dLkFK+8Ywk9iKa/fptkytc6aFFEo=
+go.opentelemetry.io/contrib/propagators/b3 v1.38.0/go.mod h1:wMRSZJZcY8ya9mApLLhwIMjqmApy2o/Ml+62lhvxyHU=
+go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc=
+go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo=
+go.opentelemetry.io/contrib/propagators/ot v1.38.0 h1:k4gSyyohaDXI8F9BDXYC3uO2vr5sRNeQFMsN9Zn0EoI=
+go.opentelemetry.io/contrib/propagators/ot v1.38.0/go.mod h1:2hDsuiHRO39SRUMhYGqmj64z/IuMRoxE4bBSFR82Lo8=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
+go.step.sm/crypto v0.70.0 h1:Q9Ft7N637mucyZcHZd1+0VVQJVwDCKqcb9CYcYi7cds=
+go.step.sm/crypto v0.70.0/go.mod h1:pzfUhS5/ue7ev64PLlEgXvhx1opwbhFCjkvlhsxVds0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
-go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
-go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
+go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
-go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
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.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
+go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
+go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
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-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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
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.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
-golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 h1:V5+zy0jmgNYmK1uW/sPpBw8ioFvalrhaUrYWmu1Fpe4=
-golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
+golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
+golang.org/x/crypto/x509roots/fallback v0.0.0-20250927194341-2beaa59a3c99 h1:CH0o4/bZX6KIUCjjgjmtNtfM/kXSkTYlzTOB9vZF45g=
+golang.org/x/crypto/x509roots/fallback v0.0.0-20250927194341-2beaa59a3c99/go.mod h1:MEIPiCnxvQEjA4astfaKItNwEVZA5Ki+3+nyGbJ5N18=
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/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
+golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
+golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
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/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.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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
-golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
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=
@@ -624,23 +532,23 @@ golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73r
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-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-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-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.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.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
-golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
-golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
+golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
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=
@@ -649,25 +557,22 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
-golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
+golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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-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-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-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-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-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/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-20200223170610-d5e6a3e2c0ae/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-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -678,60 +583,62 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
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.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.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
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.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
+golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
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.3/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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
-golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
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-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-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-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
-golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
-golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
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/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
+google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc=
+google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM=
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=
@@ -741,30 +648,27 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
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/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-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
-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-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
+google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
+google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
+google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
+google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
-google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
-google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
-google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
+google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
+google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
+google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
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/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/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
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=
@@ -776,7 +680,6 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
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=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
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=
diff --git a/internal/filesystems/map.go b/internal/filesystems/map.go
index e795ed1fe..3ecb34e40 100644
--- a/internal/filesystems/map.go
+++ b/internal/filesystems/map.go
@@ -7,10 +7,10 @@ import (
)
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
type wrapperFs struct {
@@ -18,24 +18,24 @@ type wrapperFs struct {
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
// it includes a default filesystem, based off the os fs
-type FilesystemMap struct {
+type FileSystemMap struct {
m sync.Map
}
// 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 == "" {
- k = DefaultFilesystemKey
+ k = DefaultFileSystemKey
}
return k
}
// 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
-func (f *FilesystemMap) Register(k string, v fs.FS) {
+func (f *FileSystemMap) Register(k string, v fs.FS) {
k = f.key(k)
if v == nil {
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
// 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
-func (f *FilesystemMap) Unregister(k string) {
+func (f *FileSystemMap) Unregister(k string) {
k = f.key(k)
- if k == DefaultFilesystemKey {
- f.m.Store(k, DefaultFilesystem)
+ if k == DefaultFileSystemKey {
+ f.m.Store(k, DefaultFileSystem)
} else {
f.m.Delete(k)
}
}
// 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)
c, ok := f.m.Load(strings.TrimSpace(k))
if !ok {
- if k == DefaultFilesystemKey {
- f.m.Store(k, DefaultFilesystem)
- return DefaultFilesystem, true
+ if k == DefaultFileSystemKey {
+ f.m.Store(k, DefaultFileSystem)
+ return DefaultFileSystem, true
}
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
-func (f *FilesystemMap) Default() fs.FS {
- val, _ := f.Get(DefaultFilesystemKey)
+func (f *FileSystemMap) Default() fs.FS {
+ val, _ := f.Get(DefaultFileSystemKey)
return val
}
diff --git a/internal/logbuffer.go b/internal/logbuffer.go
new file mode 100644
index 000000000..991041bd8
--- /dev/null
+++ b/internal/logbuffer.go
@@ -0,0 +1,82 @@
+// 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 internal
+
+import (
+ "sync"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+// LogBufferCore is a zapcore.Core that buffers log entries in memory.
+type LogBufferCore struct {
+ mu sync.Mutex
+ entries []zapcore.Entry
+ fields [][]zapcore.Field
+ level zapcore.LevelEnabler
+}
+
+type LogBufferCoreInterface interface {
+ zapcore.Core
+ FlushTo(*zap.Logger)
+}
+
+func NewLogBufferCore(level zapcore.LevelEnabler) *LogBufferCore {
+ return &LogBufferCore{
+ level: level,
+ }
+}
+
+func (c *LogBufferCore) Enabled(lvl zapcore.Level) bool {
+ return c.level.Enabled(lvl)
+}
+
+func (c *LogBufferCore) With(fields []zapcore.Field) zapcore.Core {
+ return c
+}
+
+func (c *LogBufferCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
+ if c.Enabled(entry.Level) {
+ return ce.AddCore(entry, c)
+ }
+ return ce
+}
+
+func (c *LogBufferCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.entries = append(c.entries, entry)
+ c.fields = append(c.fields, fields)
+ return nil
+}
+
+func (c *LogBufferCore) Sync() error { return nil }
+
+// FlushTo flushes buffered logs to the given zap.Logger.
+func (c *LogBufferCore) FlushTo(logger *zap.Logger) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ for idx, entry := range c.entries {
+ logger.WithOptions().Check(entry.Level, entry.Message).Write(c.fields[idx]...)
+ }
+ c.entries = nil
+ c.fields = nil
+}
+
+var (
+ _ zapcore.Core = (*LogBufferCore)(nil)
+ _ LogBufferCoreInterface = (*LogBufferCore)(nil)
+)
diff --git a/internal/logs.go b/internal/logs.go
new file mode 100644
index 000000000..4ed4a572e
--- /dev/null
+++ b/internal/logs.go
@@ -0,0 +1,22 @@
+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
+}
diff --git a/listen.go b/listen.go
index 1a7051bbf..fba9c3a6b 100644
--- a/listen.go
+++ b/listen.go
@@ -107,7 +107,8 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
if err != nil {
return nil, err
}
- return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
+
+ return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAliveConfig: config.KeepAliveConfig}, nil
}
// fakeCloseListener is a private wrapper over a listener that
@@ -121,12 +122,11 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
type fakeCloseListener struct {
closed int32 // accessed atomically; belongs to this struct only
*sharedListener // embedded, so we also become a net.Listener
- keepAlivePeriod time.Duration
+ keepAliveConfig net.KeepAliveConfig
}
-type canSetKeepAlive interface {
- SetKeepAlivePeriod(d time.Duration) error
- SetKeepAlive(bool) error
+type canSetKeepAliveConfig interface {
+ SetKeepAliveConfig(config net.KeepAliveConfig) error
}
func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
@@ -140,12 +140,8 @@ func (fcl *fakeCloseListener) Accept() (net.Conn, error) {
if err == nil {
// if 0, do nothing, Go's default is already set
// and if the connection allows setting KeepAlive, set it
- if tconn, ok := conn.(canSetKeepAlive); ok && fcl.keepAlivePeriod != 0 {
- if fcl.keepAlivePeriod > 0 {
- err = tconn.SetKeepAlivePeriod(fcl.keepAlivePeriod)
- } else { // negative
- err = tconn.SetKeepAlive(false)
- }
+ if tconn, ok := conn.(canSetKeepAliveConfig); ok && fcl.keepAliveConfig.Enable {
+ err = tconn.SetKeepAliveConfig(fcl.keepAliveConfig)
if err != nil {
Log().With(zap.String("server", fcl.sharedListener.key)).Warn("unable to set keepalive for new connection:", zap.Error(err))
}
@@ -265,14 +261,14 @@ func (fcpc *fakeClosePacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err e
if atomic.LoadInt32(&fcpc.closed) == 1 {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
if err = fcpc.SetReadDeadline(time.Time{}); err != nil {
- return
+ return n, addr, err
}
}
}
- return
+ return n, addr, err
}
- return
+ return n, addr, err
}
// Close won't close the underlying socket unless there is no more reference, then listenerPool will close it.
diff --git a/listeners.go b/listeners.go
index b22df77ba..8a862bacf 100644
--- a/listeners.go
+++ b/listeners.go
@@ -210,7 +210,7 @@ func (na NetworkAddress) IsUnixNetwork() bool {
return IsUnixNetwork(na.Network)
}
-// IsUnixNetwork returns true if na.Network is
+// IsFdNetwork returns true if na.Network is
// fd or fdgram.
func (na NetworkAddress) IsFdNetwork() bool {
return IsFdNetwork(na.Network)
@@ -382,7 +382,7 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
a = afterSlash
if IsUnixNetwork(network) || IsFdNetwork(network) {
host = a
- return
+ return network, host, port, err
}
}
@@ -402,7 +402,7 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
err = errors.Join(firstErr, err)
}
- return
+ return network, host, port, err
}
// JoinNetworkAddress combines network, host, and port into a single
@@ -430,7 +430,8 @@ func JoinNetworkAddress(network, host, port string) string {
// address instead.
//
// 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) {
+// NOTE: user should close the returned listener twice, once to stop accepting new connections, the second time to free up the packet conn.
+func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICListener, error) {
lnKey := listenerKey("quic"+na.Network, na.JoinHostPort(portOffset))
sharedEarlyListener, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
@@ -610,7 +611,7 @@ type fakeCloseQuicListener struct {
// server on which Accept would be called with non-empty contexts
// (mind that the default net listeners' Accept doesn't take a context argument)
// sounds way too rare for us to sacrifice efficiency here.
-func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlyConnection, error) {
+func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (*quic.Conn, error) {
conn, err := fcql.sharedQuicListener.Accept(fcql.context)
if err == nil {
return conn, nil
@@ -626,6 +627,7 @@ func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlyConnecti
func (fcql *fakeCloseQuicListener) Close() error {
if atomic.CompareAndSwapInt32(&fcql.closed, 0, 1) {
fcql.contextCancel()
+ } else if atomic.CompareAndSwapInt32(&fcql.closed, 1, 2) {
_, _ = listenerPool.Delete(fcql.sharedQuicListener.key)
}
return nil
@@ -641,7 +643,7 @@ func RegisterNetwork(network string, getListener ListenerFunc) {
if network == "tcp" || network == "tcp4" || network == "tcp6" ||
network == "udp" || network == "udp4" || network == "udp6" ||
network == "unix" || network == "unixpacket" || network == "unixgram" ||
- strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) ||
+ strings.HasPrefix(network, "ip:") || strings.HasPrefix(network, "ip4:") || strings.HasPrefix(network, "ip6:") ||
network == "fd" || network == "fdgram" {
panic("network type " + network + " is reserved")
}
diff --git a/listeners_test.go b/listeners_test.go
index 03945308e..a4cadd3aa 100644
--- a/listeners_test.go
+++ b/listeners_test.go
@@ -30,7 +30,7 @@ func TestSplitNetworkAddress(t *testing.T) {
expectErr bool
}{
{
- input: "",
+ input: "",
expectHost: "",
},
{
@@ -41,7 +41,7 @@ func TestSplitNetworkAddress(t *testing.T) {
input: ":", // empty host & empty port
},
{
- input: "::",
+ input: "::",
expectHost: "::",
},
{
@@ -184,9 +184,8 @@ func TestParseNetworkAddress(t *testing.T) {
expectErr bool
}{
{
- input: "",
- expectAddr: NetworkAddress{
- },
+ input: "",
+ expectAddr: NetworkAddress{},
},
{
input: ":",
@@ -311,9 +310,8 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
expectErr bool
}{
{
- input: "",
- expectAddr: NetworkAddress{
- },
+ input: "",
+ expectAddr: NetworkAddress{},
},
{
input: ":",
diff --git a/logging.go b/logging.go
index ca10beeed..2734b5425 100644
--- a/logging.go
+++ b/logging.go
@@ -20,6 +20,7 @@ import (
"io"
"log"
"os"
+ "slices"
"strings"
"sync"
"time"
@@ -27,6 +28,8 @@ import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/term"
+
+ "github.com/caddyserver/caddy/v2/internal"
)
func init() {
@@ -161,7 +164,9 @@ func (logging *Logging) setupNewDefault(ctx Context) error {
if err != nil {
return fmt.Errorf("setting up default log: %v", err)
}
- newDefault.logger = zap.New(newDefault.CustomLog.core, options...)
+
+ filteringCore := &filteringCore{newDefault.CustomLog.core, newDefault.CustomLog}
+ newDefault.logger = zap.New(filteringCore, options...)
// redirect the default caddy logs
defaultLoggerMu.Lock()
@@ -187,6 +192,13 @@ func (logging *Logging) setupNewDefault(ctx Context) error {
)
}
+ // if we had a buffered core, flush its contents ASAP
+ // before we try to log anything else, so the order of
+ // logs is preserved
+ if oldBufferCore, ok := oldDefault.logger.Core().(*internal.LogBufferCore); ok {
+ oldBufferCore.FlushTo(newDefault.logger)
+ }
+
return nil
}
@@ -490,10 +502,8 @@ func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
if len(cl.Include) > 0 && len(cl.Exclude) > 0 {
// prevent intersections
for _, allow := range cl.Include {
- for _, deny := range cl.Exclude {
- if allow == deny {
- return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow)
- }
+ if slices.Contains(cl.Exclude, allow) {
+ return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow)
}
}
@@ -772,6 +782,21 @@ func Log() *zap.Logger {
return defaultLogger.logger
}
+// BufferedLog sets the default logger to one that buffers
+// logs before a config is loaded.
+// Returns the buffered logger, the original default logger
+// (for flushing on errors), and the buffer core so that the
+// caller can flush the logs after the config is loaded or
+// fails to load.
+func BufferedLog() (*zap.Logger, *zap.Logger, *internal.LogBufferCore) {
+ defaultLoggerMu.Lock()
+ defer defaultLoggerMu.Unlock()
+ origLogger := defaultLogger.logger
+ bufferCore := internal.NewLogBufferCore(zap.InfoLevel)
+ defaultLogger.logger = zap.New(bufferCore)
+ return defaultLogger.logger, origLogger, bufferCore
+}
+
var (
coloringEnabled = os.Getenv("NO_COLOR") == "" && os.Getenv("TERM") != "xterm-mono"
defaultLogger, _ = newDefaultProductionLog()
diff --git a/logging_test.go b/logging_test.go
new file mode 100644
index 000000000..293591fbb
--- /dev/null
+++ b/logging_test.go
@@ -0,0 +1,106 @@
+// 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 "testing"
+
+func TestCustomLog_loggerAllowed(t *testing.T) {
+ type fields struct {
+ BaseLog BaseLog
+ Include []string
+ Exclude []string
+ }
+ type args struct {
+ name string
+ isModule bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want bool
+ }{
+ {
+ name: "include",
+ fields: fields{
+ Include: []string{"foo"},
+ },
+ args: args{
+ name: "foo",
+ isModule: true,
+ },
+ want: true,
+ },
+ {
+ name: "exclude",
+ fields: fields{
+ Exclude: []string{"foo"},
+ },
+ args: args{
+ name: "foo",
+ isModule: true,
+ },
+ want: false,
+ },
+ {
+ name: "include and exclude",
+ fields: fields{
+ Include: []string{"foo"},
+ Exclude: []string{"foo"},
+ },
+ args: args{
+ name: "foo",
+ isModule: true,
+ },
+ want: false,
+ },
+ {
+ name: "include and exclude (longer namespace)",
+ fields: fields{
+ Include: []string{"foo.bar"},
+ Exclude: []string{"foo"},
+ },
+ args: args{
+ name: "foo.bar",
+ isModule: true,
+ },
+ want: true,
+ },
+ {
+ name: "excluded module is not printed",
+ fields: fields{
+ Include: []string{"admin.api.load"},
+ Exclude: []string{"admin.api"},
+ },
+ args: args{
+ name: "admin.api",
+ isModule: false,
+ },
+ want: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ cl := &CustomLog{
+ BaseLog: tt.fields.BaseLog,
+ Include: tt.fields.Include,
+ Exclude: tt.fields.Exclude,
+ }
+ if got := cl.loggerAllowed(tt.args.name, tt.args.isModule); got != tt.want {
+ t.Errorf("CustomLog.loggerAllowed() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/modules.go b/modules.go
index 470c25e37..24c452589 100644
--- a/modules.go
+++ b/modules.go
@@ -18,6 +18,8 @@ import (
"bytes"
"encoding/json"
"fmt"
+ "net/http"
+ "net/url"
"reflect"
"sort"
"strings"
@@ -343,9 +345,11 @@ func StrictUnmarshalJSON(data []byte, v any) error {
return dec.Decode(v)
}
+var JSONRawMessageType = reflect.TypeFor[json.RawMessage]()
+
// isJSONRawMessage returns true if the type is encoding/json.RawMessage.
func isJSONRawMessage(typ reflect.Type) bool {
- return typ.PkgPath() == "encoding/json" && typ.Name() == "RawMessage"
+ return typ == JSONRawMessageType
}
// isModuleMapType returns true if the type is map[string]json.RawMessage.
@@ -360,6 +364,14 @@ func isModuleMapType(typ reflect.Type) bool {
isJSONRawMessage(typ.Elem())
}
+// ProxyFuncProducer is implemented by modules which produce a
+// function that returns a URL to use as network proxy. Modules
+// in the namespace `caddy.network_proxy` must implement this
+// interface.
+type ProxyFuncProducer interface {
+ ProxyFunc() func(*http.Request) (*url.URL, error)
+}
+
var (
modules = make(map[string]ModuleInfo)
modulesMu sync.RWMutex
diff --git a/modules/caddyevents/app.go b/modules/caddyevents/app.go
index e78b00f8c..6c2abbf7c 100644
--- a/modules/caddyevents/app.go
+++ b/modules/caddyevents/app.go
@@ -20,9 +20,7 @@ import (
"errors"
"fmt"
"strings"
- "time"
- "github.com/google/uuid"
"go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
@@ -206,27 +204,26 @@ func (app *App) On(eventName string, handler Handler) error {
//
// Note that the data map is not copied, for efficiency. After Emit() is called, the
// data passed in should not be changed in other goroutines.
-func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) Event {
+func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) caddy.Event {
logger := app.logger.With(zap.String("name", eventName))
- id, err := uuid.NewRandom()
+ e, err := caddy.NewEvent(ctx, eventName, data)
if err != nil {
- logger.Error("failed generating new event ID", zap.Error(err))
+ logger.Error("failed to create event", zap.Error(err))
}
- eventName = strings.ToLower(eventName)
-
- e := Event{
- Data: data,
- id: id,
- ts: time.Now(),
- name: eventName,
- origin: ctx.Module(),
+ var originModule caddy.ModuleInfo
+ var originModuleID caddy.ModuleID
+ var originModuleName string
+ if origin := e.Origin(); origin != nil {
+ originModule = origin.CaddyModule()
+ originModuleID = originModule.ID
+ originModuleName = originModule.String()
}
logger = logger.With(
- zap.String("id", e.id.String()),
- zap.String("origin", e.origin.CaddyModule().String()))
+ zap.String("id", e.ID().String()),
+ zap.String("origin", originModuleName))
// add event info to replacer, make sure it's in the context
repl, ok := ctx.Context.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
@@ -239,21 +236,21 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
case "event":
return e, true
case "event.id":
- return e.id, true
+ return e.ID(), true
case "event.name":
- return e.name, true
+ return e.Name(), true
case "event.time":
- return e.ts, true
+ return e.Timestamp(), true
case "event.time_unix":
- return e.ts.UnixMilli(), true
+ return e.Timestamp().UnixMilli(), true
case "event.module":
- return e.origin.CaddyModule().ID, true
+ return originModuleID, true
case "event.data":
return e.Data, true
}
- if strings.HasPrefix(key, "event.data.") {
- key = strings.TrimPrefix(key, "event.data.")
+ if after, ok0 := strings.CutPrefix(key, "event.data."); ok0 {
+ key = after
if val, ok := e.Data[key]; ok {
return val, true
}
@@ -269,7 +266,7 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
// invoke handlers bound to the event by name and also all events; this for loop
// iterates twice at most: once for the event name, once for "" (all events)
for {
- moduleID := e.origin.CaddyModule().ID
+ moduleID := originModuleID
// implement propagation up the module tree (i.e. start with "a.b.c" then "a.b" then "a" then "")
for {
@@ -292,7 +289,7 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
zap.Any("handler", handler))
if err := handler.Handle(ctx, e); err != nil {
- aborted := errors.Is(err, ErrAborted)
+ aborted := errors.Is(err, caddy.ErrEventAborted)
logger.Error("handler error",
zap.Error(err),
@@ -326,76 +323,9 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
return e
}
-// 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: As with the rest of this package, 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 map[string]any
-
- id uuid.UUID
- ts time.Time
- name string
- origin caddy.Module
-}
-
-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() caddy.Module { return e.origin }
-
-// 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.
-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"`
-}
-
-// ErrAborted cancels an event.
-var ErrAborted = errors.New("event aborted")
-
// Handler is a type that can handle events.
type Handler interface {
- Handle(context.Context, Event) error
+ Handle(context.Context, caddy.Event) error
}
// Interface guards
diff --git a/modules/caddyfs/filesystem.go b/modules/caddyfs/filesystem.go
index b2fdcf7a2..2ec43079a 100644
--- a/modules/caddyfs/filesystem.go
+++ b/modules/caddyfs/filesystem.go
@@ -69,11 +69,11 @@ func (xs *Filesystems) Provision(ctx caddy.Context) error {
}
// register that module
ctx.Logger().Debug("registering fs", zap.String("fs", f.Key))
- ctx.Filesystems().Register(f.Key, f.fileSystem)
+ ctx.FileSystems().Register(f.Key, f.fileSystem)
// remember to unregister the module when we are done
xs.defers = append(xs.defers, func() {
ctx.Logger().Debug("unregistering fs", zap.String("fs", f.Key))
- ctx.Filesystems().Unregister(f.Key)
+ ctx.FileSystems().Unregister(f.Key)
})
}
return nil
diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go
index cbd168d31..7611285f7 100644
--- a/modules/caddyhttp/app.go
+++ b/modules/caddyhttp/app.go
@@ -28,7 +28,6 @@ import (
"go.uber.org/zap"
"golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyevents"
@@ -73,7 +72,7 @@ func init() {
// `{http.request.local.host}` | The host (IP) part of the local address the connection arrived on
// `{http.request.local.port}` | The port part of the local address the connection arrived on
// `{http.request.local}` | The local address the connection arrived on
-// `{http.request.remote.host}` | The host (IP) part of the remote client's address
+// `{http.request.remote.host}` | The host (IP) part of the remote client's address, if available (not known with HTTP/3 early data)
// `{http.request.remote.port}` | The port part of the remote client's address
// `{http.request.remote}` | The address of the remote client
// `{http.request.scheme}` | The request scheme, typically `http` or `https`
@@ -151,8 +150,13 @@ type App struct {
logger *zap.Logger
tlsApp *caddytls.TLS
+ // stopped indicates whether the app has stopped
+ // It can only happen if it has started successfully in the first place.
+ // Otherwise, Cleanup will call Stop to clean up resources.
+ stopped bool
+
// used temporarily between phases 1 and 2 of auto HTTPS
- allCertDomains []string
+ allCertDomains map[string]struct{}
}
// CaddyModule returns the Caddy module information.
@@ -166,13 +170,15 @@ func (App) CaddyModule() caddy.ModuleInfo {
// Provision sets up the app.
func (app *App) Provision(ctx caddy.Context) error {
// store some references
+ app.logger = ctx.Logger()
+ app.ctx = ctx
+
+ // provision TLS and events apps
tlsAppIface, err := ctx.App("tls")
if err != nil {
return fmt.Errorf("getting tls app: %v", err)
}
app.tlsApp = tlsAppIface.(*caddytls.TLS)
- app.ctx = ctx
- app.logger = ctx.Logger()
eventsAppIface, err := ctx.App("events")
if err != nil {
@@ -231,15 +237,6 @@ func (app *App) Provision(ctx caddy.Context) error {
for _, srvProtocol := range srv.Protocols {
srvProtocolsUnique[srvProtocol] = struct{}{}
}
- _, h1ok := srvProtocolsUnique["h1"]
- _, h2ok := srvProtocolsUnique["h2"]
- _, h2cok := srvProtocolsUnique["h2c"]
-
- // the Go standard library does not let us serve only HTTP/2 using
- // http.Server; we would probably need to write our own server
- if !h1ok && (h2ok || h2cok) {
- return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
- }
if srv.ListenProtocols != nil {
if len(srv.ListenProtocols) != len(srv.Listen) {
@@ -273,19 +270,6 @@ func (app *App) Provision(ctx caddy.Context) error {
}
}
- lnProtocolsIncludeUnique := map[string]struct{}{}
- for _, lnProtocol := range lnProtocolsInclude {
- lnProtocolsIncludeUnique[lnProtocol] = struct{}{}
- }
- _, h1ok := lnProtocolsIncludeUnique["h1"]
- _, h2ok := lnProtocolsIncludeUnique["h2"]
- _, h2cok := lnProtocolsIncludeUnique["h2c"]
-
- // check if any listener protocols contain h2 or h2c without h1
- if !h1ok && (h2ok || h2cok) {
- return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i)
- }
-
srv.ListenProtocols[i] = lnProtocolsInclude
}
}
@@ -443,6 +427,25 @@ func (app *App) Validate() error {
return nil
}
+func removeTLSALPN(srv *Server, target string) {
+ for _, cp := range srv.TLSConnPolicies {
+ // the TLSConfig was already provisioned, so... manually remove it
+ for i, np := range cp.TLSConfig.NextProtos {
+ if np == target {
+ cp.TLSConfig.NextProtos = append(cp.TLSConfig.NextProtos[:i], cp.TLSConfig.NextProtos[i+1:]...)
+ break
+ }
+ }
+ // remove it from the parent connection policy too, just to keep things tidy
+ for i, alpn := range cp.ALPN {
+ if alpn == target {
+ cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...)
+ break
+ }
+ }
+ }
+}
+
// Start runs the app. It finishes automatic HTTPS if enabled,
// including management of certificates.
func (app *App) Start() error {
@@ -461,32 +464,44 @@ func (app *App) Start() error {
MaxHeaderBytes: srv.MaxHeaderBytes,
Handler: srv,
ErrorLog: serverLogger,
+ Protocols: new(http.Protocols),
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
- return context.WithValue(ctx, ConnCtxKey, c)
+ if nc, ok := c.(interface{ tlsNetConn() net.Conn }); ok {
+ getTlsConStateFunc := sync.OnceValue(func() *tls.ConnectionState {
+ tlsConnState := nc.tlsNetConn().(connectionStater).ConnectionState()
+ return &tlsConnState
+ })
+ ctx = context.WithValue(ctx, tlsConnectionStateFuncCtxKey, getTlsConStateFunc)
+ }
+ return ctx
},
}
- h2server := new(http2.Server)
// disable HTTP/2, which we enabled by default during provisioning
if !srv.protocol("h2") {
srv.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
- for _, cp := range srv.TLSConnPolicies {
- // the TLSConfig was already provisioned, so... manually remove it
- for i, np := range cp.TLSConfig.NextProtos {
- if np == "h2" {
- cp.TLSConfig.NextProtos = append(cp.TLSConfig.NextProtos[:i], cp.TLSConfig.NextProtos[i+1:]...)
- break
- }
- }
- // remove it from the parent connection policy too, just to keep things tidy
- for i, alpn := range cp.ALPN {
- if alpn == "h2" {
- cp.ALPN = append(cp.ALPN[:i], cp.ALPN[i+1:]...)
- break
- }
- }
- }
- } else {
+ removeTLSALPN(srv, "h2")
+ }
+ if !srv.protocol("h1") {
+ removeTLSALPN(srv, "http/1.1")
+ }
+
+ // configure the http versions the server will serve
+ if srv.protocol("h1") {
+ srv.server.Protocols.SetHTTP1(true)
+ }
+
+ if srv.protocol("h2") || srv.protocol("h2c") {
+ // skip setting h2 because if NextProtos is present, it's list of alpn versions will take precedence.
+ // it will always be present because http2.ConfigureServer will populate that field
+ // enabling h2c because some listener wrapper will wrap the connection that is no longer *tls.Conn
+ // However, we need to handle the case that if the connection is h2c but h2c is not enabled. We identify
+ // this type of connection by checking if it's behind a TLS listener wrapper or if it implements tls.ConnectionState.
+ srv.server.Protocols.SetUnencryptedHTTP2(true)
+ // when h2c is enabled but h2 disabled, we already removed h2 from NextProtos
+ // the handshake will never succeed with h2
+ // http2.ConfigureServer will enable the server to handle both h2 and h2c
+ h2server := new(http2.Server)
//nolint:errcheck
http2.ConfigureServer(srv.server, h2server)
}
@@ -496,11 +511,6 @@ func (app *App) Start() error {
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
srv.configureServer(srv.server)
- // enable H2C if configured
- if srv.protocol("h2c") {
- srv.server.Handler = h2c.NewHandler(srv, h2server)
- }
-
for lnIndex, lnAddr := range srv.Listen {
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
if err != nil {
@@ -531,7 +541,14 @@ func (app *App) Start() error {
if h1ok || h2ok && useTLS || h2cok {
// create the listener for this socket
- lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
+ lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{
+ KeepAliveConfig: net.KeepAliveConfig{
+ Enable: srv.KeepAliveInterval >= 0,
+ Interval: time.Duration(srv.KeepAliveInterval),
+ Idle: time.Duration(srv.KeepAliveIdle),
+ Count: srv.KeepAliveCount,
+ },
+ })
if err != nil {
return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
}
@@ -560,15 +577,13 @@ func (app *App) Start() error {
ln = srv.listenerWrappers[i].WrapListener(ln)
}
- // handle http2 if use tls listener wrapper
- if h2ok {
- http2lnWrapper := &http2Listener{
- Listener: ln,
- server: srv.server,
- h2server: h2server,
- }
- srv.h2listeners = append(srv.h2listeners, http2lnWrapper)
- ln = http2lnWrapper
+ // check if the connection is h2c
+ ln = &http2Listener{
+ useTLS: useTLS,
+ useH1: h1ok,
+ useH2: h2ok || h2cok,
+ Listener: ln,
+ logger: app.logger,
}
// if binding to port 0, the OS chooses a port for us;
@@ -586,11 +601,8 @@ func (app *App) Start() error {
srv.listeners = append(srv.listeners, ln)
- // enable HTTP/1 if configured
- if h1ok {
- //nolint:errcheck
- go srv.server.Serve(ln)
- }
+ //nolint:errcheck
+ go srv.server.Serve(ln)
}
if h2ok && !useTLS {
@@ -703,6 +715,11 @@ func (app *App) Stop() error {
defer finishedShutdown.Done()
startedShutdown.Done()
+ // possible if server failed to Start
+ if server.server == nil {
+ return
+ }
+
if err := server.server.Shutdown(ctx); err != nil {
app.logger.Error("server shutdown",
zap.Error(err),
@@ -717,31 +734,36 @@ func (app *App) Stop() error {
return
}
+ // closing quic listeners won't affect accepted connections now
+ // so like stdlib, close listeners first, but keep the net.PacketConns open
+ for _, h3ln := range server.quicListeners {
+ if err := h3ln.Close(); err != nil {
+ app.logger.Error("http3 listener close",
+ zap.Error(err))
+ }
+ }
+
if err := server.h3server.Shutdown(ctx); err != nil {
app.logger.Error("HTTP/3 server shutdown",
zap.Error(err),
zap.Strings("addresses", server.Listen))
}
- }
- stopH2Listener := func(server *Server) {
- defer finishedShutdown.Done()
- startedShutdown.Done()
- for i, s := range server.h2listeners {
- if err := s.Shutdown(ctx); err != nil {
- app.logger.Error("http2 listener shutdown",
- zap.Error(err),
- zap.Int("index", i))
+ // close the underlying net.PacketConns now
+ // see the comment for ListenQUIC
+ for _, h3ln := range server.quicListeners {
+ if err := h3ln.Close(); err != nil {
+ app.logger.Error("http3 listener close socket",
+ zap.Error(err))
}
}
}
for _, server := range app.Servers {
- startedShutdown.Add(3)
- finishedShutdown.Add(3)
+ startedShutdown.Add(2)
+ finishedShutdown.Add(2)
go stopServer(server)
go stopH3Server(server)
- go stopH2Listener(server)
}
// block until all the goroutines have been run by the scheduler;
@@ -768,9 +790,20 @@ func (app *App) Stop() error {
}
}
+ app.stopped = true
return nil
}
+// Cleanup will close remaining listeners if they still remain
+// because some of the servers fail to start.
+// It simply calls Stop because Stop won't be called when Start fails.
+func (app *App) Cleanup() error {
+ if app.stopped {
+ return nil
+ }
+ return app.Stop()
+}
+
func (app *App) httpPort() int {
if app.HTTPPort == 0 {
return DefaultHTTPPort
diff --git a/modules/caddyhttp/autohttps.go b/modules/caddyhttp/autohttps.go
index dce21a721..05f8a7517 100644
--- a/modules/caddyhttp/autohttps.go
+++ b/modules/caddyhttp/autohttps.go
@@ -25,6 +25,7 @@ import (
"go.uber.org/zap"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/internal"
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
@@ -65,12 +66,6 @@ type AutoHTTPSConfig struct {
// enabled. To force automated certificate management
// regardless of loaded certificates, set this to true.
IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
-
- // If true, automatic HTTPS will prefer wildcard names
- // and ignore non-wildcard names if both are available.
- // This allows for writing a config with top-level host
- // matchers without having those names produce certificates.
- PreferWildcard bool `json:"prefer_wildcard,omitempty"`
}
// automaticHTTPSPhase1 provisions all route matchers, determines
@@ -163,26 +158,13 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
}
}
- if srv.AutoHTTPS.PreferWildcard {
- wildcards := make(map[string]struct{})
- for d := range serverDomainSet {
- if strings.HasPrefix(d, "*.") {
- wildcards[d[2:]] = struct{}{}
- }
- }
- for d := range serverDomainSet {
- if strings.HasPrefix(d, "*.") {
- continue
- }
- base := d
- if idx := strings.Index(d, "."); idx != -1 {
- base = d[idx+1:]
- }
- if _, ok := wildcards[base]; ok {
- delete(serverDomainSet, d)
- }
- }
+ // build the list of domains that could be used with ECH (if enabled)
+ // so the TLS app can know to publish ECH configs for them
+ echDomains := make([]string, 0, len(serverDomainSet))
+ for d := range serverDomainSet {
+ echDomains = append(echDomains, d)
}
+ app.tlsApp.RegisterServerNames(echDomains)
// nothing more to do here if there are no domains that qualify for
// automatic HTTPS and there are no explicit TLS connection policies:
@@ -205,7 +187,6 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
// for all the hostnames we found, filter them so we have
// a deduplicated list of names for which to obtain certs
// (only if cert management not disabled for this server)
- var echDomains []string
if srv.AutoHTTPS.DisableCerts {
logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
} else {
@@ -232,14 +213,10 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
}
uniqueDomainsForCerts[d] = struct{}{}
- echDomains = append(echDomains, d)
}
}
}
- // let the TLS server know we have some hostnames that could be protected behind ECH
- app.tlsApp.RegisterServerNames(echDomains)
-
// tell the server to use TLS if it is not already doing so
if srv.TLSConnPolicies == nil {
srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)}
@@ -288,19 +265,26 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
}
}
- // we now have a list of all the unique names for which we need certs;
- // turn the set into a slice so that phase 2 can use it
- app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
+ // if all servers have auto_https disabled and no domains need certs,
+ // skip the rest of the TLS automation setup to avoid creating
+ // unnecessary PKI infrastructure and automation policies
+ allServersDisabled := true
+ for _, srv := range app.Servers {
+ if srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled {
+ allServersDisabled = false
+ break
+ }
+ }
+
+ if allServersDisabled && len(uniqueDomainsForCerts) == 0 {
+ logger.Debug("all servers have automatic HTTPS disabled and no domains need certificates, skipping TLS automation setup")
+ return nil
+ }
+
+ // we now have a list of all the unique names for which we need certs
var internal, tailscale []string
uniqueDomainsLoop:
for d := range uniqueDomainsForCerts {
- if !isTailscaleDomain(d) {
- // whether or not there is already an automation policy for this
- // name, we should add it to the list to manage a cert for it,
- // unless it's a Tailscale domain, because we don't manage those
- app.allCertDomains = append(app.allCertDomains, d)
- }
-
// some names we've found might already have automation policies
// explicitly specified for them; we should exclude those from
// our hidden/implicit policy, since applying a name to more than
@@ -339,6 +323,7 @@ uniqueDomainsLoop:
}
if isTailscaleDomain(d) {
tailscale = append(tailscale, d)
+ delete(uniqueDomainsForCerts, d) // not managed by us; handled separately
} else if shouldUseInternal(d) {
internal = append(internal, d)
}
@@ -374,7 +359,7 @@ uniqueDomainsLoop:
// match on known domain names, unless it's our special case of a
// catch-all which is an empty string (common among catch-all sites
// that enable on-demand TLS for yet-unknown domain names)
- if !(len(domains) == 1 && domains[0] == "") {
+ if len(domains) != 1 || domains[0] != "" {
matcherSet = append(matcherSet, MatchHost(domains))
}
@@ -468,6 +453,9 @@ redirServersLoop:
}
}
+ // persist the domains/IPs we're managing certs for through provisioning/startup
+ app.allCertDomains = uniqueDomainsForCerts
+
logger.Debug("adjusted config",
zap.Reflect("tls", app.tlsApp),
zap.Reflect("http", app))
@@ -770,7 +758,7 @@ func (app *App) automaticHTTPSPhase2() error {
return nil
}
app.logger.Info("enabling automatic TLS certificate management",
- zap.Strings("domains", app.allCertDomains),
+ zap.Strings("domains", internal.MaxSizeSubjectsListForLog(app.allCertDomains, 1000)),
)
err := app.tlsApp.Manage(app.allCertDomains)
if err != nil {
diff --git a/modules/caddyhttp/caddyauth/argon2id.go b/modules/caddyhttp/caddyauth/argon2id.go
new file mode 100644
index 000000000..f1070ce48
--- /dev/null
+++ b/modules/caddyhttp/caddyauth/argon2id.go
@@ -0,0 +1,188 @@
+// 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 caddyauth
+
+import (
+ "crypto/rand"
+ "crypto/subtle"
+ "encoding/base64"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "golang.org/x/crypto/argon2"
+
+ "github.com/caddyserver/caddy/v2"
+)
+
+func init() {
+ caddy.RegisterModule(Argon2idHash{})
+}
+
+const (
+ argon2idName = "argon2id"
+ defaultArgon2idTime = 1
+ defaultArgon2idMemory = 46 * 1024
+ defaultArgon2idThreads = 1
+ defaultArgon2idKeylen = 32
+ defaultSaltLength = 16
+)
+
+// Argon2idHash implements the Argon2id password hashing.
+type Argon2idHash struct {
+ salt []byte
+ time uint32
+ memory uint32
+ threads uint8
+ keyLen uint32
+}
+
+// CaddyModule returns the Caddy module information.
+func (Argon2idHash) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "http.authentication.hashes.argon2id",
+ New: func() caddy.Module { return new(Argon2idHash) },
+ }
+}
+
+// Compare checks if the plaintext password matches the given Argon2id hash.
+func (Argon2idHash) Compare(hashed, plaintext []byte) (bool, error) {
+ argHash, storedKey, err := DecodeHash(hashed)
+ if err != nil {
+ return false, err
+ }
+
+ computedKey := argon2.IDKey(
+ plaintext,
+ argHash.salt,
+ argHash.time,
+ argHash.memory,
+ argHash.threads,
+ argHash.keyLen,
+ )
+
+ return subtle.ConstantTimeCompare(storedKey, computedKey) == 1, nil
+}
+
+// Hash generates an Argon2id hash of the given plaintext using the configured parameters and salt.
+func (b Argon2idHash) Hash(plaintext []byte) ([]byte, error) {
+ if b.salt == nil {
+ s, err := generateSalt(defaultSaltLength)
+ if err != nil {
+ return nil, err
+ }
+ b.salt = s
+ }
+
+ key := argon2.IDKey(
+ plaintext,
+ b.salt,
+ b.time,
+ b.memory,
+ b.threads,
+ b.keyLen,
+ )
+
+ hash := fmt.Sprintf(
+ "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
+ argon2.Version,
+ b.memory,
+ b.time,
+ b.threads,
+ base64.RawStdEncoding.EncodeToString(b.salt),
+ base64.RawStdEncoding.EncodeToString(key),
+ )
+
+ return []byte(hash), nil
+}
+
+// DecodeHash parses an Argon2id PHC string into an Argon2idHash struct and returns the struct along with the derived key.
+func DecodeHash(hash []byte) (*Argon2idHash, []byte, error) {
+ parts := strings.Split(string(hash), "$")
+ if len(parts) != 6 {
+ return nil, nil, fmt.Errorf("invalid hash format")
+ }
+
+ if parts[1] != argon2idName {
+ return nil, nil, fmt.Errorf("unsupported variant: %s", parts[1])
+ }
+
+ version, err := strconv.Atoi(strings.TrimPrefix(parts[2], "v="))
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid version: %w", err)
+ }
+ if version != argon2.Version {
+ return nil, nil, fmt.Errorf("incompatible version: %d", version)
+ }
+
+ params := strings.Split(parts[3], ",")
+ if len(params) != 3 {
+ return nil, nil, fmt.Errorf("invalid parameters")
+ }
+
+ mem, err := strconv.ParseUint(strings.TrimPrefix(params[0], "m="), 10, 32)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid memory parameter: %w", err)
+ }
+
+ iter, err := strconv.ParseUint(strings.TrimPrefix(params[1], "t="), 10, 32)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid iterations parameter: %w", err)
+ }
+
+ threads, err := strconv.ParseUint(strings.TrimPrefix(params[2], "p="), 10, 8)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid parallelism parameter: %w", err)
+ }
+
+ salt, err := base64.RawStdEncoding.Strict().DecodeString(parts[4])
+ if err != nil {
+ return nil, nil, fmt.Errorf("decode salt: %w", err)
+ }
+
+ key, err := base64.RawStdEncoding.Strict().DecodeString(parts[5])
+ if err != nil {
+ return nil, nil, fmt.Errorf("decode key: %w", err)
+ }
+
+ return &Argon2idHash{
+ salt: salt,
+ time: uint32(iter),
+ memory: uint32(mem),
+ threads: uint8(threads),
+ keyLen: uint32(len(key)),
+ }, key, nil
+}
+
+// FakeHash returns a constant fake hash for timing attacks mitigation.
+func (Argon2idHash) FakeHash() []byte {
+ // hashed with the following command:
+ // caddy hash-password --plaintext "antitiming" --algorithm "argon2id"
+ return []byte("$argon2id$v=19$m=47104,t=1,p=1$P2nzckEdTZ3bxCiBCkRTyA$xQL3Z32eo5jKl7u5tcIsnEKObYiyNZQQf5/4sAau6Pg")
+}
+
+// Interface guards
+var (
+ _ Comparer = (*Argon2idHash)(nil)
+ _ Hasher = (*Argon2idHash)(nil)
+)
+
+func generateSalt(length int) ([]byte, error) {
+ salt := make([]byte, length)
+ if _, err := rand.Read(salt); err != nil {
+ return nil, fmt.Errorf("failed to generate salt: %w", err)
+ }
+ return salt, nil
+}
diff --git a/modules/caddyhttp/caddyauth/basicauth.go b/modules/caddyhttp/caddyauth/basicauth.go
index 52a5a08c1..5a9e167e1 100644
--- a/modules/caddyhttp/caddyauth/basicauth.go
+++ b/modules/caddyhttp/caddyauth/basicauth.go
@@ -236,10 +236,7 @@ func (c *Cache) makeRoom() {
// the cache is on a long tail, we can save a lot of CPU
// time by doing a whole bunch of deletions now and then
// we won't have to do them again for a while
- numToDelete := len(c.cache) / 10
- if numToDelete < 1 {
- numToDelete = 1
- }
+ numToDelete := max(len(c.cache)/10, 1)
for deleted := 0; deleted <= numToDelete; deleted++ {
// Go maps are "nondeterministic" not actually random,
// so although we could just chop off the "front" of the
diff --git a/modules/caddyhttp/caddyauth/hashes.go b/modules/caddyhttp/caddyauth/bcrypt.go
similarity index 73%
rename from modules/caddyhttp/caddyauth/hashes.go
rename to modules/caddyhttp/caddyauth/bcrypt.go
index ce3df901e..f6940996e 100644
--- a/modules/caddyhttp/caddyauth/hashes.go
+++ b/modules/caddyhttp/caddyauth/bcrypt.go
@@ -15,6 +15,8 @@
package caddyauth
import (
+ "errors"
+
"golang.org/x/crypto/bcrypt"
"github.com/caddyserver/caddy/v2"
@@ -24,8 +26,18 @@ func init() {
caddy.RegisterModule(BcryptHash{})
}
+// defaultBcryptCost cost 14 strikes a solid balance between security, usability, and hardware performance
+const (
+ bcryptName = "bcrypt"
+ defaultBcryptCost = 14
+)
+
// BcryptHash implements the bcrypt hash.
-type BcryptHash struct{}
+type BcryptHash struct {
+ // cost is the bcrypt hashing difficulty factor (work factor).
+ // Higher values increase computation time and security.
+ cost int
+}
// CaddyModule returns the Caddy module information.
func (BcryptHash) CaddyModule() caddy.ModuleInfo {
@@ -38,7 +50,7 @@ func (BcryptHash) CaddyModule() caddy.ModuleInfo {
// Compare compares passwords.
func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
err := bcrypt.CompareHashAndPassword(hashed, plaintext)
- if err == bcrypt.ErrMismatchedHashAndPassword {
+ if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return false, nil
}
if err != nil {
@@ -48,8 +60,13 @@ func (BcryptHash) Compare(hashed, plaintext []byte) (bool, error) {
}
// Hash hashes plaintext using a random salt.
-func (BcryptHash) Hash(plaintext []byte) ([]byte, error) {
- return bcrypt.GenerateFromPassword(plaintext, 14)
+func (b BcryptHash) Hash(plaintext []byte) ([]byte, error) {
+ cost := b.cost
+ if cost < bcrypt.MinCost || cost > bcrypt.MaxCost {
+ cost = defaultBcryptCost
+ }
+
+ return bcrypt.GenerateFromPassword(plaintext, cost)
}
// FakeHash returns a fake hash.
diff --git a/modules/caddyhttp/caddyauth/caddyauth.go b/modules/caddyhttp/caddyauth/caddyauth.go
index f799d7a0c..792c198ee 100644
--- a/modules/caddyhttp/caddyauth/caddyauth.go
+++ b/modules/caddyhttp/caddyauth/caddyauth.go
@@ -37,6 +37,10 @@ func init() {
// `{http.auth.user.*}` placeholders may be set for any authentication
// modules that provide user metadata.
//
+// In case of an error, the placeholder `{http.auth..error}`
+// will be set to the error message returned by the authentication
+// provider.
+//
// Its API is still experimental and may be subject to change.
type Authentication struct {
// A set of authentication providers. If none are specified,
@@ -56,7 +60,8 @@ func (Authentication) CaddyModule() caddy.ModuleInfo {
}
}
-// Provision sets up a.
+// Provision sets up an Authentication module by initializing its logger,
+// loading and registering all configured authentication providers.
func (a *Authentication) Provision(ctx caddy.Context) error {
a.logger = ctx.Logger()
a.Providers = make(map[string]Authenticator)
@@ -71,6 +76,7 @@ func (a *Authentication) Provision(ctx caddy.Context) error {
}
func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
var user User
var authed bool
var err error
@@ -80,6 +86,9 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
if c := a.logger.Check(zapcore.ErrorLevel, "auth provider returned error"); c != nil {
c.Write(zap.String("provider", provName), zap.Error(err))
}
+ // Set the error from the authentication provider in a placeholder,
+ // so it can be used in the handle_errors directive.
+ repl.Set("http.auth."+provName+".error", err.Error())
continue
}
if authed {
@@ -90,7 +99,6 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated"))
}
- repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
repl.Set("http.auth.user.id", user.ID)
for k, v := range user.Metadata {
repl.Set("http.auth.user."+k, v)
diff --git a/modules/caddyhttp/caddyauth/caddyfile.go b/modules/caddyhttp/caddyauth/caddyfile.go
index cc92477e5..99a33aff5 100644
--- a/modules/caddyhttp/caddyauth/caddyfile.go
+++ b/modules/caddyhttp/caddyauth/caddyfile.go
@@ -51,7 +51,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
var hashName string
switch len(args) {
case 0:
- hashName = "bcrypt"
+ hashName = bcryptName
case 1:
hashName = args[0]
case 2:
@@ -62,8 +62,10 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
}
switch hashName {
- case "bcrypt":
+ case bcryptName:
cmp = BcryptHash{}
+ case argon2idName:
+ cmp = Argon2idHash{}
default:
return nil, h.Errf("unrecognized hash algorithm: %s", hashName)
}
diff --git a/modules/caddyhttp/caddyauth/command.go b/modules/caddyhttp/caddyauth/command.go
index c9f440060..e9c513005 100644
--- a/modules/caddyhttp/caddyauth/command.go
+++ b/modules/caddyhttp/caddyauth/command.go
@@ -32,21 +32,55 @@ import (
func init() {
caddycmd.RegisterCommand(caddycmd.Command{
Name: "hash-password",
- Usage: "[--plaintext ] [--algorithm ]",
+ Usage: "[--plaintext ] [--algorithm ] [--bcrypt-cost ] [--argon2id-time ] [--argon2id-memory ] [--argon2id-threads ] [--argon2id-keylen ]",
Short: "Hashes a password and writes base64",
Long: `
Convenient way to hash a plaintext password. The resulting
hash is written to stdout as a base64 string.
---plaintext, when omitted, will be read from stdin. If
-Caddy is attached to a controlling tty, the plaintext will
-not be echoed.
+--plaintext
+ The password to hash. If omitted, it will be read from stdin.
+ If Caddy is attached to a controlling TTY, the input will not be echoed.
---algorithm currently only supports 'bcrypt', and is the default.
+--algorithm
+ Selects the hashing algorithm. Valid options are:
+ * 'argon2id' (recommended for modern security)
+ * 'bcrypt' (legacy, slower, configurable cost)
+
+bcrypt-specific parameters:
+
+--bcrypt-cost
+ Sets the bcrypt hashing difficulty. Higher values increase security by
+ making the hash computation slower and more CPU-intensive.
+ Must be within the valid range [bcrypt.MinCost, bcrypt.MaxCost].
+ If omitted or invalid, the default cost is used.
+
+Argon2id-specific parameters:
+
+--argon2id-time
+ Number of iterations to perform. Increasing this makes
+ hashing slower and more resistant to brute-force attacks.
+
+--argon2id-memory
+ Amount of memory to use during hashing.
+ Larger values increase resistance to GPU/ASIC attacks.
+
+--argon2id-threads
+ Number of CPU threads to use. Increase for faster hashing
+ on multi-core systems.
+
+--argon2id-keylen
+ Length of the resulting hash in bytes. Longer keys increase
+ security but slightly increase storage size.
`,
CobraFunc: func(cmd *cobra.Command) {
cmd.Flags().StringP("plaintext", "p", "", "The plaintext password")
- cmd.Flags().StringP("algorithm", "a", "bcrypt", "Name of the hash algorithm")
+ cmd.Flags().StringP("algorithm", "a", bcryptName, "Name of the hash algorithm")
+ cmd.Flags().Int("bcrypt-cost", defaultBcryptCost, "Bcrypt hashing cost (only used with 'bcrypt' algorithm)")
+ cmd.Flags().Uint32("argon2id-time", defaultArgon2idTime, "Number of iterations for Argon2id hashing. Increasing this makes the hash slower and more resistant to brute-force attacks.")
+ cmd.Flags().Uint32("argon2id-memory", defaultArgon2idMemory, "Memory to use in KiB for Argon2id hashing. Larger values increase resistance to GPU/ASIC attacks.")
+ cmd.Flags().Uint8("argon2id-threads", defaultArgon2idThreads, "Number of CPU threads to use for Argon2id hashing. Increase for faster hashing on multi-core systems.")
+ cmd.Flags().Uint32("argon2id-keylen", defaultArgon2idKeylen, "Length of the resulting Argon2id hash in bytes. Longer hashes increase security but slightly increase storage size.")
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdHashPassword)
},
})
@@ -57,6 +91,7 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
algorithm := fs.String("algorithm")
plaintext := []byte(fs.String("plaintext"))
+ bcryptCost := fs.Int("bcrypt-cost")
if len(plaintext) == 0 {
fd := int(os.Stdin.Fd())
@@ -107,8 +142,34 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
var hash []byte
var hashString string
switch algorithm {
- case "bcrypt":
- hash, err = BcryptHash{}.Hash(plaintext)
+ case bcryptName:
+ hash, err = BcryptHash{cost: bcryptCost}.Hash(plaintext)
+ hashString = string(hash)
+ case argon2idName:
+ time, err := fs.GetUint32("argon2id-time")
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id time parameter: %w", err)
+ }
+ memory, err := fs.GetUint32("argon2id-memory")
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id memory parameter: %w", err)
+ }
+ threads, err := fs.GetUint8("argon2id-threads")
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id threads parameter: %w", err)
+ }
+ keyLen, err := fs.GetUint32("argon2id-keylen")
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to get argon2id keylen parameter: %w", err)
+ }
+
+ hash, _ = Argon2idHash{
+ time: time,
+ memory: memory,
+ threads: threads,
+ keyLen: keyLen,
+ }.Hash(plaintext)
+
hashString = string(hash)
default:
return caddy.ExitCodeFailedStartup, fmt.Errorf("unrecognized hash algorithm: %s", algorithm)
diff --git a/modules/caddyhttp/celmatcher_test.go b/modules/caddyhttp/celmatcher_test.go
index a7e91529c..1bd8e527e 100644
--- a/modules/caddyhttp/celmatcher_test.go
+++ b/modules/caddyhttp/celmatcher_test.go
@@ -535,7 +535,7 @@ func BenchmarkMatchExpressionMatch(b *testing.B) {
}
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
tc.expression.MatchWithError(req)
}
})
diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go
index bea86083a..e23d9109c 100644
--- a/modules/caddyhttp/encode/encode.go
+++ b/modules/caddyhttp/encode/encode.go
@@ -50,7 +50,7 @@ type Encode struct {
// Only encode responses that are at least this many bytes long.
MinLength int `json:"minimum_length,omitempty"`
- // Only encode responses that match against this ResponseMmatcher.
+ // Only encode responses that match against this ResponseMatcher.
// The default is a collection of text-based Content-Type headers.
Matcher *caddyhttp.ResponseMatcher `json:"match,omitempty"`
@@ -92,6 +92,7 @@ func (enc *Encode) Provision(ctx caddy.Context) error {
"application/font*",
"application/geo+json*",
"application/graphql+json*",
+ "application/graphql-response+json*",
"application/javascript*",
"application/json*",
"application/ld+json*",
@@ -176,7 +177,17 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
break
}
}
- return next.ServeHTTP(w, r)
+
+ err := next.ServeHTTP(w, r)
+ // If there was an error, disable encoding completely
+ // This prevents corruption when handle_errors processes the response
+ if err != nil {
+ if ew, ok := w.(*responseWriter); ok {
+ ew.disabled = true
+ }
+ }
+
+ return err
}
func (enc *Encode) addEncoding(e Encoding) error {
@@ -232,6 +243,7 @@ type responseWriter struct {
statusCode int
wroteHeader bool
isConnect bool
+ disabled bool // disable encoding (for error responses)
}
// WriteHeader stores the status to write when the time comes
@@ -424,7 +436,14 @@ func (rw *responseWriter) Unwrap() http.ResponseWriter {
// init should be called before we write a response, if rw.buf has contents.
func (rw *responseWriter) init() {
+ // Don't initialize encoder for error responses
+ // This prevents response corruption when handle_errors is used
+ if rw.disabled {
+ return
+ }
+
hdr := rw.Header()
+
if hdr.Get("Content-Encoding") == "" && isEncodeAllowed(hdr) &&
rw.config.Match(rw) {
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
@@ -452,8 +471,7 @@ func (rw *responseWriter) init() {
func hasVaryValue(hdr http.Header, target string) bool {
for _, vary := range hdr.Values("Vary") {
- vals := strings.Split(vary, ",")
- for _, val := range vals {
+ for val := range strings.SplitSeq(vary, ",") {
if strings.EqualFold(strings.TrimSpace(val), target) {
return true
}
@@ -478,7 +496,7 @@ func AcceptedEncodings(r *http.Request, preferredOrder []string) []string {
prefs := []encodingPreference{}
- for _, accepted := range strings.Split(acceptEncHeader, ",") {
+ for accepted := range strings.SplitSeq(acceptEncHeader, ",") {
parts := strings.Split(accepted, ";")
encName := strings.ToLower(strings.TrimSpace(parts[0]))
diff --git a/modules/caddyhttp/encode/encode_test.go b/modules/caddyhttp/encode/encode_test.go
index 83effa58c..818f76745 100644
--- a/modules/caddyhttp/encode/encode_test.go
+++ b/modules/caddyhttp/encode/encode_test.go
@@ -2,13 +2,14 @@ package encode
import (
"net/http"
+ "slices"
"sync"
"testing"
)
func BenchmarkOpenResponseWriter(b *testing.B) {
enc := new(Encode)
- for n := 0; n < b.N; n++ {
+ for b.Loop() {
enc.openResponseWriter("test", nil, false)
}
}
@@ -112,7 +113,7 @@ func TestPreferOrder(t *testing.T) {
}
enc.Prefer = test.prefer
result := AcceptedEncodings(r, enc.Prefer)
- if !sliceEqual(result, test.expected) {
+ if !slices.Equal(result, test.expected) {
t.Errorf("AcceptedEncodings() actual: %s expected: %s",
result,
test.expected)
@@ -121,18 +122,6 @@ func TestPreferOrder(t *testing.T) {
}
}
-func sliceEqual(a, b []string) bool {
- if len(a) != len(b) {
- return false
- }
- for i := range a {
- if a[i] != b[i] {
- return false
- }
- }
- return true
-}
-
func TestValidate(t *testing.T) {
type testCase struct {
name string
diff --git a/modules/caddyhttp/fileserver/browse.html b/modules/caddyhttp/fileserver/browse.html
index d2d698197..5d9dc7dbe 100644
--- a/modules/caddyhttp/fileserver/browse.html
+++ b/modules/caddyhttp/fileserver/browse.html
@@ -26,7 +26,7 @@
- {{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg"}}
+ {{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg" ".avif"}}
{{- if eq .Tpl.Layout "grid"}}
{{- else}}
@@ -802,7 +802,7 @@ footer {
{{.NumFiles}} file{{if ne 1 .NumFiles}}s{{end}}
- {{.HumanTotalFileSize}} total
+ {{.HumanTotalFileSize}} total
{{- if ne 0 .Limit}}
@@ -828,6 +828,96 @@ footer {
Grid
+ {{- if and (eq .Layout "grid") (eq .Sort "name") (ne .Order "asc")}}
+
+
+
+ {{- else if and (eq .Layout "grid") (eq .Sort "name") (ne .Order "desc")}}
+
+
+
+ {{- else if and (eq .Layout "grid")}}
+
+
+
+ {{- end}}
+ {{- if and (eq .Layout "grid") (eq .Sort "size") (ne .Order "asc")}}
+
+
+
+ {{- else if and (eq .Layout "grid") (eq .Sort "size") (ne .Order "desc")}}
+
+
+
+ {{- else if and (eq .Layout "grid")}}
+
+
+
+ {{- end}}
+ {{- if and (eq .Layout "grid") (eq .Sort "time") (ne .Order "asc")}}
+
+
+
+ {{- else if and (eq .Layout "grid") (eq .Sort "time") (ne .Order "desc")}}
+
+
+
+ {{- else if and (eq .Layout "grid")}}
+
+
+
+ {{- end}}