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/dependabot.yml b/.github/dependabot.yml index aeee0fd93..85a85f63a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,10 +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: / + + - package-ecosystem: "gomod" + directory: "/" + open-pull-requests-limit: 1 + groups: + all-updates: + patterns: + - "*" schedule: - interval: weekly + 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 ab4e1e4ca..fdc521367 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ on: - 2.* env: + GOFLAGS: '-tags=nobadger,nomysql,nopgx' # https://github.com/actions/setup-go/issues/491 GOTOOLCHAIN: local @@ -33,13 +34,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) @@ -67,15 +68,15 @@ jobs: actions: write # to allow uploading artifacts and cache steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version: ${{ matrix.GO_SEMVER }} check-latest: true @@ -113,7 +114,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 @@ -136,7 +137,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 @@ -268,13 +269,13 @@ jobs: 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@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Run Tests run: | set +e @@ -297,7 +298,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 @@ -327,27 +328,27 @@ jobs: 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@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 + - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: version: latest args: check - name: Install Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + 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@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 + - 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 fe6b44c68..8aa9eaf59 100644 --- a/.github/workflows/cross-build.yml +++ b/.github/workflows/cross-build.yml @@ -11,6 +11,8 @@ on: - 2.* env: + GOFLAGS: '-tags=nobadger,nomysql,nopgx' + CGO_ENABLED: '0' # https://github.com/actions/setup-go/issues/491 GOTOOLCHAIN: local @@ -34,13 +36,13 @@ 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: @@ -49,15 +51,15 @@ jobs: continue-on-error: true steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version: ${{ matrix.GO_SEMVER }} check-latest: true @@ -74,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 7531d6a8e..849188c64 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -45,14 +45,14 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: - go-version: '~1.24' + go-version: '~1.25' check-latest: true - name: golangci-lint @@ -73,14 +73,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - name: govulncheck uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 with: - go-version-input: '~1.24.1' + go-version-input: '~1.25.0' check-latest: true dependency-review: @@ -90,14 +90,14 @@ jobs: pull-requests: write steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - name: 'Checkout Repository' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: 'Dependency Review' - uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 + 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31c60df75..397df5ea2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,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 @@ -39,23 +39,23 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 - name: Install Go - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 + 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 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 @@ -109,11 +109,11 @@ jobs: git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1 - name: Install Cosign - uses: sigstore/cosign-installer@e9a05e6d32d7ed22b5656cd874ef31af58d05bfa # main + uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # main - name: Cosign version run: cosign version - name: Install Syft - uses: anchore/sbom-action/download-syft@9246b90769f852b3a8921f330c59e0b3f439d6e9 # main + uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # main - name: Syft version run: syft version - name: Install xcaddy @@ -122,7 +122,7 @@ jobs: xcaddy version # GoReleaser will take care of publishing those artifacts into the release - name: Run GoReleaser - uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 + 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 e33cd8a96..8afc5c35e 100644 --- a/.github/workflows/release_published.yml +++ b/.github/workflows/release_published.yml @@ -24,12 +24,12 @@ jobs: # See https://github.com/peter-evans/repository-dispatch - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - name: Trigger event on caddyserver/dist - uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 + uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0 with: token: ${{ secrets.REPO_DISPATCH_TOKEN }} repository: caddyserver/dist @@ -37,7 +37,7 @@ jobs: client-payload: '{"tag": "${{ github.event.release.tag_name }}"}' - name: Trigger event on caddyserver/caddy-docker - uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 + 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 index fad8621d2..bb49f935d 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -37,17 +37,17 @@ jobs: steps: - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 with: egress-policy: audit - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif @@ -72,7 +72,7 @@ jobs: # 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@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif @@ -81,6 +81,6 @@ jobs: # 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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5 with: sarif_file: results.sarif diff --git a/.golangci.yml b/.golangci.yml index 0f1082e72..4f4545054 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,6 +2,10 @@ version: "2" run: issues-exit-code: 1 tests: false + build-tags: + - nobadger + - nomysql + - nopgx output: formats: text: diff --git a/README.md b/README.md index e5d5a83d6..4c091f714 100644 --- a/README.md +++ b/README.md @@ -89,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 45bd574a1..6ccec41e7 100644 --- a/admin.go +++ b/admin.go @@ -946,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() } @@ -1029,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 c637f92a9..92dd43a5c 100644 --- a/admin_test.go +++ b/admin_test.go @@ -149,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() } @@ -207,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) } } diff --git a/caddy.go b/caddy.go index 012480d11..5f71d8e8b 100644 --- a/caddy.go +++ b/caddy.go @@ -81,7 +81,10 @@ type Config struct { // associated value. AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="` - apps map[string]App + 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 @@ -522,6 +525,7 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error) // 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 { @@ -971,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 { @@ -1055,7 +1059,7 @@ func Version() (simple, full string) { } } - return + return simple, full } // Event represents something that has happened or is happening. @@ -1193,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/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 0476a9b93..8a757ea58 100644 --- a/caddyconfig/caddyfile/formatter.go +++ b/caddyconfig/caddyfile/formatter.go @@ -224,7 +224,7 @@ func Format(input []byte) []byte { openBrace = false if beginningOfLine { indent() - } else if !openBraceSpace { + } else if !openBraceSpace || !unicode.IsSpace(last) { write(' ') } write('{') @@ -241,7 +241,7 @@ func Format(input []byte) []byte { case ch == '{': openBrace = true openBraceSpace = spacePrior && !beginningOfLine - if openBraceSpace { + if openBraceSpace && newLines == 0 { write(' ') } openBraceWritten = false diff --git a/caddyconfig/caddyfile/formatter_test.go b/caddyconfig/caddyfile/formatter_test.go index a64383c3c..29b910ff1 100644 --- a/caddyconfig/caddyfile/formatter_test.go +++ b/caddyconfig/caddyfile/formatter_test.go @@ -444,6 +444,21 @@ block2 { input: "block {respond \"All braces should remain: {{now | date `2006`}}\"}", expect: "block {respond \"All braces should remain: {{now | date `2006`}}\"}", }, + { + description: "No trailing space on line before env variable", + input: `{ + a + + {$ENV_VAR} +} +`, + expect: `{ + a + + {$ENV_VAR} +} +`, + }, } { // the formatter should output a trailing newline, // even if the tests aren't written to expect that diff --git a/caddyconfig/caddyfile/lexer.go b/caddyconfig/caddyfile/lexer.go index a3b1fc7db..60dabe43d 100644 --- a/caddyconfig/caddyfile/lexer.go +++ b/caddyconfig/caddyfile/lexer.go @@ -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 fb05d29f1..061aaa48b 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -91,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 // } @@ -130,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: @@ -350,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": @@ -371,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": @@ -398,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": @@ -419,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": @@ -435,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": @@ -470,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() 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 a0899dd8d..30948f84f 100644 --- a/caddyconfig/httpcaddyfile/tlsapp.go +++ b/caddyconfig/httpcaddyfile/tlsapp.go @@ -464,10 +464,10 @@ 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 := range tlsApp.Automation.Policies { ap := tlsApp.Automation.Policies[i] @@ -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/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/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/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/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_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_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 674036e17..9bc6af4b6 100644 --- a/caddytest/integration/caddyfile_adapt_test.go +++ b/caddytest/integration/caddyfile_adapt_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddytest" _ "github.com/caddyserver/caddy/v2/internal/testmocks" ) @@ -27,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/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 31dc4be7b..e55a6df58 100644 --- a/caddytest/integration/mockdns_test.go +++ b/caddytest/integration/mockdns_test.go @@ -15,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 { @@ -31,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 } diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 1660e0f6f..75d114992 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -172,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") @@ -187,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 } @@ -204,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 == "" { @@ -219,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 } } @@ -236,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 @@ -256,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 @@ -289,6 +331,9 @@ func cmdRun(fl Flags) (int, error) { } } + // release the last local logger reference + logger = nil //nolint:wastedassign,ineffassign + select {} } @@ -319,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 } @@ -337,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 { @@ -441,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) @@ -469,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 { @@ -541,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 } @@ -756,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 0dae876fb..c9ea636b9 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -293,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)") @@ -390,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. `, @@ -480,7 +482,7 @@ argument of --directory. If the directory does not exist, it will be created. }, } - // source: https://github.com/spf13/cobra/blob/main/shell_completions.md + // source: https://github.com/spf13/cobra/blob/6dec1ae26659a130bdb4c985768d1853b0e1bc06/site/content/completions/_index.md completionCommand := Command{ Name: "completion", Usage: "[bash|zsh|fish|powershell]", diff --git a/cmd/main.go b/cmd/main.go index 47d702ca7..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 diff --git a/cmd/packagesfuncs.go b/cmd/packagesfuncs.go index cda6f31f6..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) { @@ -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 eb0979f3a..4c1139936 100644 --- a/context.go +++ b/context.go @@ -393,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 @@ -402,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 @@ -422,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 { @@ -487,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 } @@ -511,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 } diff --git a/go.mod b/go.mod index 8c95e2e0c..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.23.0 + github.com/caddyserver/certmagic v0.25.0 github.com/caddyserver/zerossl v0.1.3 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.2 - github.com/prometheus/client_golang v1.19.1 - github.com/quic-go/quic-go v0.51.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.38.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 v1.0.0-beta.1 + 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 7113739f7..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.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU= -github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= +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= @@ -115,19 +117,16 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/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/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 v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= -github.com/libdns/libdns v1.0.0-beta.1/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.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= -github.com/mholt/acmez/v3 v3.1.2/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.51.0 h1:K8exxe9zXxeRKxaXxi/GpUqYiTrtdiWP8bo1KFya6Wc= -github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= -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.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.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/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/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 9e0057678..8a862bacf 100644 --- a/listeners.go +++ b/listeners.go @@ -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 diff --git a/logging.go b/logging.go index 1a7b0ce29..2734b5425 100644 --- a/logging.go +++ b/logging.go @@ -28,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() { @@ -190,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 } @@ -773,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/modules.go b/modules.go index 37b56a988..24c452589 100644 --- a/modules.go +++ b/modules.go @@ -345,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. diff --git a/modules/caddyevents/app.go b/modules/caddyevents/app.go index 9fc8fa8ed..6c2abbf7c 100644 --- a/modules/caddyevents/app.go +++ b/modules/caddyevents/app.go @@ -249,8 +249,8 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) c 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 } diff --git a/modules/caddyhttp/app.go b/modules/caddyhttp/app.go index b550904e2..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" @@ -151,6 +150,11 @@ 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 map[string]struct{} } @@ -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 c34954f92..05f8a7517 100644 --- a/modules/caddyhttp/autohttps.go +++ b/modules/caddyhttp/autohttps.go @@ -265,6 +265,22 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er } } + // 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: 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 69db62a5c..792c198ee 100644 --- a/modules/caddyhttp/caddyauth/caddyauth.go +++ b/modules/caddyhttp/caddyauth/caddyauth.go @@ -60,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) 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 704661f21..5d9dc7dbe 100644 --- a/modules/caddyhttp/fileserver/browse.html +++ b/modules/caddyhttp/fileserver/browse.html @@ -828,6 +828,96 @@ footer { Grid + {{- if and (eq .Layout "grid") (eq .Sort "name") (ne .Order "asc")}} + + + Z + A + + + + + {{- else if and (eq .Layout "grid") (eq .Sort "name") (ne .Order "desc")}} + + + A + Z + + + + + {{- else if and (eq .Layout "grid")}} + + + A + Z + + + + + {{- 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}}
{{- if eq .Layout "grid"}} @@ -1086,6 +1176,7 @@ footer { diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index 5c2ea7018..3daf8daef 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -167,6 +167,8 @@ type FileServer struct { // If set, file Etags will be read from sidecar files // with any of these suffixes, instead of generating // our own Etag. + // Keep in mind that the Etag values in the files have to be quoted as per RFC7232. + // See https://datatracker.ietf.org/doc/html/rfc7232#section-2.3 for a few examples. EtagFileExtensions []string `json:"etag_file_extensions,omitempty"` fsmap caddy.FileSystems @@ -455,7 +457,14 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c } defer file.Close() respHeader.Set("Content-Encoding", ae) - respHeader.Del("Accept-Ranges") + + // stdlib won't set Content-Length for non-range requests if Content-Encoding is set. + // see: https://github.com/caddyserver/caddy/issues/7040 + // Setting the Range header manually will result in 206 Partial Content. + // see: https://github.com/caddyserver/caddy/issues/7250 + if r.Header.Get("Range") == "" { + respHeader.Set("Content-Length", strconv.FormatInt(compressedInfo.Size(), 10)) + } // try to get the etag from pre computed files if an etag suffix list was provided if etag == "" && fsrv.EtagFileExtensions != nil { @@ -684,11 +693,11 @@ func fileHidden(filename string, hide []string) bool { return true } } - } else if strings.HasPrefix(filename, h) { + } else if after, ok := strings.CutPrefix(filename, h); ok { // if there is a separator in h, and filename is exactly // prefixed with h, then we can do a prefix match so that // "/foo" matches "/foo/bar" but not "/foobar". - withoutPrefix := strings.TrimPrefix(filename, h) + withoutPrefix := after if strings.HasPrefix(withoutPrefix, separator) { return true } diff --git a/modules/caddyhttp/headers/headers.go b/modules/caddyhttp/headers/headers.go index ef9e35e7d..33d9e39ee 100644 --- a/modules/caddyhttp/headers/headers.go +++ b/modules/caddyhttp/headers/headers.go @@ -141,6 +141,14 @@ func (ops *HeaderOps) Provision(_ caddy.Context) error { if r.SearchRegexp == "" { continue } + + // Check if it contains placeholders + if containsPlaceholders(r.SearchRegexp) { + // Contains placeholders, skips precompilation, and recompiles at runtime + continue + } + + // Does not contain placeholders, safe to precompile re, err := regexp.Compile(r.SearchRegexp) if err != nil { return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err) @@ -151,6 +159,20 @@ func (ops *HeaderOps) Provision(_ caddy.Context) error { return nil } +// containsPlaceholders checks if the string contains Caddy placeholder syntax {key} +func containsPlaceholders(s string) bool { + openIdx := strings.Index(s, "{") + if openIdx == -1 { + return false + } + closeIdx := strings.Index(s[openIdx+1:], "}") + if closeIdx == -1 { + return false + } + // Make sure there is content between the brackets + return closeIdx > 0 +} + func (ops HeaderOps) validate() error { for fieldName, replacements := range ops.Replace { for _, r := range replacements { @@ -269,7 +291,15 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) { for fieldName, vals := range hdr { for i := range vals { if r.re != nil { + // Use precompiled regular expressions hdr[fieldName][i] = r.re.ReplaceAllString(hdr[fieldName][i], replace) + } else if r.SearchRegexp != "" { + // Runtime compilation of regular expressions + searchRegexp := repl.ReplaceKnown(r.SearchRegexp, "") + if re, err := regexp.Compile(searchRegexp); err == nil { + hdr[fieldName][i] = re.ReplaceAllString(hdr[fieldName][i], replace) + } + // If compilation fails, skip this replacement } else { hdr[fieldName][i] = strings.ReplaceAll(hdr[fieldName][i], search, replace) } @@ -291,6 +321,11 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) { for i := range vals { if r.re != nil { hdr[hdrFieldName][i] = r.re.ReplaceAllString(hdr[hdrFieldName][i], replace) + } else if r.SearchRegexp != "" { + searchRegexp := repl.ReplaceKnown(r.SearchRegexp, "") + if re, err := regexp.Compile(searchRegexp); err == nil { + hdr[hdrFieldName][i] = re.ReplaceAllString(hdr[hdrFieldName][i], replace) + } } else { hdr[hdrFieldName][i] = strings.ReplaceAll(hdr[hdrFieldName][i], search, replace) } diff --git a/modules/caddyhttp/headers/headers_test.go b/modules/caddyhttp/headers/headers_test.go index 9808c29c9..a303c92dd 100644 --- a/modules/caddyhttp/headers/headers_test.go +++ b/modules/caddyhttp/headers/headers_test.go @@ -272,3 +272,107 @@ type nextHandler func(http.ResponseWriter, *http.Request) error func (f nextHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) error { return f(w, r) } + +func TestContainsPlaceholders(t *testing.T) { + for i, tc := range []struct { + input string + expected bool + }{ + {"static", false}, + {"{placeholder}", true}, + {"prefix-{placeholder}-suffix", true}, + {"{}", false}, + {"no-braces", false}, + {"{unclosed", false}, + {"unopened}", false}, + } { + actual := containsPlaceholders(tc.input) + if actual != tc.expected { + t.Errorf("Test %d: containsPlaceholders(%q) = %v, expected %v", i, tc.input, actual, tc.expected) + } + } +} + +func TestHeaderProvisionSkipsPlaceholders(t *testing.T) { + ops := &HeaderOps{ + Replace: map[string][]Replacement{ + "Static": { + Replacement{SearchRegexp: ":443", Replace: "STATIC"}, + }, + "Dynamic": { + Replacement{SearchRegexp: ":{http.request.local.port}", Replace: "DYNAMIC"}, + }, + }, + } + + err := ops.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("Provision failed: %v", err) + } + + // Static regex should be precompiled + if ops.Replace["Static"][0].re == nil { + t.Error("Expected static regex to be precompiled") + } + + // Dynamic regex with placeholder should not be precompiled + if ops.Replace["Dynamic"][0].re != nil { + t.Error("Expected dynamic regex with placeholder to not be precompiled") + } +} + +func TestPlaceholderInSearchRegexp(t *testing.T) { + handler := Handler{ + Response: &RespHeaderOps{ + HeaderOps: &HeaderOps{ + Replace: map[string][]Replacement{ + "Test-Header": { + Replacement{ + SearchRegexp: ":{http.request.local.port}", + Replace: "PLACEHOLDER-WORKS", + }, + }, + }, + }, + }, + } + + // Provision the handler + err := handler.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("Provision failed: %v", err) + } + + replacement := handler.Response.HeaderOps.Replace["Test-Header"][0] + t.Logf("After provision - SearchRegexp: %q, re: %v", replacement.SearchRegexp, replacement.re) + + rr := httptest.NewRecorder() + + req := httptest.NewRequest("GET", "http://localhost:443/", nil) + repl := caddy.NewReplacer() + repl.Set("http.request.local.port", "443") + + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + rr.Header().Set("Test-Header", "prefix:443suffix") + t.Logf("Initial header: %v", rr.Header()) + + next := nextHandler(func(w http.ResponseWriter, r *http.Request) error { + w.WriteHeader(200) + return nil + }) + + err = handler.ServeHTTP(rr, req, next) + if err != nil { + t.Fatalf("ServeHTTP failed: %v", err) + } + + t.Logf("Final header: %v", rr.Header()) + + result := rr.Header().Get("Test-Header") + expected := "prefixPLACEHOLDER-WORKSsuffix" + if result != expected { + t.Errorf("Expected header value %q, got %q", expected, result) + } +} diff --git a/modules/caddyhttp/http2listener.go b/modules/caddyhttp/http2listener.go index 51b356a77..ad5991790 100644 --- a/modules/caddyhttp/http2listener.go +++ b/modules/caddyhttp/http2listener.go @@ -1,102 +1,131 @@ package caddyhttp import ( - "context" "crypto/tls" - weakrand "math/rand" + "io" "net" - "net/http" - "sync/atomic" - "time" + "go.uber.org/zap" "golang.org/x/net/http2" ) -// http2Listener wraps the listener to solve the following problems: -// 1. server h2 natively without using h2c hack when listener handles tls connection but -// don't return *tls.Conn -// 2. graceful shutdown. the shutdown logic is copied from stdlib http.Server, it's an extra maintenance burden but -// whatever, the shutdown logic maybe extracted to be used with h2c graceful shutdown. http2.Server supports graceful shutdown -// sending GO_AWAY frame to connected clients, but doesn't track connection status. It requires explicit call of http2.ConfigureServer -type http2Listener struct { - cnt uint64 - net.Listener - server *http.Server - h2server *http2.Server -} - -type connectionStateConn interface { - net.Conn +type connectionStater interface { ConnectionState() tls.ConnectionState } +// http2Listener wraps the listener to solve the following problems: +// 1. prevent genuine h2c connections from succeeding if h2c is not enabled +// and the connection doesn't implment connectionStater or the resulting NegotiatedProtocol +// isn't http2. +// This does allow a connection to pass as tls enabled even if it's not, listener wrappers +// can do this. +// 2. After wrapping the connection doesn't implement connectionStater, emit a warning so that listener +// wrapper authors will hopefully implement it. +// 3. check if the connection matches a specific http version. h2/h2c has a distinct preface. +type http2Listener struct { + useTLS bool + useH1 bool + useH2 bool + net.Listener + logger *zap.Logger +} + func (h *http2Listener) Accept() (net.Conn, error) { - for { - conn, err := h.Listener.Accept() - if err != nil { - return nil, err - } + conn, err := h.Listener.Accept() + if err != nil { + return nil, err + } - if csc, ok := conn.(connectionStateConn); ok { - // *tls.Conn will return empty string because it's only populated after handshake is complete - if csc.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS { - go h.serveHttp2(csc) - continue - } - } + // *tls.Conn doesn't need to be wrapped because we already removed unwanted alpns + // and handshake won't succeed without mutually supported alpns + if tlsConn, ok := conn.(*tls.Conn); ok { + return tlsConn, nil + } + _, isConnectionStater := conn.(connectionStater) + // emit a warning + if h.useTLS && !isConnectionStater { + h.logger.Warn("tls is enabled, but listener wrapper returns a connection that doesn't implement connectionStater") + } else if !h.useTLS && isConnectionStater { + h.logger.Warn("tls is disabled, but listener wrapper returns a connection that implements connectionStater") + } + + // if both h1 and h2 are enabled, we don't need to check the preface + if h.useH1 && h.useH2 { + if isConnectionStater { + return tlsStateConn{conn}, nil + } return conn, nil } -} -func (h *http2Listener) serveHttp2(csc connectionStateConn) { - atomic.AddUint64(&h.cnt, 1) - h.runHook(csc, http.StateNew) - defer func() { - csc.Close() - atomic.AddUint64(&h.cnt, ^uint64(0)) - h.runHook(csc, http.StateClosed) - }() - h.h2server.ServeConn(csc, &http2.ServeConnOpts{ - Context: h.server.ConnContext(context.Background(), csc), - BaseConfig: h.server, - Handler: h.server.Handler, - }) -} - -const shutdownPollIntervalMax = 500 * time.Millisecond - -func (h *http2Listener) Shutdown(ctx context.Context) error { - pollIntervalBase := time.Millisecond - nextPollInterval := func() time.Duration { - // Add 10% jitter. - //nolint:gosec - interval := pollIntervalBase + time.Duration(weakrand.Intn(int(pollIntervalBase/10))) - // Double and clamp for next time. - pollIntervalBase *= 2 - if pollIntervalBase > shutdownPollIntervalMax { - pollIntervalBase = shutdownPollIntervalMax - } - return interval + // impossible both are false, either useH1 or useH2 must be true, + // or else the listener wouldn't be created + h2Conn := &http2Conn{ + h2Expected: h.useH2, + logger: h.logger, + Conn: conn, } + if isConnectionStater { + return tlsStateConn{http2StateConn{h2Conn}}, nil + } + return h2Conn, nil +} - timer := time.NewTimer(nextPollInterval()) - defer timer.Stop() - for { - if atomic.LoadUint64(&h.cnt) == 0 { - return nil +// tlsStateConn wraps a net.Conn that implements connectionStater to hide that method +// we can call netConn to get the original net.Conn and get the tls connection state +// golang 1.25 will call that method, and it breaks h2 with connections other than *tls.Conn +type tlsStateConn struct { + net.Conn +} + +func (conn tlsStateConn) tlsNetConn() net.Conn { + return conn.Conn +} + +type http2StateConn struct { + *http2Conn +} + +func (conn http2StateConn) ConnectionState() tls.ConnectionState { + return conn.Conn.(connectionStater).ConnectionState() +} + +type http2Conn struct { + // current index where the preface should match, + // no matching is done if idx is >= len(http2.ClientPreface) + idx int + // whether the connection is expected to be h2/h2c + h2Expected bool + // log if one such connection is detected + logger *zap.Logger + net.Conn +} + +func (c *http2Conn) Read(p []byte) (int, error) { + if c.idx >= len(http2.ClientPreface) { + return c.Conn.Read(p) + } + n, err := c.Conn.Read(p) + for i := range n { + // first mismatch + if p[i] != http2.ClientPreface[c.idx] { + // close the connection if h2 is expected + if c.h2Expected { + c.logger.Debug("h1 connection detected, but h1 is not enabled") + _ = c.Conn.Close() + return 0, io.EOF + } + // no need to continue matching anymore + c.idx = len(http2.ClientPreface) + return n, err } - select { - case <-ctx.Done(): - return ctx.Err() - case <-timer.C: - timer.Reset(nextPollInterval()) + c.idx++ + // matching complete + if c.idx == len(http2.ClientPreface) && !c.h2Expected { + c.logger.Debug("h2/h2c connection detected, but h2/h2c is not enabled") + _ = c.Conn.Close() + return 0, io.EOF } } -} - -func (h *http2Listener) runHook(conn net.Conn, state http.ConnState) { - if h.server.ConnState != nil { - h.server.ConnState(conn, state) - } + return n, err } diff --git a/modules/caddyhttp/ip_matchers.go b/modules/caddyhttp/ip_matchers.go index 5e0b356e7..9335112e8 100644 --- a/modules/caddyhttp/ip_matchers.go +++ b/modules/caddyhttp/ip_matchers.go @@ -20,7 +20,6 @@ import ( "net" "net/http" "net/netip" - "reflect" "strings" "github.com/google/cel-go/cel" @@ -109,7 +108,7 @@ func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) { []*cel.Type{cel.ListType(cel.StringType)}, // function to convert a constant list of strings to a MatchPath instance. func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType strList, err := data.ConvertToNative(refStringList) if err != nil { return nil, err @@ -222,7 +221,7 @@ func (MatchClientIP) CELLibrary(ctx caddy.Context) (cel.Library, error) { []*cel.Type{cel.ListType(cel.StringType)}, // function to convert a constant list of strings to a MatchPath instance. func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType strList, err := data.ConvertToNative(refStringList) if err != nil { return nil, err diff --git a/modules/caddyhttp/logging.go b/modules/caddyhttp/logging.go index 87298ac3c..e8a1316bd 100644 --- a/modules/caddyhttp/logging.go +++ b/modules/caddyhttp/logging.go @@ -209,7 +209,7 @@ func errLogValues(err error) (status int, msg string, fields func() []zapcore.Fi zap.String("err_trace", handlerErr.Trace), } } - return + return status, msg, fields } fields = func() []zapcore.Field { return []zapcore.Field{ @@ -218,7 +218,7 @@ func errLogValues(err error) (status int, msg string, fields func() []zapcore.Fi } status = http.StatusInternalServerError msg = err.Error() - return + return status, msg, fields } // ExtraLogFields is a list of extra fields to log with every request. diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 48c92f174..22976cfbd 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -23,7 +23,6 @@ import ( "net/textproto" "net/url" "path" - "reflect" "regexp" "runtime" "slices" @@ -373,7 +372,7 @@ func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) { "host_match_request_list", []*cel.Type{cel.ListType(cel.StringType)}, func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType strList, err := data.ConvertToNative(refStringList) if err != nil { return nil, err @@ -654,7 +653,7 @@ func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) { []*cel.Type{cel.ListType(cel.StringType)}, // function to convert a constant list of strings to a MatchPath instance. func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType strList, err := data.ConvertToNative(refStringList) if err != nil { return nil, err @@ -733,7 +732,7 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { "path_regexp_request_string_string", []*cel.Type{cel.StringType, cel.StringType}, func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType params, err := data.ConvertToNative(refStringList) if err != nil { return nil, err @@ -802,7 +801,7 @@ func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) { "method_request_list", []*cel.Type{cel.ListType(cel.StringType)}, func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType strList, err := data.ConvertToNative(refStringList) if err != nil { return nil, err @@ -1173,7 +1172,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { "header_regexp_request_string_string", []*cel.Type{cel.StringType, cel.StringType}, func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType params, err := data.ConvertToNative(refStringList) if err != nil { return nil, err @@ -1196,7 +1195,7 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { "header_regexp_request_string_string_string", []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType params, err := data.ConvertToNative(refStringList) if err != nil { return nil, err @@ -1546,7 +1545,7 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } -// ParseCaddyfileNestedMatcher parses the Caddyfile tokens for a nested +// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested // matcher set, and returns its raw module map value. func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) { matcherMap := make(map[string]any) diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go index f7be6909e..b15b6316d 100644 --- a/modules/caddyhttp/matchers_test.go +++ b/modules/caddyhttp/matchers_test.go @@ -947,7 +947,7 @@ func BenchmarkHeaderREMatcher(b *testing.B) { ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) req = req.WithContext(ctx) addHTTPVarsToReplacer(repl, req, httptest.NewRecorder()) - for run := 0; run < b.N; run++ { + for b.Loop() { match.MatchWithError(req) } } @@ -992,8 +992,6 @@ func TestVarREMatcher(t *testing.T) { expect: true, }, } { - i := i // capture range value - tc := tc // capture range value t.Run(tc.desc, func(t *testing.T) { t.Parallel() // compile the regexp and validate its name @@ -1180,8 +1178,7 @@ func BenchmarkLargeHostMatcher(b *testing.B) { b.Fatal(err) } - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { matcher.MatchWithError(req) } } @@ -1194,8 +1191,7 @@ func BenchmarkHostMatcherWithoutPlaceholder(b *testing.B) { match := MatchHost{"localhost"} - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { match.MatchWithError(req) } } @@ -1212,8 +1208,7 @@ func BenchmarkHostMatcherWithPlaceholder(b *testing.B) { req = req.WithContext(ctx) match := MatchHost{"{env.GO_BENCHMARK_DOMAIN}"} - b.ResetTimer() - for i := 0; i < b.N; i++ { + for b.Loop() { match.MatchWithError(req) } } diff --git a/modules/caddyhttp/push/link.go b/modules/caddyhttp/push/link.go index 855dffd05..2a4af5803 100644 --- a/modules/caddyhttp/push/link.go +++ b/modules/caddyhttp/push/link.go @@ -41,7 +41,7 @@ func parseLinkHeader(header string) []linkResource { return resources } - for _, link := range strings.Split(header, comma) { + for link := range strings.SplitSeq(header, comma) { l := linkResource{params: make(map[string]string)} li, ri := strings.Index(link, "<"), strings.Index(link, ">") @@ -51,7 +51,7 @@ func parseLinkHeader(header string) []linkResource { l.uri = strings.TrimSpace(link[li+1 : ri]) - for _, param := range strings.Split(strings.TrimSpace(link[ri+1:]), semicolon) { + for param := range strings.SplitSeq(strings.TrimSpace(link[ri+1:]), semicolon) { before, after, isCut := strings.Cut(strings.TrimSpace(param), equal) key := strings.TrimSpace(before) if key == "" { diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go index 69779e6ed..9c3ab85f2 100644 --- a/modules/caddyhttp/replacer.go +++ b/modules/caddyhttp/replacer.go @@ -172,8 +172,12 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo // current URI, including any internal rewrites case "http.request.uri": return req.URL.RequestURI(), true + case "http.request.uri_escaped": + return url.QueryEscape(req.URL.RequestURI()), true case "http.request.uri.path": return req.URL.Path, true + case "http.request.uri.path_escaped": + return url.QueryEscape(req.URL.Path), true case "http.request.uri.path.file": _, file := path.Split(req.URL.Path) return file, true @@ -186,6 +190,8 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo return path.Ext(req.URL.Path), true case "http.request.uri.query": return req.URL.RawQuery, true + case "http.request.uri.query_escaped": + return url.QueryEscape(req.URL.RawQuery), true case "http.request.uri.prefixed_query": if req.URL.RawQuery == "" { return "", true @@ -283,7 +289,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo return prefix.String(), true } - // hostname labels + // hostname labels (case insensitive, so normalize to lowercase) if strings.HasPrefix(key, reqHostLabelsReplPrefix) { idxStr := key[len(reqHostLabelsReplPrefix):] idx, err := strconv.Atoi(idxStr) @@ -298,7 +304,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo if idx >= len(hostLabels) { return "", true } - return hostLabels[len(hostLabels)-idx-1], true + return strings.ToLower(hostLabels[len(hostLabels)-idx-1]), true } // path parts diff --git a/modules/caddyhttp/replacer_test.go b/modules/caddyhttp/replacer_test.go index 50a2e8c62..c75fe82ed 100644 --- a/modules/caddyhttp/replacer_test.go +++ b/modules/caddyhttp/replacer_test.go @@ -28,7 +28,7 @@ import ( ) func TestHTTPVarReplacement(t *testing.T) { - req, _ := http.NewRequest(http.MethodGet, "/foo/bar.tar.gz", nil) + req, _ := http.NewRequest(http.MethodGet, "/foo/bar.tar.gz?a=1&b=2", nil) repl := caddy.NewReplacer() localAddr, _ := net.ResolveTCPAddr("tcp", "192.168.159.1:80") ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) @@ -142,6 +142,22 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV get: "http.request.host.labels.2", expect: "", }, + { + get: "http.request.uri", + expect: "/foo/bar.tar.gz?a=1&b=2", + }, + { + get: "http.request.uri_escaped", + expect: "%2Ffoo%2Fbar.tar.gz%3Fa%3D1%26b%3D2", + }, + { + get: "http.request.uri.path", + expect: "/foo/bar.tar.gz", + }, + { + get: "http.request.uri.path_escaped", + expect: "%2Ffoo%2Fbar.tar.gz", + }, { get: "http.request.uri.path.file", expect: "bar.tar.gz", @@ -155,6 +171,26 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV get: "http.request.uri.path.file.ext", expect: ".gz", }, + { + get: "http.request.uri.query", + expect: "a=1&b=2", + }, + { + get: "http.request.uri.query_escaped", + expect: "a%3D1%26b%3D2", + }, + { + get: "http.request.uri.query.a", + expect: "1", + }, + { + get: "http.request.uri.query.b", + expect: "2", + }, + { + get: "http.request.uri.prefixed_query", + expect: "?a=1&b=2", + }, { get: "http.request.tls.cipher_suite", expect: "TLS_AES_256_GCM_SHA384", diff --git a/modules/caddyhttp/requestbody/requestbody.go b/modules/caddyhttp/requestbody/requestbody.go index 1fa654811..65b09ded0 100644 --- a/modules/caddyhttp/requestbody/requestbody.go +++ b/modules/caddyhttp/requestbody/requestbody.go @@ -116,7 +116,7 @@ func (ew errorWrapper) Read(p []byte) (n int, err error) { if errors.As(err, &mbe) { err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err) } - return + return n, err } // Interface guard diff --git a/modules/caddyhttp/reverseproxy/command.go b/modules/caddyhttp/reverseproxy/command.go index 54955bcf7..3f6ffa8cb 100644 --- a/modules/caddyhttp/reverseproxy/command.go +++ b/modules/caddyhttp/reverseproxy/command.go @@ -75,8 +75,8 @@ For proxying: cmd.Flags().BoolP("insecure", "", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING TLS CERTIFICATES!)") cmd.Flags().BoolP("disable-redirects", "r", false, "Disable HTTP->HTTPS redirects") cmd.Flags().BoolP("internal-certs", "i", false, "Use internal CA for issuing certs") - cmd.Flags().StringSliceP("header-up", "H", []string{}, "Set a request header to send to the upstream (format: \"Field: value\")") - cmd.Flags().StringSliceP("header-down", "d", []string{}, "Set a response header to send back to the client (format: \"Field: value\")") + cmd.Flags().StringArrayP("header-up", "H", []string{}, "Set a request header to send to the upstream (format: \"Field: value\")") + cmd.Flags().StringArrayP("header-down", "d", []string{}, "Set a response header to send back to the client (format: \"Field: value\")") cmd.Flags().BoolP("access-log", "", false, "Enable the access log") cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs") cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdReverseProxy) @@ -182,7 +182,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) { } // set up header_up - headerUp, err := fs.GetStringSlice("header-up") + headerUp, err := fs.GetStringArray("header-up") if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err) } @@ -204,7 +204,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) { } // set up header_down - headerDown, err := fs.GetStringSlice("header-down") + headerDown, err := fs.GetStringArray("header-down") if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err) } diff --git a/modules/caddyhttp/reverseproxy/fastcgi/client.go b/modules/caddyhttp/reverseproxy/fastcgi/client.go index 684394f53..48599c27f 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/client.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/client.go @@ -154,13 +154,13 @@ func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error) err = writer.writeBeginRequest(uint16(Responder), 0) if err != nil { - return + return r, err } writer.recType = Params err = writer.writePairs(p) if err != nil { - return + return r, err } writer.recType = Stdin @@ -176,7 +176,7 @@ func (c *client) Do(p map[string]string, req io.Reader) (r io.Reader, err error) } r = &streamReader{c: c} - return + return r, err } // clientCloser is a io.ReadCloser. It wraps a io.Reader with a Closer @@ -213,7 +213,7 @@ func (f clientCloser) Close() error { func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) { r, err := c.Do(p, req) if err != nil { - return + return resp, err } rb := bufio.NewReader(r) @@ -223,7 +223,7 @@ func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Respons // Parse the response headers. mimeHeader, err := tp.ReadMIMEHeader() if err != nil && err != io.EOF { - return + return resp, err } resp.Header = http.Header(mimeHeader) @@ -231,7 +231,7 @@ func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Respons statusNumber, statusInfo, statusIsCut := strings.Cut(resp.Header.Get("Status"), " ") resp.StatusCode, err = strconv.Atoi(statusNumber) if err != nil { - return + return resp, err } if statusIsCut { resp.Status = statusInfo @@ -260,7 +260,7 @@ func (c *client) Request(p map[string]string, req io.Reader) (resp *http.Respons } resp.Body = closer - return + return resp, err } // Get issues a GET request to the fcgi responder. @@ -329,7 +329,7 @@ func (c *client) PostFile(p map[string]string, data url.Values, file map[string] for _, v0 := range val { err = writer.WriteField(key, v0) if err != nil { - return + return resp, err } } } @@ -347,13 +347,13 @@ func (c *client) PostFile(p map[string]string, data url.Values, file map[string] } _, err = io.Copy(part, fd) if err != nil { - return + return resp, err } } err = writer.Close() if err != nil { - return + return resp, err } return c.Post(p, "POST", bodyType, buf, int64(buf.Len())) diff --git a/modules/caddyhttp/reverseproxy/fastcgi/client_test.go b/modules/caddyhttp/reverseproxy/fastcgi/client_test.go index 14a1cf684..f850cfb9d 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/client_test.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/client_test.go @@ -120,7 +120,7 @@ func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[ conn, err := net.Dial("tcp", ipPort) if err != nil { log.Println("err:", err) - return + return content } fcgi := client{rwc: conn, reqID: 1} @@ -162,7 +162,7 @@ func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[ if err != nil { log.Println("err:", err) - return + return content } defer resp.Body.Close() @@ -176,7 +176,7 @@ func sendFcgi(reqType int, fcgiParams map[string]string, data []byte, posts map[ globalt.Error("Server return failed message") } - return + return content } func generateRandFile(size int) (p string, m string) { @@ -206,7 +206,7 @@ func generateRandFile(size int) (p string, m string) { } } m = fmt.Sprintf("%x", h.Sum(nil)) - return + return p, m } func DisabledTest(t *testing.T) { diff --git a/modules/caddyhttp/reverseproxy/fastcgi/record.go b/modules/caddyhttp/reverseproxy/fastcgi/record.go index 46c1f17bb..57d8d83ee 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/record.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/record.go @@ -30,23 +30,23 @@ func (rec *record) fill(r io.Reader) (err error) { rec.lr.N = rec.padding rec.lr.R = r if _, err = io.Copy(io.Discard, rec); err != nil { - return + return err } if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { - return + return err } if rec.h.Version != 1 { err = errors.New("fcgi: invalid header version") - return + return err } if rec.h.Type == EndRequest { err = io.EOF - return + return err } rec.lr.N = int64(rec.h.ContentLength) rec.padding = int64(rec.h.PaddingLength) - return + return err } func (rec *record) Read(p []byte) (n int, err error) { diff --git a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go index 347f6dfbf..f838c8702 100644 --- a/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go @@ -84,7 +84,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) // create the reverse proxy handler rpHandler := &reverseproxy.Handler{ // set up defaults for header_up; reverse_proxy already deals with - // adding the other three X-Forwarded-* headers, but for this flow, + // adding the other three X-Forwarded-* headers, but for this flow, // we want to also send along the incoming method and URI since this // request will have a rewritten URI and method. Headers: &headers.Handler{ diff --git a/modules/caddyhttp/reverseproxy/hosts.go b/modules/caddyhttp/reverseproxy/hosts.go index 300003f2b..6d35ed821 100644 --- a/modules/caddyhttp/reverseproxy/hosts.go +++ b/modules/caddyhttp/reverseproxy/hosts.go @@ -281,3 +281,7 @@ const proxyProtocolInfoVarKey = "reverse_proxy.proxy_protocol_info" type ProxyProtocolInfo struct { AddrPort netip.AddrPort } + +// tlsH1OnlyVarKey is the key used that indicates the connection will use h1 only for TLS. +// https://github.com/caddyserver/caddy/issues/7292 +const tlsH1OnlyVarKey = "reverse_proxy.tls_h1_only" diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 24407df2b..3031bda46 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -171,12 +171,25 @@ func (HTTPTransport) CaddyModule() caddy.ModuleInfo { } } +var ( + allowedVersions = []string{"1.1", "2", "h2c", "3"} + allowedVersionsString = strings.Join(allowedVersions, ", ") +) + // Provision sets up h.Transport with a *http.Transport // that is ready to use. func (h *HTTPTransport) Provision(ctx caddy.Context) error { if len(h.Versions) == 0 { h.Versions = []string{"1.1", "2"} } + // some users may provide http versions not recognized by caddy, instead of trying to + // guess the version, we just error out and let the user fix their config + // see: https://github.com/caddyserver/caddy/issues/7111 + for _, v := range h.Versions { + if !slices.Contains(allowedVersions, v) { + return fmt.Errorf("unsupported HTTP version: %s, supported version: %s", v, allowedVersionsString) + } + } rt, err := h.NewTransport(ctx) if err != nil { @@ -396,6 +409,14 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) tlsConfig := rt.TLSClientConfig.Clone() tlsConfig.ServerName = repl.ReplaceAll(tlsConfig.ServerName, "") + + // h1 only + if caddyhttp.GetVar(ctx, tlsH1OnlyVarKey) == true { + // stdlib does this + // https://github.com/golang/go/blob/4837fbe4145cd47b43eed66fee9eed9c2b988316/src/net/http/transport.go#L1701 + tlsConfig.NextProtos = nil + } + tlsConn := tls.Client(conn, tlsConfig) // complete the handshake before returning the connection @@ -415,7 +436,19 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e } if h.KeepAlive != nil { + // according to https://pkg.go.dev/net#Dialer.KeepAliveConfig, + // KeepAlive is ignored if KeepAliveConfig.Enable is true. + // If configured to 0, a system-dependent default is used. + // To disable tcp keepalive, choose a negative value, + // so KeepAliveConfig.Enable is false and KeepAlive is negative. + + // This is different from http keepalive where a tcp connection + // can transfer multiple http requests/responses. dialer.KeepAlive = time.Duration(h.KeepAlive.ProbeInterval) + dialer.KeepAliveConfig = net.KeepAliveConfig{ + Enable: h.KeepAlive.ProbeInterval > 0, + Interval: time.Duration(h.KeepAlive.ProbeInterval), + } if h.KeepAlive.Enabled != nil { rt.DisableKeepAlives = !*h.KeepAlive.Enabled } diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index 88fba55a1..0c4028ce7 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -409,12 +409,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht return caddyhttp.Error(http.StatusInternalServerError, fmt.Errorf("preparing request for upstream round-trip: %v", err)) } - // websocket over http2, assuming backend doesn't support this, the request will be modified to http1.1 upgrade + + // websocket over http2 or http3 if extended connect is enabled, assuming backend doesn't support this, the request will be modified to http1.1 upgrade + // Both use the same upgrade mechanism: server advertizes extended connect support, and client sends the pseudo header :protocol in a CONNECT request + // The quic-go http3 implementation also puts :protocol in r.Proto for CONNECT requests (quic-go/http3/headers.go@70-72,185,203) // TODO: once we can reliably detect backend support this, it can be removed for those backends - if r.ProtoMajor == 2 && r.Method == http.MethodConnect && r.Header.Get(":protocol") == "websocket" { + if (r.ProtoMajor == 2 && r.Method == http.MethodConnect && r.Header.Get(":protocol") == "websocket") || + (r.ProtoMajor == 3 && r.Method == http.MethodConnect && r.Proto == "websocket") { clonedReq.Header.Del(":protocol") // keep the body for later use. http1.1 upgrade uses http.NoBody - caddyhttp.SetVar(clonedReq.Context(), "h2_websocket_body", clonedReq.Body) + caddyhttp.SetVar(clonedReq.Context(), "extended_connect_websocket_body", clonedReq.Body) clonedReq.Body = http.NoBody clonedReq.Method = http.MethodGet clonedReq.Header.Set("Upgrade", "websocket") @@ -726,6 +730,12 @@ func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http. proxyProtocolInfo := ProxyProtocolInfo{AddrPort: addrPort} caddyhttp.SetVar(req.Context(), proxyProtocolInfoVarKey, proxyProtocolInfo) + // some of the outbound requests require h1 (e.g. websocket) + // https://github.com/golang/go/blob/4837fbe4145cd47b43eed66fee9eed9c2b988316/src/net/http/request.go#L1579 + if isWebsocket(req) { + caddyhttp.SetVar(req.Context(), tlsH1OnlyVarKey, true) + } + // Add the supported X-Forwarded-* headers err = h.addForwardedHeaders(req) if err != nil { @@ -1356,7 +1366,7 @@ func upgradeType(h http.Header) string { // See RFC 7230, section 6.1 func removeConnectionHeaders(h http.Header) { for _, f := range h["Connection"] { - for _, sf := range strings.Split(f, ",") { + for sf := range strings.SplitSeq(f, ",") { if sf = textproto.TrimString(sf); sf != "" { h.Del(sf) } diff --git a/modules/caddyhttp/reverseproxy/selectionpolicies.go b/modules/caddyhttp/reverseproxy/selectionpolicies.go index a9c036596..585fc3400 100644 --- a/modules/caddyhttp/reverseproxy/selectionpolicies.go +++ b/modules/caddyhttp/reverseproxy/selectionpolicies.go @@ -219,10 +219,7 @@ func (r RandomChoiceSelection) Validate() error { // Select returns an available host, if any. func (r RandomChoiceSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream { - k := r.Choose - if k > len(pool) { - k = len(pool) - } + k := min(r.Choose, len(pool)) choices := make([]*Upstream, k) for i, upstream := range pool { if !upstream.Available() { diff --git a/modules/caddyhttp/reverseproxy/streaming.go b/modules/caddyhttp/reverseproxy/streaming.go index d697eb402..66dd106d5 100644 --- a/modules/caddyhttp/reverseproxy/streaming.go +++ b/modules/caddyhttp/reverseproxy/streaming.go @@ -94,9 +94,9 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, conn io.ReadWriteCloser brw *bufio.ReadWriter ) - // websocket over http2, assuming backend doesn't support this, the request will be modified to http1.1 upgrade + // websocket over http2 or http3 if extended connect is enabled, assuming backend doesn't support this, the request will be modified to http1.1 upgrade // TODO: once we can reliably detect backend support this, it can be removed for those backends - if body, ok := caddyhttp.GetVar(req.Context(), "h2_websocket_body").(io.ReadCloser); ok { + if body, ok := caddyhttp.GetVar(req.Context(), "extended_connect_websocket_body").(io.ReadCloser); ok { req.Body = body rw.Header().Del("Upgrade") rw.Header().Del("Connection") @@ -146,7 +146,7 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, // adopted from https://github.com/golang/go/commit/8bcf2834afdf6a1f7937390903a41518715ef6f5 backConnCloseCh := make(chan struct{}) go func() { - // Ensure that the cancelation of a request closes the backend. + // Ensure that the cancellation of a request closes the backend. // See issue https://golang.org/issue/35559. select { case <-req.Context().Done(): @@ -588,11 +588,11 @@ func (m *maxLatencyWriter) Write(p []byte) (n int, err error) { m.logger.Debug("flushing immediately") //nolint:errcheck m.flush() - return + return n, err } if m.flushPending { m.logger.Debug("delayed flush already pending") - return + return n, err } if m.t == nil { m.t = time.AfterFunc(m.latency, m.delayedFlush) @@ -603,7 +603,7 @@ func (m *maxLatencyWriter) Write(p []byte) (n int, err error) { c.Write(zap.Duration("duration", m.latency)) } m.flushPending = true - return + return n, err } func (m *maxLatencyWriter) delayedFlush() { diff --git a/modules/caddyhttp/reverseproxy/upstreams.go b/modules/caddyhttp/reverseproxy/upstreams.go index aa59dc41b..e9eb7e60a 100644 --- a/modules/caddyhttp/reverseproxy/upstreams.go +++ b/modules/caddyhttp/reverseproxy/upstreams.go @@ -213,12 +213,12 @@ func (su SRVUpstreams) expandedAddr(r *http.Request) (addr, service, proto, name name = repl.ReplaceAll(su.Name, "") if su.Service == "" && su.Proto == "" { addr = name - return + return addr, service, proto, name } service = repl.ReplaceAll(su.Service, "") proto = repl.ReplaceAll(su.Proto, "") addr = su.formattedAddr(service, proto, name) - return + return addr, service, proto, name } // formattedAddr the RFC 2782 representation of the SRV domain, in diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go index ccb5f2515..3dd770938 100644 --- a/modules/caddyhttp/routes.go +++ b/modules/caddyhttp/routes.go @@ -302,13 +302,7 @@ func wrapRoute(route Route) Middleware { // wrapMiddleware wraps mh such that it can be correctly // appended to a list of middleware in preparation for -// compiling into a handler chain. We can't do this inline -// inside a loop, because it relies on a reference to mh -// not changing until the execution of its handler (which -// is deferred by multiple func closures). In other words, -// we need to pull this particular MiddlewareHandler -// pointer into its own stack frame to preserve it so it -// won't be overwritten in future loop iterations. +// compiling into a handler chain. func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware { handlerToUse := mh if metrics != nil { @@ -317,18 +311,12 @@ func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) M } return func(next Handler) Handler { - // copy the next handler (it's an interface, so it's - // just a very lightweight copy of a pointer); this - // is a safeguard against the handler changing the - // value, which could affect future requests (yikes) - nextCopy := next - return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { // EXPERIMENTAL: Trace each module that gets invoked if server, ok := r.Context().Value(ServerCtxKey).(*Server); ok && server != nil { server.logTrace(handlerToUse) } - return handlerToUse.ServeHTTP(w, r, nextCopy) + return handlerToUse.ServeHTTP(w, r, next) }) } } diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index a2b29d658..ac30f4028 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -76,9 +76,25 @@ type Server struct { // KeepAliveInterval is the interval at which TCP keepalive packets // are sent to keep the connection alive at the TCP layer when no other - // data is being transmitted. The default is 15s. + // data is being transmitted. + // If zero, the default is 15s. + // If negative, keepalive packets are not sent and other keepalive parameters + // are ignored. KeepAliveInterval caddy.Duration `json:"keepalive_interval,omitempty"` + // KeepAliveIdle is the time that the connection must be idle before + // the first TCP keep-alive probe is sent when no other data is being + // transmitted. + // If zero, the default is 15s. + // If negative, underlying socket value is unchanged. + KeepAliveIdle caddy.Duration `json:"keepalive_idle,omitempty"` + + // KeepAliveCount is the maximum number of TCP keep-alive probes that + // should be sent before dropping a connection. + // If zero, the default is 9. + // If negative, underlying socket value is unchanged. + KeepAliveCount int `json:"keepalive_count,omitempty"` + // MaxHeaderBytes is the maximum size to parse from a client's // HTTP request headers. MaxHeaderBytes int `json:"max_header_bytes,omitempty"` @@ -186,6 +202,13 @@ type Server struct { // This option is disabled by default. TrustedProxiesStrict int `json:"trusted_proxies_strict,omitempty"` + // If greater than zero, enables trusting socket connections + // (e.g. Unix domain sockets) as coming from a trusted + // proxy. + // + // This option is disabled by default. + TrustedProxiesUnix bool `json:"trusted_proxies_unix,omitempty"` + // Enables access logging and configures how access logs are handled // in this server. To minimally enable access logs, simply set this // to a non-null, empty struct. @@ -235,7 +258,8 @@ type Server struct { primaryHandlerChain Handler errorHandlerChain Handler listenerWrappers []caddy.ListenerWrapper - listeners []net.Listener + listeners []net.Listener // stdlib http.Server will close these + quicListeners []http3.QUICListener // http3 now leave the quic.Listener management to us tlsApp *caddytls.TLS events *caddyevents.App @@ -245,10 +269,9 @@ type Server struct { traceLogger *zap.Logger ctx caddy.Context - server *http.Server - h3server *http3.Server - h2listeners []*http2Listener - addresses []caddy.NetworkAddress + server *http.Server + h3server *http3.Server + addresses []caddy.NetworkAddress trustedProxies IPRangeSource @@ -265,14 +288,9 @@ type Server struct { // ServeHTTP is the entry point for all HTTP requests. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // If there are listener wrappers that process tls connections but don't return a *tls.Conn, this field will be nil. - // TODO: Can be removed if https://github.com/golang/go/pull/56110 is ever merged. if r.TLS == nil { - // not all requests have a conn (like virtual requests) - see #5698 - if conn, ok := r.Context().Value(ConnCtxKey).(net.Conn); ok { - if csc, ok := conn.(connectionStateConn); ok { - r.TLS = new(tls.ConnectionState) - *r.TLS = csc.ConnectionState() - } + if tlsConnStateFunc, ok := r.Context().Value(tlsConnectionStateFuncCtxKey).(func() *tls.ConnectionState); ok { + r.TLS = tlsConnStateFunc() } } @@ -626,6 +644,8 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error } } + s.quicListeners = append(s.quicListeners, h3ln) + //nolint:errcheck go s.h3server.ServeListener(h3ln) @@ -923,6 +943,17 @@ func determineTrustedProxy(r *http.Request, s *Server) (bool, string) { return false, "" } + if s.TrustedProxiesUnix && r.RemoteAddr == "@" { + if s.TrustedProxiesStrict > 0 { + ipRanges := []netip.Prefix{} + if s.trustedProxies != nil { + ipRanges = s.trustedProxies.GetIPRanges(r) + } + return true, strictUntrustedClientIp(r, s.ClientIPHeaders, ipRanges, "@") + } else { + return true, trustedRealClientIP(r, s.ClientIPHeaders, "@") + } + } // Parse the remote IP, ignore the error as non-fatal, // but the remote IP is required to continue, so we // just return early. This should probably never happen @@ -982,10 +1013,10 @@ func trustedRealClientIP(r *http.Request, headers []string, clientIP string) str // Since there can be many header values, we need to // join them together before splitting to get the full list - allValues := strings.Split(strings.Join(values, ","), ",") + allValues := strings.SplitSeq(strings.Join(values, ","), ",") // Get first valid left-most IP address - for _, part := range allValues { + for part := range allValues { // Some proxies may retain the port number, so split if possible host, _, err := net.SplitHostPort(part) if err != nil { @@ -1079,9 +1110,14 @@ const ( // originally came into the server's entry handler OriginalRequestCtxKey caddy.CtxKey = "original_request" - // For referencing underlying net.Conn + // DEPRECATED: not used anymore. + // To refer to the underlying connection, implement a middleware plugin + // that RegisterConnContext during provisioning. ConnCtxKey caddy.CtxKey = "conn" + // used to get the tls connection state in the context, if available + tlsConnectionStateFuncCtxKey caddy.CtxKey = "tls_connection_state_func" + // For tracking whether the client is a trusted proxy TrustedProxyVarKey string = "trusted_proxy" diff --git a/modules/caddyhttp/server_test.go b/modules/caddyhttp/server_test.go index 6ce09974b..eecb392e4 100644 --- a/modules/caddyhttp/server_test.go +++ b/modules/caddyhttp/server_test.go @@ -116,9 +116,7 @@ func BenchmarkServer_LogRequest(b *testing.B) { buf := io.Discard accLog := testLogger(buf.Write) - b.ResetTimer() - - for i := 0; i < b.N; i++ { + for b.Loop() { s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false) } } @@ -139,9 +137,7 @@ func BenchmarkServer_LogRequest_NopLogger(b *testing.B) { accLog := zap.NewNop() - b.ResetTimer() - - for i := 0; i < b.N; i++ { + for b.Loop() { s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false) } } @@ -165,9 +161,7 @@ func BenchmarkServer_LogRequest_WithTrace(b *testing.B) { buf := io.Discard accLog := testLogger(buf.Write) - b.ResetTimer() - - for i := 0; i < b.N; i++ { + for b.Loop() { s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false) } } @@ -303,6 +297,39 @@ func TestServer_DetermineTrustedProxy_TrustedLoopback(t *testing.T) { assert.Equal(t, clientIP, "31.40.0.10") } +func TestServer_DetermineTrustedProxy_UnixSocket(t *testing.T) { + server := &Server{ + ClientIPHeaders: []string{"X-Forwarded-For"}, + TrustedProxiesUnix: true, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "@" + req.Header.Set("X-Forwarded-For", "2.2.2.2, 3.3.3.3") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, "2.2.2.2", clientIP) +} + +func TestServer_DetermineTrustedProxy_UnixSocketStrict(t *testing.T) { + server := &Server{ + ClientIPHeaders: []string{"X-Forwarded-For"}, + TrustedProxiesUnix: true, + TrustedProxiesStrict: 1, + } + + req := httptest.NewRequest("GET", "/", nil) + req.RemoteAddr = "@" + req.Header.Set("X-Forwarded-For", "2.2.2.2, 3.3.3.3") + + trusted, clientIP := determineTrustedProxy(req, server) + + assert.True(t, trusted) + assert.Equal(t, "3.3.3.3", clientIP) +} + func TestServer_DetermineTrustedProxy_UntrustedPrefix(t *testing.T) { loopbackPrefix, _ := netip.ParsePrefix("127.0.0.1/8") diff --git a/modules/caddyhttp/staticresp.go b/modules/caddyhttp/staticresp.go index 12108ac03..d783d1b04 100644 --- a/modules/caddyhttp/staticresp.go +++ b/modules/caddyhttp/staticresp.go @@ -79,7 +79,7 @@ Response headers may be added using the --header flag for each header field. cmd.Flags().StringP("body", "b", "", "The body of the HTTP response") cmd.Flags().BoolP("access-log", "", false, "Enable the access log") cmd.Flags().BoolP("debug", "v", false, "Enable more verbose debug-level logging") - cmd.Flags().StringSliceP("header", "H", []string{}, "Set a header on the response (format: \"Field: value\")") + cmd.Flags().StringArrayP("header", "H", []string{}, "Set a header on the response (format: \"Field: value\")") cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdRespond) }, }) @@ -359,7 +359,7 @@ func cmdRespond(fl caddycmd.Flags) (int, error) { } // build headers map - headers, err := fl.GetStringSlice("header") + headers, err := fl.GetStringArray("header") if err != nil { return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid header flag: %v", err) } diff --git a/modules/caddyhttp/templates/tplcontext.go b/modules/caddyhttp/templates/tplcontext.go index 1b1020f1b..ee553e7a5 100644 --- a/modules/caddyhttp/templates/tplcontext.go +++ b/modules/caddyhttp/templates/tplcontext.go @@ -380,7 +380,7 @@ func (TemplateContext) funcMarkdown(input any) (string, error) { return buf.String(), nil } -// splitFrontMatter parses front matter out from the beginning of input, +// funcSplitFrontMatter parses front matter out from the beginning of input, // and returns the separated key-value pairs and the body/content. input // must be a "stringy" value. func (TemplateContext) funcSplitFrontMatter(input any) (parsedMarkdownDoc, error) { diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index 7ab891fc0..d01f4a431 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -28,6 +28,8 @@ import ( "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" ) +var stringSliceType = reflect.TypeFor[[]string]() + func init() { caddy.RegisterModule(VarsMiddleware{}) caddy.RegisterModule(VarsMatcher{}) @@ -353,7 +355,7 @@ func (MatchVarsRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { "vars_regexp_request_string_string", []*cel.Type{cel.StringType, cel.StringType}, func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType params, err := data.ConvertToNative(refStringList) if err != nil { return nil, err @@ -376,7 +378,7 @@ func (MatchVarsRE) CELLibrary(ctx caddy.Context) (cel.Library, error) { "vars_regexp_request_string_string_string", []*cel.Type{cel.StringType, cel.StringType, cel.StringType}, func(data ref.Val) (RequestMatcherWithError, error) { - refStringList := reflect.TypeOf([]string{}) + refStringList := stringSliceType params, err := data.ConvertToNative(refStringList) if err != nil { return nil, err diff --git a/modules/caddypki/adminapi.go b/modules/caddypki/adminapi.go index c454f6458..463e31f35 100644 --- a/modules/caddypki/adminapi.go +++ b/modules/caddypki/adminapi.go @@ -220,13 +220,13 @@ func (a *adminAPI) getCAFromAPIRequestPath(r *http.Request) (*CA, error) { func rootAndIntermediatePEM(ca *CA) (root, inter []byte, err error) { root, err = pemEncodeCert(ca.RootCertificate().Raw) if err != nil { - return + return root, inter, err } inter, err = pemEncodeCert(ca.IntermediateCertificate().Raw) if err != nil { - return + return root, inter, err } - return + return root, inter, err } // caInfo is the response structure for the CA info API endpoint. diff --git a/modules/caddypki/ca.go b/modules/caddypki/ca.go index 6c48da6f9..5b17518ca 100644 --- a/modules/caddypki/ca.go +++ b/modules/caddypki/ca.go @@ -124,8 +124,6 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error { } if ca.IntermediateLifetime == 0 { ca.IntermediateLifetime = caddy.Duration(defaultIntermediateLifetime) - } else if time.Duration(ca.IntermediateLifetime) >= defaultRootLifetime { - return fmt.Errorf("intermediate certificate lifetime must be less than root certificate lifetime (%s)", defaultRootLifetime) } // load the certs and key that will be used for signing @@ -144,6 +142,10 @@ func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error { if err != nil { return err } + actualRootLifetime := time.Until(rootCert.NotAfter) + if time.Duration(ca.IntermediateLifetime) >= actualRootLifetime { + return fmt.Errorf("intermediate certificate lifetime must be less than actual root certificate lifetime (%s)", actualRootLifetime) + } if ca.Intermediate != nil { interCert, interKey, err = ca.Intermediate.Load() } else { diff --git a/modules/caddytls/acmeissuer.go b/modules/caddytls/acmeissuer.go index 4830570bf..7f13fd71f 100644 --- a/modules/caddytls/acmeissuer.go +++ b/modules/caddytls/acmeissuer.go @@ -244,6 +244,9 @@ func (iss *ACMEIssuer) makeIssuerTemplate(ctx caddy.Context) (certmagic.ACMEIssu template.DNS01Solver = iss.Challenges.DNS.solver } template.ListenHost = iss.Challenges.BindHost + if iss.Challenges.Distributed != nil { + template.DisableDistributedSolvers = !*iss.Challenges.Distributed + } } if iss.PreferredChains != nil { @@ -480,6 +483,20 @@ func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } iss.Challenges.TLSALPN.Disabled = true + case "distributed": + if !d.NextArg() { + return d.ArgErr() + } + if d.Val() != "false" { + return d.Errf("only accepted value is 'false'") + } + if iss.Challenges == nil { + iss.Challenges = new(ChallengesConfig) + } + if iss.Challenges.Distributed == nil { + iss.Challenges.Distributed = new(bool) + } + case "alt_http_port": if !d.NextArg() { return d.ArgErr() diff --git a/modules/caddytls/automation.go b/modules/caddytls/automation.go index c9274878a..e69b5ad2f 100644 --- a/modules/caddytls/automation.go +++ b/modules/caddytls/automation.go @@ -173,6 +173,9 @@ type AutomationPolicy struct { subjects []string magic *certmagic.Config storage certmagic.Storage + + // Whether this policy had explicit managers configured directly on it. + hadExplicitManagers bool } // Provision sets up ap and builds its underlying CertMagic config. @@ -209,9 +212,8 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error { // store them on the policy before putting it on the config // load and provision any cert manager modules - var hadExplicitManagers bool if ap.ManagersRaw != nil { - hadExplicitManagers = true + ap.hadExplicitManagers = true vals, err := tlsApp.ctx.LoadModule(ap, "ManagersRaw") if err != nil { return fmt.Errorf("loading external certificate manager modules: %v", err) @@ -271,9 +273,9 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error { // prevent issuance from Issuers (when Managers don't provide a certificate) if there's no // permission module configured noProtections := ap.isWildcardOrDefault() && !ap.onlyInternalIssuer() && (tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil || tlsApp.Automation.OnDemand.permission == nil) - failClosed := noProtections && !hadExplicitManagers // don't allow on-demand issuance (other than implicit managers) if no managers have been explicitly configured + failClosed := noProtections && !ap.hadExplicitManagers // don't allow on-demand issuance (other than implicit managers) if no managers have been explicitly configured if noProtections { - if !hadExplicitManagers { + if !ap.hadExplicitManagers { // no managers, no explicitly-configured permission module, this is a config error return fmt.Errorf("on-demand TLS cannot be enabled without a permission module to prevent abuse; please refer to documentation for details") } @@ -454,6 +456,22 @@ type ChallengesConfig struct { // Optionally customize the host to which a listener // is bound if required for solving a challenge. BindHost string `json:"bind_host,omitempty"` + + // Whether distributed solving is enabled. This is + // enabled by default, so this is only used to + // disable it, which should only need to be done if + // you cannot reliably or affordably use storage + // backend for writing/distributing challenge info. + // (Applies to HTTP and TLS-ALPN challenges.) + // If set to false, challenges can only be solved + // from the Caddy instance that initiated the + // challenge, with the exception of HTTP challenges + // initiated with the same ACME account that this + // config uses. (Caddy can still solve those challenges + // without explicitly writing the info to storage.) + // + // Default: true + Distributed *bool `json:"distributed,omitempty"` } // HTTPChallengeConfig configures the ACME HTTP challenge. diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index 018ef243f..724271a8e 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -457,7 +457,7 @@ func (p ConnectionPolicy) SettingsEmpty() bool { p.InsecureSecretsLog == "" } -// SettingsEmpty returns true if p's settings (fields +// SettingsEqual returns true if p's settings (fields // except the matchers) are the same as q. func (p ConnectionPolicy) SettingsEqual(q ConnectionPolicy) bool { p.MatchersRaw = nil diff --git a/modules/caddytls/ech.go b/modules/caddytls/ech.go index 7329bf1f2..1b3bacbd2 100644 --- a/modules/caddytls/ech.go +++ b/modules/caddytls/ech.go @@ -291,18 +291,54 @@ func (t *TLS) publishECHConfigs() error { } logger.Debug("publishing ECH config list", + zap.String("publisher", publisherKey), zap.Strings("domains", dnsNamesToPublish), zap.Uint8s("config_ids", configIDs)) // publish this ECH config list with this publisher pubTime := time.Now() err := publisher.PublishECHConfigList(t.ctx, dnsNamesToPublish, echCfgListBin) - if err == nil { - t.logger.Info("published ECH configuration list", + + var publishErrs PublishECHConfigListErrors + if errors.As(err, &publishErrs) { + // at least a partial failure, maybe a complete failure, but we can + // log each error by domain + for innerName, domainErr := range publishErrs { + t.logger.Error("failed to publish ECH configuration list", + zap.String("publisher", publisherKey), + zap.String("domain", innerName), + zap.Uint8s("config_ids", configIDs), + zap.Error(domainErr)) + } + } else if err != nil { + // generic error; assume the entire thing failed, I guess + t.logger.Error("failed publishing ECH configuration list", + zap.String("publisher", publisherKey), zap.Strings("domains", dnsNamesToPublish), zap.Uint8s("config_ids", configIDs), zap.Error(err)) - // update publication history, so that we don't unnecessarily republish every time + } + + if err == nil || (len(publishErrs) > 0 && len(publishErrs) < len(dnsNamesToPublish)) { + // if publication for at least some domains succeeded, we should update our publication + // state for those domains to avoid unnecessarily republishing every time + someAll := "all" + if len(publishErrs) > 0 { + someAll = "some" + } + // make a list of names that published successfully with this publisher + // so that we update only their state in storage, not the failed ones + var successNames []string + for _, name := range dnsNamesToPublish { + if _, ok := publishErrs[name]; !ok { + successNames = append(successNames, name) + } + } + t.logger.Info("successfully published ECH configuration list for "+someAll+" domains", + zap.String("publisher", publisherKey), + zap.Strings("domains", successNames), + zap.Uint8s("config_ids", configIDs)) + for _, cfg := range echCfgList { if cfg.meta.Publications == nil { cfg.meta.Publications = make(publicationHistory) @@ -310,7 +346,7 @@ func (t *TLS) publishECHConfigs() error { if _, ok := cfg.meta.Publications[publisherKey]; !ok { cfg.meta.Publications[publisherKey] = make(map[string]time.Time) } - for _, name := range dnsNamesToPublish { + for _, name := range successNames { cfg.meta.Publications[publisherKey][name] = pubTime } metaBytes, err := json.Marshal(cfg.meta) @@ -323,10 +359,10 @@ func (t *TLS) publishECHConfigs() error { } } } else { - t.logger.Error("publishing ECH configuration list", - zap.Strings("domains", publication.Domains), - zap.Uint8s("config_ids", configIDs), - zap.Error(err)) + t.logger.Error("all domains failed to publish ECH configuration list (see earlier errors)", + zap.String("publisher", publisherKey), + zap.Strings("domains", dnsNamesToPublish), + zap.Uint8s("config_ids", configIDs)) } } } @@ -631,17 +667,19 @@ func (dnsPub ECHDNSPublisher) PublisherKey() string { return string(dnsPub.provider.(caddy.Module).CaddyModule().ID) } -// PublishECHConfigList publishes the given ECH config list to the given DNS names. +// PublishECHConfigList publishes the given ECH config list (as binary) to the given DNS names. +// If there is an error, it may be of type PublishECHConfigListErrors, detailing +// potentially multiple errors keyed by associated innerName. func (dnsPub *ECHDNSPublisher) PublishECHConfigList(ctx context.Context, innerNames []string, configListBin []byte) error { nameservers := certmagic.RecursiveNameservers(nil) // TODO: we could make resolvers configurable + errs := make(PublishECHConfigListErrors) + nextName: for _, domain := range innerNames { zone, err := certmagic.FindZoneByFQDN(ctx, dnsPub.logger, domain, nameservers) if err != nil { - dnsPub.logger.Error("could not determine zone for domain", - zap.String("domain", domain), - zap.Error(err)) + errs[domain] = fmt.Errorf("could not determine zone for domain: %w (domain=%s nameservers=%v)", err, domain, nameservers) continue } @@ -653,9 +691,7 @@ nextName: // we can augment the ech SvcParamKey with any other existing SvcParams recs, err := dnsPub.provider.GetRecords(ctx, zone) if err != nil { - dnsPub.logger.Error("unable to get existing DNS records to publish ECH data to HTTPS DNS record", - zap.String("domain", domain), - zap.Error(err)) + errs[domain] = fmt.Errorf("unable to get existing DNS records to publish ECH data to HTTPS DNS record: %w", err) continue } var httpsRec libdns.ServiceBinding @@ -713,16 +749,14 @@ nextName: }, }) if err != nil { - // TODO: Maybe this should just stop and return the error... - dnsPub.logger.Error("unable to publish ECH data to HTTPS DNS record", - zap.String("domain", domain), - zap.String("zone", zone), - zap.String("dns_record_name", relName), - zap.Error(err)) + errs[domain] = fmt.Errorf("unable to publish ECH data to HTTPS DNS record: %w (zone=%s dns_record_name=%s)", err, zone, relName) continue } } + if len(errs) > 0 { + return errs + } return nil } @@ -956,13 +990,36 @@ type ECHPublisher interface { // It is used to prevent duplicating publications. PublisherKey() string - // Publishes the ECH config list for the given innerNames. Some publishers - // may not need a list of inner/protected names, and can ignore the argument; - // most, however, will want to use it to know which inner names are to be - // associated with the given ECH config list. + // Publishes the ECH config list (as binary) for the given innerNames. Some + // publishers may not need a list of inner/protected names, and can ignore the + // argument; most, however, will want to use it to know which inner names are + // to be associated with the given ECH config list. + // + // Implementations should return an error of type PublishECHConfigListErrors + // when relevant to key errors to their associated innerName, but should never + // return a non-nil PublishECHConfigListErrors when its length is 0. PublishECHConfigList(ctx context.Context, innerNames []string, echConfigList []byte) error } +// PublishECHConfigListErrors is returned by ECHPublishers to describe one or more +// errors publishing an ECH config list from PublishECHConfigList. A non-nil, empty +// value of this type should never be returned. +// nolint:errname // The linter wants "Error" convention, but this is a multi-error type. +type PublishECHConfigListErrors map[string]error + +func (p PublishECHConfigListErrors) Error() string { + var sb strings.Builder + for innerName, err := range p { + if sb.Len() > 0 { + sb.WriteString("; ") + } + sb.WriteString(innerName) + sb.WriteString(": ") + sb.WriteString(err.Error()) + } + return sb.String() +} + type echConfigMeta struct { Created time.Time `json:"created"` Publications publicationHistory `json:"publications"` diff --git a/modules/logging/filewriter.go b/modules/logging/filewriter.go index 0999bbfb2..c3df562cb 100644 --- a/modules/logging/filewriter.go +++ b/modules/logging/filewriter.go @@ -22,9 +22,11 @@ import ( "os" "path/filepath" "strconv" + "strings" + "time" + "github.com/DeRuina/timberjack" "github.com/dustin/go-humanize" - "gopkg.in/natefinch/lumberjack.v2" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -96,6 +98,21 @@ type FileWriter struct { // it will be rotated. RollSizeMB int `json:"roll_size_mb,omitempty"` + // Roll log file after some time + RollInterval time.Duration `json:"roll_interval,omitempty"` + + // Roll log file at fix minutes + // For example []int{0, 30} will roll file at xx:00 and xx:30 each hour + // Invalid value are ignored with a warning on stderr + // See https://github.com/DeRuina/timberjack#%EF%B8%8F-rotation-notes--warnings for caveats + RollAtMinutes []int `json:"roll_minutes,omitempty"` + + // Roll log file at fix time + // For example []string{"00:00", "12:00"} will roll file at 00:00 and 12:00 each day + // Invalid value are ignored with a warning on stderr + // See https://github.com/DeRuina/timberjack#%EF%B8%8F-rotation-notes--warnings for caveats + RollAt []string `json:"roll_at,omitempty"` + // Whether to compress rolled files. Default: true RollCompress *bool `json:"roll_gzip,omitempty"` @@ -109,6 +126,11 @@ type FileWriter struct { // How many days to keep rolled log files. Default: 90 RollKeepDays int `json:"roll_keep_days,omitempty"` + + // Rotated file will have format --.log + // Optional. If unset or invalid, defaults to 2006-01-02T15-04-05.000 (with fallback warning) + // must be a Go time compatible format, see https://pkg.go.dev/time#pkg-constants + BackupTimeFormat string `json:"backup_time_format,omitempty"` } // CaddyModule returns the Caddy module information. @@ -156,7 +178,7 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) { roll := fw.Roll == nil || *fw.Roll // create the file if it does not exist; create with the configured mode, or default - // to restrictive if not set. (lumberjack will reuse the file mode across log rotation) + // to restrictive if not set. (timberjack will reuse the file mode across log rotation) if err := os.MkdirAll(filepath.Dir(fw.Filename), 0o700); err != nil { return nil, err } @@ -166,7 +188,7 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) { } info, err := file.Stat() if roll { - file.Close() // lumberjack will reopen it on its own + file.Close() // timberjack will reopen it on its own } // Ensure already existing files have the right mode, since OpenFile will not set the mode in such case. @@ -201,13 +223,17 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) { if fw.RollKeepDays == 0 { fw.RollKeepDays = 90 } - return &lumberjack.Logger{ - Filename: fw.Filename, - MaxSize: fw.RollSizeMB, - MaxAge: fw.RollKeepDays, - MaxBackups: fw.RollKeep, - LocalTime: fw.RollLocalTime, - Compress: *fw.RollCompress, + return &timberjack.Logger{ + Filename: fw.Filename, + MaxSize: fw.RollSizeMB, + MaxAge: fw.RollKeepDays, + MaxBackups: fw.RollKeep, + LocalTime: fw.RollLocalTime, + Compress: *fw.RollCompress, + RotationInterval: fw.RollInterval, + RotateAtMinutes: fw.RollAtMinutes, + RotateAt: fw.RollAt, + BackupTimeFormat: fw.BackupTimeFormat, }, nil } @@ -314,6 +340,53 @@ func (fw *FileWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } fw.RollKeepDays = int(math.Ceil(keepFor.Hours() / 24)) + case "roll_interval": + var durationStr string + if !d.AllArgs(&durationStr) { + return d.ArgErr() + } + duration, err := time.ParseDuration(durationStr) + if err != nil { + return d.Errf("parsing roll_interval duration: %v", err) + } + fw.RollInterval = duration + + case "roll_minutes": + var minutesArrayStr string + if !d.AllArgs(&minutesArrayStr) { + return d.ArgErr() + } + minutesStr := strings.Split(minutesArrayStr, ",") + minutes := make([]int, len(minutesStr)) + for i := range minutesStr { + ms := strings.Trim(minutesStr[i], " ") + m, err := strconv.Atoi(ms) + if err != nil { + return d.Errf("parsing roll_minutes number: %v", err) + } + minutes[i] = m + } + fw.RollAtMinutes = minutes + + case "roll_at": + var timeArrayStr string + if !d.AllArgs(&timeArrayStr) { + return d.ArgErr() + } + timeStr := strings.Split(timeArrayStr, ",") + times := make([]string, len(timeStr)) + for i := range timeStr { + times[i] = strings.Trim(timeStr[i], " ") + } + fw.RollAt = times + + case "backup_time_format": + var format string + if !d.AllArgs(&format) { + return d.ArgErr() + } + fw.BackupTimeFormat = format + default: return d.Errf("unrecognized subdirective '%s'", d.Val()) } diff --git a/modules/logging/filterencoder.go b/modules/logging/filterencoder.go index c46df0788..01333e195 100644 --- a/modules/logging/filterencoder.go +++ b/modules/logging/filterencoder.go @@ -152,6 +152,9 @@ func (fe *FilterEncoder) ConfigureDefaultFormat(wo caddy.WriterOpener) error { func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { d.Next() // consume encoder name + // Track regexp filters for automatic merging + regexpFilters := make(map[string][]*RegexpFilter) + // parse a field parseField := func() error { if fe.FieldsRaw == nil { @@ -171,6 +174,23 @@ func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if !ok { return d.Errf("module %s (%T) is not a logging.LogFieldFilter", moduleID, unm) } + + // Special handling for regexp filters to support multiple instances + if regexpFilter, isRegexp := filter.(*RegexpFilter); isRegexp { + regexpFilters[field] = append(regexpFilters[field], regexpFilter) + return nil // Don't set FieldsRaw yet, we'll merge them later + } + + // Check if we're trying to add a non-regexp filter to a field that already has regexp filters + if _, hasRegexpFilters := regexpFilters[field]; hasRegexpFilters { + return d.Errf("cannot mix regexp filters with other filter types for field %s", field) + } + + // Check if field already has a filter and it's not regexp-related + if _, exists := fe.FieldsRaw[field]; exists { + return d.Errf("field %s already has a filter; multiple non-regexp filters per field are not supported", field) + } + fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(filter, "filter", filterName, nil) return nil } @@ -210,6 +230,25 @@ func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } } } + + // After parsing all fields, merge multiple regexp filters into MultiRegexpFilter + for field, filters := range regexpFilters { + if len(filters) == 1 { + // Single regexp filter, use the original RegexpFilter + fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(filters[0], "filter", "regexp", nil) + } else { + // Multiple regexp filters, merge into MultiRegexpFilter + multiFilter := &MultiRegexpFilter{} + for _, regexpFilter := range filters { + err := multiFilter.AddOperation(regexpFilter.RawRegexp, regexpFilter.Value) + if err != nil { + return fmt.Errorf("adding regexp operation for field %s: %v", field, err) + } + } + fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(multiFilter, "filter", "multi_regexp", nil) + } + } + return nil } diff --git a/modules/logging/filters.go b/modules/logging/filters.go index 79d908fca..a2ce6502f 100644 --- a/modules/logging/filters.go +++ b/modules/logging/filters.go @@ -41,6 +41,7 @@ func init() { caddy.RegisterModule(CookieFilter{}) caddy.RegisterModule(RegexpFilter{}) caddy.RegisterModule(RenameFilter{}) + caddy.RegisterModule(MultiRegexpFilter{}) } // LogFieldFilter can filter (or manipulate) @@ -255,7 +256,7 @@ func (m IPMaskFilter) Filter(in zapcore.Field) zapcore.Field { func (m IPMaskFilter) mask(s string) string { output := "" - for _, value := range strings.Split(s, ",") { + for value := range strings.SplitSeq(s, ",") { value = strings.TrimSpace(value) host, port, err := net.SplitHostPort(value) if err != nil { @@ -625,6 +626,222 @@ func (f *RegexpFilter) Filter(in zapcore.Field) zapcore.Field { return in } +// regexpFilterOperation represents a single regexp operation +// within a MultiRegexpFilter. +type regexpFilterOperation struct { + // The regular expression pattern defining what to replace. + RawRegexp string `json:"regexp,omitempty"` + + // The value to use as replacement + Value string `json:"value,omitempty"` + + regexp *regexp.Regexp +} + +// MultiRegexpFilter is a Caddy log field filter that +// can apply multiple regular expression replacements to +// the same field. This filter processes operations in the +// order they are defined, applying each regexp replacement +// sequentially to the result of the previous operation. +// +// This allows users to define multiple regexp filters for +// the same field without them overwriting each other. +// +// Security considerations: +// - Uses Go's regexp package (RE2 engine) which is safe from ReDoS attacks +// - Validates all patterns during provisioning +// - Limits the maximum number of operations to prevent resource exhaustion +// - Sanitizes input to prevent injection attacks +type MultiRegexpFilter struct { + // A list of regexp operations to apply in sequence. + // Maximum of 50 operations allowed for security and performance. + Operations []regexpFilterOperation `json:"operations"` +} + +// Security constants +const ( + maxRegexpOperations = 50 // Maximum operations to prevent resource exhaustion + maxPatternLength = 1000 // Maximum pattern length to prevent abuse +) + +// CaddyModule returns the Caddy module information. +func (MultiRegexpFilter) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.logging.encoders.filter.multi_regexp", + New: func() caddy.Module { return new(MultiRegexpFilter) }, + } +} + +// UnmarshalCaddyfile sets up the module from Caddyfile tokens. +// Syntax: +// +// multi_regexp { +// regexp +// regexp +// ... +// } +func (f *MultiRegexpFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { + d.Next() // consume filter name + for d.NextBlock(0) { + switch d.Val() { + case "regexp": + // Security check: limit number of operations + if len(f.Operations) >= maxRegexpOperations { + return d.Errf("too many regexp operations (maximum %d allowed)", maxRegexpOperations) + } + + op := regexpFilterOperation{} + if !d.NextArg() { + return d.ArgErr() + } + op.RawRegexp = d.Val() + + // Security validation: check pattern length + if len(op.RawRegexp) > maxPatternLength { + return d.Errf("regexp pattern too long (maximum %d characters)", maxPatternLength) + } + + // Security validation: basic pattern validation + if op.RawRegexp == "" { + return d.Errf("regexp pattern cannot be empty") + } + + if !d.NextArg() { + return d.ArgErr() + } + op.Value = d.Val() + f.Operations = append(f.Operations, op) + default: + return d.Errf("unrecognized subdirective %s", d.Val()) + } + } + + // Security check: ensure at least one operation is defined + if len(f.Operations) == 0 { + return d.Err("multi_regexp filter requires at least one regexp operation") + } + + return nil +} + +// Provision compiles all regexp patterns with security validation. +func (f *MultiRegexpFilter) Provision(ctx caddy.Context) error { + // Security check: validate operation count + if len(f.Operations) > maxRegexpOperations { + return fmt.Errorf("too many regexp operations: %d (maximum %d allowed)", len(f.Operations), maxRegexpOperations) + } + + if len(f.Operations) == 0 { + return fmt.Errorf("multi_regexp filter requires at least one operation") + } + + for i := range f.Operations { + // Security validation: pattern length check + if len(f.Operations[i].RawRegexp) > maxPatternLength { + return fmt.Errorf("regexp pattern %d too long: %d characters (maximum %d)", i, len(f.Operations[i].RawRegexp), maxPatternLength) + } + + // Security validation: empty pattern check + if f.Operations[i].RawRegexp == "" { + return fmt.Errorf("regexp pattern %d cannot be empty", i) + } + + // Compile and validate the pattern (uses RE2 engine - safe from ReDoS) + r, err := regexp.Compile(f.Operations[i].RawRegexp) + if err != nil { + return fmt.Errorf("compiling regexp pattern %d (%s): %v", i, f.Operations[i].RawRegexp, err) + } + f.Operations[i].regexp = r + } + return nil +} + +// Validate ensures the filter is properly configured with security checks. +func (f *MultiRegexpFilter) Validate() error { + if len(f.Operations) == 0 { + return fmt.Errorf("multi_regexp filter requires at least one operation") + } + + if len(f.Operations) > maxRegexpOperations { + return fmt.Errorf("too many regexp operations: %d (maximum %d allowed)", len(f.Operations), maxRegexpOperations) + } + + for i, op := range f.Operations { + if op.RawRegexp == "" { + return fmt.Errorf("regexp pattern %d cannot be empty", i) + } + if len(op.RawRegexp) > maxPatternLength { + return fmt.Errorf("regexp pattern %d too long: %d characters (maximum %d)", i, len(op.RawRegexp), maxPatternLength) + } + if op.regexp == nil { + return fmt.Errorf("regexp pattern %d not compiled (call Provision first)", i) + } + } + return nil +} + +// Filter applies all regexp operations sequentially to the input field. +// Input is sanitized and validated for security. +func (f *MultiRegexpFilter) Filter(in zapcore.Field) zapcore.Field { + if array, ok := in.Interface.(caddyhttp.LoggableStringArray); ok { + newArray := make(caddyhttp.LoggableStringArray, len(array)) + for i, s := range array { + newArray[i] = f.processString(s) + } + in.Interface = newArray + } else { + in.String = f.processString(in.String) + } + + return in +} + +// processString applies all regexp operations to a single string with input validation. +func (f *MultiRegexpFilter) processString(s string) string { + // Security: validate input string length to prevent resource exhaustion + const maxInputLength = 1000000 // 1MB max input size + if len(s) > maxInputLength { + // Log warning but continue processing (truncated) + s = s[:maxInputLength] + } + + result := s + for _, op := range f.Operations { + // Each regexp operation is applied sequentially + // Using RE2 engine which is safe from ReDoS attacks + result = op.regexp.ReplaceAllString(result, op.Value) + + // Ensure result doesn't exceed max length after each operation + if len(result) > maxInputLength { + result = result[:maxInputLength] + } + } + return result +} + +// AddOperation adds a single regexp operation to the filter with validation. +// This is used when merging multiple RegexpFilter instances. +func (f *MultiRegexpFilter) AddOperation(rawRegexp, value string) error { + // Security checks + if len(f.Operations) >= maxRegexpOperations { + return fmt.Errorf("cannot add operation: maximum %d operations allowed", maxRegexpOperations) + } + + if rawRegexp == "" { + return fmt.Errorf("regexp pattern cannot be empty") + } + + if len(rawRegexp) > maxPatternLength { + return fmt.Errorf("regexp pattern too long: %d characters (maximum %d)", len(rawRegexp), maxPatternLength) + } + + f.Operations = append(f.Operations, regexpFilterOperation{ + RawRegexp: rawRegexp, + Value: value, + }) + return nil +} + // RenameFilter is a Caddy log field filter that // renames the field's key with the indicated name. type RenameFilter struct { @@ -664,6 +881,7 @@ var ( _ LogFieldFilter = (*CookieFilter)(nil) _ LogFieldFilter = (*RegexpFilter)(nil) _ LogFieldFilter = (*RenameFilter)(nil) + _ LogFieldFilter = (*MultiRegexpFilter)(nil) _ caddyfile.Unmarshaler = (*DeleteFilter)(nil) _ caddyfile.Unmarshaler = (*HashFilter)(nil) @@ -673,9 +891,12 @@ var ( _ caddyfile.Unmarshaler = (*CookieFilter)(nil) _ caddyfile.Unmarshaler = (*RegexpFilter)(nil) _ caddyfile.Unmarshaler = (*RenameFilter)(nil) + _ caddyfile.Unmarshaler = (*MultiRegexpFilter)(nil) _ caddy.Provisioner = (*IPMaskFilter)(nil) _ caddy.Provisioner = (*RegexpFilter)(nil) + _ caddy.Provisioner = (*MultiRegexpFilter)(nil) _ caddy.Validator = (*QueryFilter)(nil) + _ caddy.Validator = (*MultiRegexpFilter)(nil) ) diff --git a/modules/logging/filters_test.go b/modules/logging/filters_test.go index a929617d7..42aa29757 100644 --- a/modules/logging/filters_test.go +++ b/modules/logging/filters_test.go @@ -1,6 +1,8 @@ package logging import ( + "fmt" + "strings" "testing" "go.uber.org/zap/zapcore" @@ -239,3 +241,198 @@ func TestHashFilterMultiValue(t *testing.T) { t.Fatalf("field entry 1 has not been filtered: %s", arr[1]) } } + +func TestMultiRegexpFilterSingleOperation(t *testing.T) { + f := MultiRegexpFilter{ + Operations: []regexpFilterOperation{ + {RawRegexp: `secret`, Value: "REDACTED"}, + }, + } + err := f.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("unexpected error provisioning: %v", err) + } + + out := f.Filter(zapcore.Field{String: "foo-secret-bar"}) + if out.String != "foo-REDACTED-bar" { + t.Fatalf("field has not been filtered: %s", out.String) + } +} + +func TestMultiRegexpFilterMultipleOperations(t *testing.T) { + f := MultiRegexpFilter{ + Operations: []regexpFilterOperation{ + {RawRegexp: `secret`, Value: "REDACTED"}, + {RawRegexp: `password`, Value: "HIDDEN"}, + {RawRegexp: `token`, Value: "XXX"}, + }, + } + err := f.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("unexpected error provisioning: %v", err) + } + + // Test sequential application + out := f.Filter(zapcore.Field{String: "my-secret-password-token-data"}) + expected := "my-REDACTED-HIDDEN-XXX-data" + if out.String != expected { + t.Fatalf("field has not been filtered correctly: got %s, expected %s", out.String, expected) + } +} + +func TestMultiRegexpFilterMultiValue(t *testing.T) { + f := MultiRegexpFilter{ + Operations: []regexpFilterOperation{ + {RawRegexp: `secret`, Value: "REDACTED"}, + {RawRegexp: `\d+`, Value: "NUM"}, + }, + } + err := f.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("unexpected error provisioning: %v", err) + } + + out := f.Filter(zapcore.Field{Interface: caddyhttp.LoggableStringArray{ + "foo-secret-123", + "bar-secret-456", + }}) + arr, ok := out.Interface.(caddyhttp.LoggableStringArray) + if !ok { + t.Fatalf("field is wrong type: %T", out.Interface) + } + if arr[0] != "foo-REDACTED-NUM" { + t.Fatalf("field entry 0 has not been filtered: %s", arr[0]) + } + if arr[1] != "bar-REDACTED-NUM" { + t.Fatalf("field entry 1 has not been filtered: %s", arr[1]) + } +} + +func TestMultiRegexpFilterAddOperation(t *testing.T) { + f := MultiRegexpFilter{} + err := f.AddOperation("secret", "REDACTED") + if err != nil { + t.Fatalf("unexpected error adding operation: %v", err) + } + err = f.AddOperation("password", "HIDDEN") + if err != nil { + t.Fatalf("unexpected error adding operation: %v", err) + } + err = f.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("unexpected error provisioning: %v", err) + } + + if len(f.Operations) != 2 { + t.Fatalf("expected 2 operations, got %d", len(f.Operations)) + } + + out := f.Filter(zapcore.Field{String: "my-secret-password"}) + expected := "my-REDACTED-HIDDEN" + if out.String != expected { + t.Fatalf("field has not been filtered correctly: got %s, expected %s", out.String, expected) + } +} + +func TestMultiRegexpFilterSecurityLimits(t *testing.T) { + f := MultiRegexpFilter{} + + // Test maximum operations limit + for i := 0; i < 51; i++ { + err := f.AddOperation(fmt.Sprintf("pattern%d", i), "replacement") + if i < 50 { + if err != nil { + t.Fatalf("unexpected error adding operation %d: %v", i, err) + } + } else { + if err == nil { + t.Fatalf("expected error when adding operation %d (exceeds limit)", i) + } + } + } + + // Test empty pattern validation + f2 := MultiRegexpFilter{} + err := f2.AddOperation("", "replacement") + if err == nil { + t.Fatalf("expected error for empty pattern") + } + + // Test pattern length limit + f3 := MultiRegexpFilter{} + longPattern := strings.Repeat("a", 1001) + err = f3.AddOperation(longPattern, "replacement") + if err == nil { + t.Fatalf("expected error for pattern exceeding length limit") + } +} + +func TestMultiRegexpFilterValidation(t *testing.T) { + // Test validation with empty operations + f := MultiRegexpFilter{} + err := f.Validate() + if err == nil { + t.Fatalf("expected validation error for empty operations") + } + + // Test validation with valid operations + err = f.AddOperation("valid", "replacement") + if err != nil { + t.Fatalf("unexpected error adding operation: %v", err) + } + err = f.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("unexpected error provisioning: %v", err) + } + err = f.Validate() + if err != nil { + t.Fatalf("unexpected validation error: %v", err) + } +} + +func TestMultiRegexpFilterInputSizeLimit(t *testing.T) { + f := MultiRegexpFilter{ + Operations: []regexpFilterOperation{ + {RawRegexp: `test`, Value: "REPLACED"}, + }, + } + err := f.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("unexpected error provisioning: %v", err) + } + + // Test with very large input (should be truncated) + largeInput := strings.Repeat("test", 300000) // Creates ~1.2MB string + out := f.Filter(zapcore.Field{String: largeInput}) + + // The input should be truncated to 1MB and still processed + if len(out.String) > 1000000 { + t.Fatalf("output string not truncated: length %d", len(out.String)) + } + + // Should still contain replacements within the truncated portion + if !strings.Contains(out.String, "REPLACED") { + t.Fatalf("replacements not applied to truncated input") + } +} + +func TestMultiRegexpFilterOverlappingPatterns(t *testing.T) { + f := MultiRegexpFilter{ + Operations: []regexpFilterOperation{ + {RawRegexp: `secret.*password`, Value: "SENSITIVE"}, + {RawRegexp: `password`, Value: "HIDDEN"}, + }, + } + err := f.Provision(caddy.Context{}) + if err != nil { + t.Fatalf("unexpected error provisioning: %v", err) + } + + // The first pattern should match and replace the entire "secret...password" portion + // Then the second pattern should not find "password" anymore since it was already replaced + out := f.Filter(zapcore.Field{String: "my-secret-data-password-end"}) + expected := "my-SENSITIVE-end" + if out.String != expected { + t.Fatalf("field has not been filtered correctly: got %s, expected %s", out.String, expected) + } +} diff --git a/modules/logging/netwriter.go b/modules/logging/netwriter.go index 7d8481e3c..0ca866a8e 100644 --- a/modules/logging/netwriter.go +++ b/modules/logging/netwriter.go @@ -172,7 +172,7 @@ func (reconn *redialerConn) Write(b []byte) (n int, err error) { reconn.connMu.RUnlock() if conn != nil { if n, err = conn.Write(b); err == nil { - return + return n, err } } @@ -184,7 +184,7 @@ func (reconn *redialerConn) Write(b []byte) (n int, err error) { // one of them might have already re-dialed by now; try writing again if reconn.Conn != nil { if n, err = reconn.Conn.Write(b); err == nil { - return + return n, err } } @@ -198,7 +198,7 @@ func (reconn *redialerConn) Write(b []byte) (n int, err error) { if err2 != nil { // logger socket still offline; instead of discarding the log, dump it to stderr os.Stderr.Write(b) - return + return n, err } if n, err = conn2.Write(b); err == nil { if reconn.Conn != nil { @@ -211,7 +211,7 @@ func (reconn *redialerConn) Write(b []byte) (n int, err error) { os.Stderr.Write(b) } - return + return n, err } func (reconn *redialerConn) dial() (net.Conn, error) { diff --git a/replacer.go b/replacer.go index 297dd935c..1a2aa5771 100644 --- a/replacer.go +++ b/replacer.go @@ -335,7 +335,7 @@ type replacementProvider interface { replace(key string) (any, bool) } -// fileReplacementsProvider handles {file.*} replacements, +// fileReplacementProvider handles {file.*} replacements, // reading a file from disk and replacing with its contents. type fileReplacementProvider struct{} @@ -360,7 +360,7 @@ func (f fileReplacementProvider) replace(key string) (any, bool) { return string(body), true } -// globalDefaultReplacementsProvider handles replacements +// globalDefaultReplacementProvider handles replacements // that can be used in any context, such as system variables, // time, or environment variables. type globalDefaultReplacementProvider struct{} diff --git a/replacer_test.go b/replacer_test.go index 1c1a7048f..4f20bede3 100644 --- a/replacer_test.go +++ b/replacer_test.go @@ -516,7 +516,7 @@ func BenchmarkReplacer(b *testing.B) { }, } { b.Run(bm.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { + for b.Loop() { rep.ReplaceAll(bm.input, bm.empty) } }) diff --git a/sigtrap_posix.go b/sigtrap_posix.go index 2c6306121..018a81165 100644 --- a/sigtrap_posix.go +++ b/sigtrap_posix.go @@ -18,6 +18,7 @@ package caddy import ( "context" + "errors" "os" "os/signal" "syscall" @@ -48,7 +49,31 @@ func trapSignalsPosix() { exitProcessFromSignal("SIGTERM") case syscall.SIGUSR1: - Log().Info("not implemented", zap.String("signal", "SIGUSR1")) + logger := Log().With(zap.String("signal", "SIGUSR1")) + // If we know the last source config file/adapter (set when starting + // via `caddy run --config --adapter `), attempt + // to reload from that source. Otherwise, ignore the signal. + file, adapter, reloadCallback := getLastConfig() + if file == "" { + logger.Info("last config unknown, ignored SIGUSR1") + break + } + logger = logger.With( + zap.String("file", file), + zap.String("adapter", adapter)) + if reloadCallback == nil { + logger.Warn("no reload helper available, ignored SIGUSR1") + break + } + logger.Info("reloading config from last-known source") + if err := reloadCallback(file, adapter); errors.Is(err, errReloadFromSourceUnavailable) { + // No reload helper available (likely not started via caddy run). + logger.Warn("reload from source unavailable in this process; ignored SIGUSR1") + } else if err != nil { + logger.Error("failed to reload config from file", zap.Error(err)) + } else { + logger.Info("successfully reloaded config from file") + } case syscall.SIGUSR2: Log().Info("not implemented", zap.String("signal", "SIGUSR2")) diff --git a/usagepool.go b/usagepool.go index e011be961..a6466b9b1 100644 --- a/usagepool.go +++ b/usagepool.go @@ -106,7 +106,7 @@ func (up *UsagePool) LoadOrNew(key any, construct Constructor) (value any, loade } upv.Unlock() } - return + return value, loaded, err } // LoadOrStore loads the value associated with key from the pool if it @@ -134,7 +134,7 @@ func (up *UsagePool) LoadOrStore(key, val any) (value any, loaded bool) { up.Unlock() value = val } - return + return value, loaded } // Range iterates the pool similarly to how sync.Map.Range() does: @@ -191,7 +191,7 @@ func (up *UsagePool) Delete(key any) (deleted bool, err error) { upv.value, upv.refs)) } } - return + return deleted, err } // References returns the number of references (count of usages) to a