diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 557b4bac1..6f13031b9 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -48,9 +48,9 @@ We consider publicly-registered domain names to be public information. This nece It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding. -When you are ready, please email Matt Holt (the author) directly: matt at dyanim dot com. +When you are ready, please submit a [new private vulnerability report](https://github.com/caddyserver/caddy/security/advisories/new). -Please don't encrypt the email body. It only makes the process more complicated. +Please don't encrypt the message. It only makes the process more complicated. Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 64284b907..aeee0fd93 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,8 @@ updates: directory: "/" schedule: interval: "monthly" + + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f3e98db6..03e931830 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,9 @@ env: # https://github.com/actions/setup-go/issues/491 GOTOOLCHAIN: local +permissions: + contents: read + jobs: test: strategy: @@ -55,13 +58,21 @@ jobs: SUCCESS: 'True' runs-on: ${{ matrix.OS_LABEL }} - + permissions: + contents: read + pull-requests: read + actions: write # to allow uploading artifacts and cache steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: ${{ matrix.GO_SEMVER }} check-latest: true @@ -108,7 +119,7 @@ jobs: ./caddy stop - name: Publish Build Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }} path: ${{ matrix.CADDY_BIN_PATH }} @@ -142,12 +153,21 @@ jobs: s390x-test: name: test (s390x on IBM Z) + permissions: + contents: read + pull-requests: read runs-on: ubuntu-latest if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]' continue-on-error: true # August 2020: s390x VM is down due to weather and power issues steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + allowed-endpoints: ci-s390x.caddyserver.com:22 + - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Run Tests run: | set +e @@ -194,17 +214,25 @@ jobs: goreleaser-check: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]' steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: goreleaser/goreleaser-action@v6 + - uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 with: version: latest args: check - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: "~1.24" check-latest: true @@ -212,7 +240,7 @@ jobs: run: | go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest xcaddy version - - uses: goreleaser/goreleaser-action@v6 + - uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 with: version: latest args: build --single-target --snapshot diff --git a/.github/workflows/cross-build.yml b/.github/workflows/cross-build.yml index 372cc7652..fe6b44c68 100644 --- a/.github/workflows/cross-build.yml +++ b/.github/workflows/cross-build.yml @@ -14,6 +14,9 @@ env: # https://github.com/actions/setup-go/issues/491 GOTOOLCHAIN: local +permissions: + contents: read + jobs: build: strategy: @@ -40,13 +43,21 @@ jobs: GO_SEMVER: '~1.24.1' runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read continue-on-error: true steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: ${{ matrix.GO_SEMVER }} check-latest: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c5c89b502..7531d6a8e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -44,14 +44,19 @@ jobs: runs-on: ${{ matrix.OS_LABEL }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: '~1.24' check-latest: true - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 with: version: latest @@ -62,10 +67,39 @@ jobs: # only-new-issues: true govulncheck: + permissions: + contents: read + pull-requests: read runs-on: ubuntu-latest steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + - name: govulncheck - uses: golang/govulncheck-action@v1 + uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 with: go-version-input: '~1.24.1' check-latest: true + + dependency-review: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - name: 'Checkout Repository' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: 'Dependency Review' + uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 + with: + comment-summary-in-pr: on-failure + # https://github.com/actions/dependency-review-action/issues/430#issuecomment-1468975566 + base-ref: ${{ github.event.pull_request.base.sha || 'master' }} + head-ref: ${{ github.event.pull_request.head.sha || github.ref }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b508ba468..31c60df75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,9 @@ env: # https://github.com/actions/setup-go/issues/491 GOTOOLCHAIN: local +permissions: + contents: read + jobs: release: name: Release @@ -35,19 +38,24 @@ jobs: contents: write steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: ${{ matrix.GO_SEMVER }} check-latest: true # Force fetch upstream tags -- because 65 minutes - # tl;dr: actions/checkout@v4 runs this line: + # tl;dr: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 runs this line: # git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/ # which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran: # git fetch --prune --unshallow @@ -101,11 +109,11 @@ jobs: git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1 - name: Install Cosign - uses: sigstore/cosign-installer@main + uses: sigstore/cosign-installer@e9a05e6d32d7ed22b5656cd874ef31af58d05bfa # main - name: Cosign version run: cosign version - name: Install Syft - uses: anchore/sbom-action/download-syft@main + uses: anchore/sbom-action/download-syft@9246b90769f852b3a8921f330c59e0b3f439d6e9 # main - name: Syft version run: syft version - name: Install xcaddy @@ -114,7 +122,7 @@ jobs: xcaddy version # GoReleaser will take care of publishing those artifacts into the release - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 with: version: latest args: release --clean --timeout 60m diff --git a/.github/workflows/release_published.yml b/.github/workflows/release_published.yml index 491dae75d..e33cd8a96 100644 --- a/.github/workflows/release_published.yml +++ b/.github/workflows/release_published.yml @@ -5,6 +5,9 @@ on: release: types: [published] +permissions: + contents: read + jobs: release: name: Release Published @@ -13,12 +16,20 @@ jobs: os: - ubuntu-latest runs-on: ${{ matrix.os }} - + permissions: + contents: read + pull-requests: read + actions: write steps: # See https://github.com/peter-evans/repository-dispatch + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + - name: Trigger event on caddyserver/dist - uses: peter-evans/repository-dispatch@v3 + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 with: token: ${{ secrets.REPO_DISPATCH_TOKEN }} repository: caddyserver/dist @@ -26,7 +37,7 @@ jobs: client-payload: '{"tag": "${{ github.event.release.tag_name }}"}' - name: Trigger event on caddyserver/caddy-docker - uses: peter-evans/repository-dispatch@v3 + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 with: token: ${{ secrets.REPO_DISPATCH_TOKEN }} repository: caddyserver/caddy-docker diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000..fad8621d2 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,86 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: OpenSSF Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '20 2 * * 5' + push: + branches: [ "master", "2.*" ] + pull_request: + branches: [ "master", "2.*" ] + + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. + if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + egress-policy: audit + + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore + # file_mode: git + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + with: + sarif_file: results.sarif diff --git a/.golangci.yml b/.golangci.yml index aecff563e..0f1082e72 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,27 +1,15 @@ -linters-settings: - errcheck: - exclude-functions: - - fmt.* - - (go.uber.org/zap/zapcore.ObjectEncoder).AddObject - - (go.uber.org/zap/zapcore.ObjectEncoder).AddArray - gci: - sections: - - standard # Standard section: captures all standard packages. - - default # Default section: contains all imports that could not be matched to another section type. - - prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break. - - prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix. - # Skip generated files. - # Default: true - skip-generated: true - # Enable custom order of sections. - # If `true`, make the section order the same as the order of `sections`. - # Default: false - custom-order: true - exhaustive: - ignore-enum-types: reflect.Kind|svc.Cmd - +version: "2" +run: + issues-exit-code: 1 + tests: false +output: + formats: + text: + path: stdout + print-linter-name: true + print-issued-lines: true linters: - disable-all: true + default: none enable: - asasalint - asciicheck @@ -35,148 +23,96 @@ linters: - errcheck - errname - exhaustive - - gci - - gofmt - - goimports - - gofumpt - gosec - - gosimple - govet - - ineffassign - importas + - ineffassign - misspell - prealloc - promlinter - sloglint - sqlclosecheck - staticcheck - - tenv - testableexamples - testifylint - tparallel - - typecheck - unconvert - unused - wastedassign - whitespace - zerologlint - # these are implicitly disabled: - # - containedctx - # - contextcheck - # - cyclop - # - depguard - # - errchkjson - # - errorlint - # - exhaustruct - # - execinquery - # - exhaustruct - # - forbidigo - # - forcetypeassert - # - funlen - # - ginkgolinter - # - gocheckcompilerdirectives - # - gochecknoglobals - # - gochecknoinits - # - gochecksumtype - # - gocognit - # - goconst - # - gocritic - # - gocyclo - # - godot - # - godox - # - goerr113 - # - goheader - # - gomnd - # - gomoddirectives - # - gomodguard - # - goprintffuncname - # - gosmopolitan - # - grouper - # - inamedparam - # - interfacebloat - # - ireturn - # - lll - # - loggercheck - # - maintidx - # - makezero - # - mirror - # - musttag - # - nakedret - # - nestif - # - nilerr - # - nilnil - # - nlreturn - # - noctx - # - nolintlint - # - nonamedreturns - # - nosprintfhostport - # - paralleltest - # - perfsprint - # - predeclared - # - protogetter - # - reassign - # - revive - # - rowserrcheck - # - stylecheck - # - tagalign - # - tagliatelle - # - testpackage - # - thelper - # - unparam - # - usestdlibvars - # - varnamelen - # - wrapcheck - # - wsl - -run: - # default concurrency is a available CPU number. - # concurrency: 4 # explicitly omit this value to fully utilize available resources. - timeout: 5m - issues-exit-code: 1 - tests: false - -# output configuration options -output: - formats: - - format: 'colored-line-number' - print-issued-lines: true - print-linter-name: true - -issues: - exclude-rules: - - text: 'G115' # TODO: Either we should fix the issues or nuke the linter if it's bad - linters: - - gosec - # we aren't calling unknown URL - - text: 'G107' # G107: Url provided to HTTP request as taint input - linters: - - gosec - # as a web server that's expected to handle any template, this is totally in the hands of the user. - - text: 'G203' # G203: Use of unescaped data in HTML templates - linters: - - gosec - # we're shelling out to known commands, not relying on user-defined input. - - text: 'G204' # G204: Audit use of command execution - linters: - - gosec - # the choice of weakrand is deliberate, hence the named import "weakrand" - - path: modules/caddyhttp/reverseproxy/selectionpolicies.go - text: 'G404' # G404: Insecure random number source (rand) - linters: - - gosec - - path: modules/caddyhttp/reverseproxy/streaming.go - text: 'G404' # G404: Insecure random number source (rand) - linters: - - gosec - - path: modules/logging/filters.go - linters: - - dupl - - path: modules/caddyhttp/matchers.go - linters: - - dupl - - path: modules/caddyhttp/vars.go - linters: - - dupl - - path: _test\.go - linters: - - errcheck + settings: + staticcheck: + checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1006", "-QF1008"] # default, and exclude 1 more undesired check + errcheck: + exclude-functions: + - fmt.* + - (go.uber.org/zap/zapcore.ObjectEncoder).AddObject + - (go.uber.org/zap/zapcore.ObjectEncoder).AddArray + exhaustive: + ignore-enum-types: reflect.Kind|svc.Cmd + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - gosec + text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad + - linters: + - gosec + text: G107 # we aren't calling unknown URL + - linters: + - gosec + text: G203 # as a web server that's expected to handle any template, this is totally in the hands of the user. + - linters: + - gosec + text: G204 # we're shelling out to known commands, not relying on user-defined input. + - linters: + - gosec + # the choice of weakrand is deliberate, hence the named import "weakrand" + path: modules/caddyhttp/reverseproxy/selectionpolicies.go + text: G404 + - linters: + - gosec + path: modules/caddyhttp/reverseproxy/streaming.go + text: G404 + - linters: + - dupl + path: modules/logging/filters.go + - linters: + - dupl + path: modules/caddyhttp/matchers.go + - linters: + - dupl + path: modules/caddyhttp/vars.go + - linters: + - errcheck + path: _test\.go + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + settings: + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break. + - prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix. + custom-order: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..a38a13da3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: +- repo: https://github.com/gitleaks/gitleaks + rev: v8.16.3 + hooks: + - id: gitleaks +- repo: https://github.com/golangci/golangci-lint + rev: v1.52.2 + hooks: + - id: golangci-lint-config-verify + - id: golangci-lint + - id: golangci-lint-fmt +- repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 3.0.0 + hooks: + - id: shellcheck +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace diff --git a/README.md b/README.md index 4bebaafdb..e5d5a83d6 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@
Caddy is an extensible server platform that uses TLS by default.
+
diff --git a/caddy.go b/caddy.go
index 1a6a11629..012480d11 100644
--- a/caddy.go
+++ b/caddy.go
@@ -408,11 +408,23 @@ func run(newCfg *Config, start bool) (Context, error) {
return ctx, nil
}
+ defer func() {
+ // if newCfg fails to start completely, clean up the already provisioned modules
+ // partially copied from provisionContext
+ if err != nil {
+ globalMetrics.configSuccess.Set(0)
+ ctx.cfg.cancelFunc()
+
+ if currentCtx.cfg != nil {
+ certmagic.Default.Storage = currentCtx.cfg.storage
+ }
+ }
+ }()
+
// Provision any admin routers which may need to access
// some of the other apps at runtime
err = ctx.cfg.Admin.provisionAdminRouters(ctx)
if err != nil {
- globalMetrics.configSuccess.Set(0)
return ctx, err
}
@@ -438,7 +450,6 @@ func run(newCfg *Config, start bool) (Context, error) {
return nil
}()
if err != nil {
- globalMetrics.configSuccess.Set(0)
return ctx, err
}
globalMetrics.configSuccess.Set(1)
@@ -449,7 +460,8 @@ func run(newCfg *Config, start bool) (Context, error) {
// now that the user's config is running, finish setting up anything else,
// such as remote admin endpoint, config loader, etc.
- return ctx, finishSettingUp(ctx, ctx.cfg)
+ err = finishSettingUp(ctx, ctx.cfg)
+ return ctx, err
}
// provisionContext creates a new context from the given configuration and provisions
@@ -1104,9 +1116,15 @@ func (e Event) Origin() Module { return e.origin } // Returns the module t
// CloudEvents spec.
func (e Event) CloudEvent() CloudEvent {
dataJSON, _ := json.Marshal(e.Data)
+ var source string
+ if e.Origin() == nil {
+ source = "caddy"
+ } else {
+ source = string(e.Origin().CaddyModule().ID)
+ }
return CloudEvent{
ID: e.id.String(),
- Source: e.origin.CaddyModule().String(),
+ Source: source,
SpecVersion: "1.0",
Type: e.name,
Time: e.ts,
diff --git a/caddy_test.go b/caddy_test.go
index adf14350e..08fa5c0d0 100644
--- a/caddy_test.go
+++ b/caddy_test.go
@@ -15,6 +15,7 @@
package caddy
import (
+ "context"
"testing"
"time"
)
@@ -72,3 +73,21 @@ func TestParseDuration(t *testing.T) {
}
}
}
+
+func TestEvent_CloudEvent_NilOrigin(t *testing.T) {
+ ctx, _ := NewContext(Context{Context: context.Background()}) // module will be nil by default
+ event, err := NewEvent(ctx, "started", nil)
+ if err != nil {
+ t.Fatalf("NewEvent() error = %v", err)
+ }
+
+ // This should not panic
+ ce := event.CloudEvent()
+
+ if ce.Source != "caddy" {
+ t.Errorf("Expected CloudEvent Source to be 'caddy', got '%s'", ce.Source)
+ }
+ if ce.Type != "started" {
+ t.Errorf("Expected CloudEvent Type to be 'started', got '%s'", ce.Type)
+ }
+}
diff --git a/caddyconfig/caddyfile/adapter.go b/caddyconfig/caddyfile/adapter.go
index da4f98337..449370dc6 100644
--- a/caddyconfig/caddyfile/adapter.go
+++ b/caddyconfig/caddyfile/adapter.go
@@ -68,7 +68,7 @@ func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconf
// TODO: also perform this check on imported files
func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
// replace windows-style newlines to normalize comparison
- normalizedBody := bytes.Replace(body, []byte("\r\n"), []byte("\n"), -1)
+ normalizedBody := bytes.ReplaceAll(body, []byte("\r\n"), []byte("\n"))
formatted := Format(normalizedBody)
if bytes.Equal(formatted, normalizedBody) {
diff --git a/caddyconfig/caddyfile/formatter.go b/caddyconfig/caddyfile/formatter.go
index f1d12fa51..0476a9b93 100644
--- a/caddyconfig/caddyfile/formatter.go
+++ b/caddyconfig/caddyfile/formatter.go
@@ -94,7 +94,7 @@ func Format(input []byte) []byte {
}
// detect whether we have the start of a heredoc
- if !quoted && !(heredoc != heredocClosed || heredocEscaped) &&
+ if !quoted && (heredoc == heredocClosed && !heredocEscaped) &&
space && last == '<' && ch == '<' {
write(ch)
heredoc = heredocOpening
diff --git a/caddyconfig/caddyfile/lexer.go b/caddyconfig/caddyfile/lexer.go
index 9b523f397..a3b1fc7db 100644
--- a/caddyconfig/caddyfile/lexer.go
+++ b/caddyconfig/caddyfile/lexer.go
@@ -137,7 +137,7 @@ func (l *lexer) next() (bool, error) {
}
// detect whether we have the start of a heredoc
- if !(quoted || btQuoted) && !(inHeredoc || heredocEscaped) &&
+ if (!quoted && !btQuoted) && (!inHeredoc && !heredocEscaped) &&
len(val) > 1 && string(val[:2]) == "<<" {
// a space means it's just a regular token and not a heredoc
if ch == ' ' {
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 3fc08b2c8..fb05d29f1 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -15,6 +15,7 @@
package httpcaddyfile
import (
+ "encoding/json"
"fmt"
"html"
"net/http"
@@ -843,13 +844,18 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
return nil, h.Errf("segment was not parsed as a subroute")
}
+ // wrap the subroutes
+ wrappingRoute := caddyhttp.Route{
+ HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)},
+ }
+ subroute = &caddyhttp.Subroute{
+ Routes: []caddyhttp.Route{wrappingRoute},
+ }
if expression != "" {
statusMatcher := caddy.ModuleMap{
"expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}),
}
- for i := range subroute.Routes {
- subroute.Routes[i].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
- }
+ subroute.Routes[0].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
}
return []ConfigValue{
{
@@ -1160,6 +1166,11 @@ func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
if h.NextArg() {
return nil, h.ArgErr()
}
+
+ if h.NextBlock(0) {
+ return nil, h.Err("log_skip directive does not accept blocks")
+ }
+
return caddyhttp.VarsMiddleware{"log_skip": true}, nil
}
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index 3fe27bfff..eac7f5dc2 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -174,10 +174,12 @@ func RegisterDirectiveOrder(dir string, position Positional, standardDir string)
if d != standardDir {
continue
}
- if position == Before {
+ switch position {
+ case Before:
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
- } else if position == After {
+ case After:
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
+ case First, Last:
}
break
}
diff --git a/caddytest/caddytest.go b/caddytest/caddytest.go
index 623c45e5e..7b56bb281 100644
--- a/caddytest/caddytest.go
+++ b/caddytest/caddytest.go
@@ -281,7 +281,7 @@ func validateTestPrerequisites(tc *Tester) error {
tc.t.Cleanup(func() {
os.Remove(f.Name())
})
- if _, err := f.WriteString(fmt.Sprintf(initConfig, tc.config.AdminPort)); err != nil {
+ if _, err := fmt.Fprintf(f, initConfig, tc.config.AdminPort); err != nil {
return err
}
diff --git a/caddytest/integration/acme_test.go b/caddytest/integration/acme_test.go
index d7e4c296d..f10aef6a8 100644
--- a/caddytest/integration/acme_test.go
+++ b/caddytest/integration/acme_test.go
@@ -12,13 +12,14 @@ import (
"strings"
"testing"
- "github.com/caddyserver/caddy/v2"
- "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
smallstepacme "github.com/smallstep/certificates/acme"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddytest"
)
const acmeChallengePort = 9081
diff --git a/caddytest/integration/acmeserver_test.go b/caddytest/integration/acmeserver_test.go
index ca5845f87..d6a9ba005 100644
--- a/caddytest/integration/acmeserver_test.go
+++ b/caddytest/integration/acmeserver_test.go
@@ -9,11 +9,12 @@ import (
"strings"
"testing"
- "github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme"
"go.uber.org/zap"
"go.uber.org/zap/exp/zapslog"
+
+ "github.com/caddyserver/caddy/v2/caddytest"
)
func TestACMEServerDirectory(t *testing.T) {
diff --git a/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest
index bd42aee55..6f3059ab2 100644
--- a/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_example.caddyfiletest
@@ -106,20 +106,29 @@ example.com {
"handler": "subroute",
"routes": [
{
- "group": "group0",
"handle": [
{
- "handler": "rewrite",
- "uri": "/{http.error.status_code}.html"
- }
- ]
- },
- {
- "handle": [
- {
- "handler": "file_server",
- "hide": [
- "./Caddyfile"
+ "handler": "subroute",
+ "routes": [
+ {
+ "group": "group0",
+ "handle": [
+ {
+ "handler": "rewrite",
+ "uri": "/{http.error.status_code}.html"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "file_server",
+ "hide": [
+ "./Caddyfile"
+ ]
+ }
+ ]
+ }
]
}
]
diff --git a/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest
index 0e84a13c2..1bec4b3e8 100644
--- a/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_multi_site_blocks.caddyfiletest
@@ -165,8 +165,17 @@ bar.localhost {
{
"handle": [
{
- "body": "404 or 410 error",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -178,8 +187,17 @@ bar.localhost {
{
"handle": [
{
- "body": "Error In range [500 .. 599]",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error In range [500 .. 599]",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -208,8 +226,17 @@ bar.localhost {
{
"handle": [
{
- "body": "404 or 410 error from second site",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error from second site",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -221,8 +248,17 @@ bar.localhost {
{
"handle": [
{
- "body": "Error In range [500 .. 599] from second site",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error In range [500 .. 599] from second site",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
diff --git a/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest
index 46b70c8e3..299abc5f1 100644
--- a/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_range_codes.caddyfiletest
@@ -96,8 +96,17 @@ localhost:3010 {
{
"handle": [
{
- "body": "Error in the [400 .. 499] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
diff --git a/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest
index 70158830c..9d4d2645c 100644
--- a/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_range_simple_codes.caddyfiletest
@@ -116,8 +116,17 @@ localhost:2099 {
{
"handle": [
{
- "body": "Error in the [400 .. 499] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -129,8 +138,17 @@ localhost:2099 {
{
"handle": [
{
- "body": "Error code is equal to 500 or in the [300..399] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error code is equal to 500 or in the [300..399] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
diff --git a/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest
index 5ac5863e3..29a51ffad 100644
--- a/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_simple_codes.caddyfiletest
@@ -96,8 +96,17 @@ localhost:3010 {
{
"handle": [
{
- "body": "404 or 410 error",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "404 or 410 error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
diff --git a/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest
index 63701cccb..1faf9b3bd 100644
--- a/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest
+++ b/caddytest/integration/caddyfile_adapt/error_sort.caddyfiletest
@@ -116,8 +116,17 @@ localhost:2099 {
{
"handle": [
{
- "body": "Error in the [400 .. 499] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Error in the [400 .. 499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
],
"match": [
@@ -129,8 +138,17 @@ localhost:2099 {
{
"handle": [
{
- "body": "Fallback route: code outside the [400..499] range",
- "handler": "static_response"
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Fallback route: code outside the [400..499] range",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
}
]
}
diff --git a/caddytest/integration/caddyfile_adapt/error_subhandlers.caddyfiletest b/caddytest/integration/caddyfile_adapt/error_subhandlers.caddyfiletest
new file mode 100644
index 000000000..54429a73e
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/error_subhandlers.caddyfiletest
@@ -0,0 +1,260 @@
+{
+ http_port 2099
+}
+localhost:2099 {
+ root * /var/www/
+ file_server
+
+ handle_errors 404 {
+ handle /en/* {
+ respond "not found" 404
+ }
+ handle /es/* {
+ respond "no encontrado"
+ }
+ handle {
+ respond "default not found"
+ }
+ }
+ handle_errors {
+ handle /en/* {
+ respond "English error"
+ }
+ handle /es/* {
+ respond "Spanish error"
+ }
+ handle {
+ respond "Default error"
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "http_port": 2099,
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":2099"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "vars",
+ "root": "/var/www/"
+ },
+ {
+ "handler": "file_server",
+ "hide": [
+ "./Caddyfile"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "errors": {
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "group": "group3",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "not found",
+ "handler": "static_response",
+ "status_code": 404
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/en/*"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "group3",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "no encontrado",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/es/*"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "group3",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "default not found",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "expression": "{http.error.status_code} in [404]"
+ }
+ ]
+ },
+ {
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "group": "group8",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "English error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/en/*"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "group8",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Spanish error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "match": [
+ {
+ "path": [
+ "/es/*"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "group8",
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "Default error",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_block.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_block.caddyfiletest
new file mode 100644
index 000000000..bc71456b8
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_block.caddyfiletest
@@ -0,0 +1,87 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf {
+ file ../caddy.ca.cer
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "files": [
+ "../caddy.ca.cer"
+ ],
+ "loader": "file"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_inline.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_inline.caddyfiletest
new file mode 100644
index 000000000..baca2433a
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_inline.caddyfiletest
@@ -0,0 +1,85 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf file ../caddy.ca.cer
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "files": [
+ "../caddy.ca.cer"
+ ],
+ "loader": "file"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_multi-in-block.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_multi-in-block.caddyfiletest
new file mode 100644
index 000000000..1fe22a317
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_file_loader_multi-in-block.caddyfiletest
@@ -0,0 +1,94 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf {
+ file ../caddy.ca.cer
+ file ../caddy.ca.cer
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "files": [
+ "../caddy.ca.cer"
+ ],
+ "loader": "file"
+ },
+ {
+ "files": [
+ "../caddy.ca.cer"
+ ],
+ "loader": "file"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_block.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_block.caddyfiletest
new file mode 100644
index 000000000..ee9be71aa
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_block.caddyfiletest
@@ -0,0 +1,87 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf {
+ folder ../
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "folders": [
+ "../"
+ ],
+ "loader": "folder"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_inline.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_inline.caddyfiletest
new file mode 100644
index 000000000..b6c4b8727
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_inline.caddyfiletest
@@ -0,0 +1,85 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf folder ../
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "folders": [
+ "../"
+ ],
+ "loader": "folder"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_multi-in-block.caddyfiletest b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_multi-in-block.caddyfiletest
new file mode 100644
index 000000000..dd5663d8d
--- /dev/null
+++ b/caddytest/integration/caddyfile_adapt/tls_client_auth_leaf_verifier_folder_loader_multi-in-block.caddyfiletest
@@ -0,0 +1,94 @@
+localhost
+
+respond "hello from localhost"
+tls {
+ client_auth {
+ mode request
+ trust_pool inline {
+ trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
+ }
+ verifier leaf {
+ folder ../
+ folder ../
+ }
+ }
+}
+----------
+{
+ "apps": {
+ "http": {
+ "servers": {
+ "srv0": {
+ "listen": [
+ ":443"
+ ],
+ "routes": [
+ {
+ "match": [
+ {
+ "host": [
+ "localhost"
+ ]
+ }
+ ],
+ "handle": [
+ {
+ "handler": "subroute",
+ "routes": [
+ {
+ "handle": [
+ {
+ "body": "hello from localhost",
+ "handler": "static_response"
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "terminal": true
+ }
+ ],
+ "tls_connection_policies": [
+ {
+ "match": {
+ "sni": [
+ "localhost"
+ ]
+ },
+ "client_authentication": {
+ "ca": {
+ "provider": "inline",
+ "trusted_ca_certs": [
+ "MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
+ ]
+ },
+ "verifiers": [
+ {
+ "leaf_certs_loaders": [
+ {
+ "folders": [
+ "../"
+ ],
+ "loader": "folder"
+ },
+ {
+ "folders": [
+ "../"
+ ],
+ "loader": "folder"
+ }
+ ],
+ "verifier": "leaf"
+ }
+ ],
+ "mode": "request"
+ }
+ },
+ {}
+ ]
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/caddytest/integration/caddyfile_adapt_test.go b/caddytest/integration/caddyfile_adapt_test.go
index 0d9f0fa47..674036e17 100644
--- a/caddytest/integration/caddyfile_adapt_test.go
+++ b/caddytest/integration/caddyfile_adapt_test.go
@@ -10,7 +10,6 @@ import (
"testing"
"github.com/caddyserver/caddy/v2/caddytest"
-
_ "github.com/caddyserver/caddy/v2/internal/testmocks"
)
diff --git a/caddytest/integration/caddyfile_test.go b/caddytest/integration/caddyfile_test.go
index 11ffc08ae..d45d5a5e9 100644
--- a/caddytest/integration/caddyfile_test.go
+++ b/caddytest/integration/caddyfile_test.go
@@ -615,7 +615,6 @@ func TestReplaceWithReplacementPlaceholder(t *testing.T) {
respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz")
-
}
func TestReplaceWithKeyPlaceholder(t *testing.T) {
@@ -783,6 +782,46 @@ func TestHandleErrorRangeAndCodes(t *testing.T) {
tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range")
}
+func TestHandleErrorSubHandlers(t *testing.T) {
+ tester := caddytest.NewTester(t)
+ tester.InitServer(`{
+ admin localhost:2999
+ http_port 9080
+ }
+ localhost:9080 {
+ root * /srv
+ file_server
+ error /*/internalerr* "Internal Server Error" 500
+
+ handle_errors 404 {
+ handle /en/* {
+ respond "not found" 404
+ }
+ handle /es/* {
+ respond "no encontrado" 404
+ }
+ handle {
+ respond "default not found"
+ }
+ }
+ handle_errors {
+ handle {
+ respond "Default error"
+ }
+ handle /en/* {
+ respond "English error"
+ }
+ }
+ }
+ `, "caddyfile")
+ // act and assert
+ tester.AssertGetResponse("http://localhost:9080/en/notfound", 404, "not found")
+ tester.AssertGetResponse("http://localhost:9080/es/notfound", 404, "no encontrado")
+ tester.AssertGetResponse("http://localhost:9080/notfound", 404, "default not found")
+ tester.AssertGetResponse("http://localhost:9080/es/internalerr", 500, "Default error")
+ tester.AssertGetResponse("http://localhost:9080/en/internalerr", 500, "English error")
+}
+
func TestInvalidSiteAddressesAsDirectives(t *testing.T) {
type testCase struct {
config, expectedError string
diff --git a/caddytest/integration/mockdns_test.go b/caddytest/integration/mockdns_test.go
index 615116a3a..31dc4be7b 100644
--- a/caddytest/integration/mockdns_test.go
+++ b/caddytest/integration/mockdns_test.go
@@ -3,10 +3,11 @@ package integration
import (
"context"
- "github.com/caddyserver/caddy/v2"
- "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/certmagic"
"github.com/libdns/libdns"
+
+ "github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
func init() {
@@ -55,7 +56,9 @@ func (MockDNSProvider) SetRecords(ctx context.Context, zone string, recs []libdn
}
// Interface guard
-var _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil)
-var _ certmagic.DNSProvider = (*MockDNSProvider)(nil)
-var _ caddy.Provisioner = (*MockDNSProvider)(nil)
-var _ caddy.Module = (*MockDNSProvider)(nil)
+var (
+ _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil)
+ _ certmagic.DNSProvider = (*MockDNSProvider)(nil)
+ _ caddy.Provisioner = (*MockDNSProvider)(nil)
+ _ caddy.Module = (*MockDNSProvider)(nil)
+)
diff --git a/caddytest/integration/stream_test.go b/caddytest/integration/stream_test.go
index d2f2fd79b..57231a527 100644
--- a/caddytest/integration/stream_test.go
+++ b/caddytest/integration/stream_test.go
@@ -13,9 +13,10 @@ import (
"testing"
"time"
- "github.com/caddyserver/caddy/v2/caddytest"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
+
+ "github.com/caddyserver/caddy/v2/caddytest"
)
// (see https://github.com/caddyserver/caddy/issues/3556 for use case)
diff --git a/cmd/commands.go b/cmd/commands.go
index 259dd358f..0dae876fb 100644
--- a/cmd/commands.go
+++ b/cmd/commands.go
@@ -20,6 +20,7 @@ import (
"os"
"regexp"
"strings"
+ "sync"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
@@ -80,10 +81,16 @@ type CommandFunc func(Flags) (int, error)
// Commands returns a list of commands initialised by
// RegisterCommand
func Commands() map[string]Command {
+ commandsMu.RLock()
+ defer commandsMu.RUnlock()
+
return commands
}
-var commands = make(map[string]Command)
+var (
+ commandsMu sync.RWMutex
+ commands = make(map[string]Command)
+)
func init() {
RegisterCommand(Command{
@@ -441,7 +448,7 @@ EXPERIMENTAL: May be changed or removed.
})
defaultFactory.Use(func(rootCmd *cobra.Command) {
- rootCmd.AddCommand(caddyCmdToCobra(Command{
+ manpageCommand := Command{
Name: "manpage",
Usage: "--directory