Compare commits

..

No commits in common. "master" and "v2.10.0-beta.1" have entirely different histories.

118 changed files with 1662 additions and 3376 deletions

4
.github/SECURITY.md vendored
View File

@ -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. 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 submit a [new private vulnerability report](https://github.com/caddyserver/caddy/security/advisories/new). When you are ready, please email Matt Holt (the author) directly: matt at dyanim dot com.
Please don't encrypt the message. It only makes the process more complicated. Please don't encrypt the email body. 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. 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.

View File

@ -5,8 +5,3 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "monthly" interval: "monthly"
- package-ecosystem: gomod
directory: /
schedule:
interval: weekly

View File

@ -12,13 +12,6 @@ on:
- master - master
- 2.* - 2.*
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
permissions:
contents: read
jobs: jobs:
test: test:
strategy: strategy:
@ -58,21 +51,13 @@ jobs:
SUCCESS: 'True' SUCCESS: 'True'
runs-on: ${{ matrix.OS_LABEL }} 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
steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@v4
- name: Install Go - name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.GO_SEMVER }} go-version: ${{ matrix.GO_SEMVER }}
check-latest: true check-latest: true
@ -110,7 +95,7 @@ jobs:
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
run: | run: |
go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v go build -tags nobadger -trimpath -ldflags="-w -s" -v
- name: Smoke test Caddy - name: Smoke test Caddy
working-directory: ./cmd/caddy working-directory: ./cmd/caddy
@ -119,7 +104,7 @@ jobs:
./caddy stop ./caddy stop
- name: Publish Build Artifact - name: Publish Build Artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@v4
with: with:
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }} name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
path: ${{ matrix.CADDY_BIN_PATH }} path: ${{ matrix.CADDY_BIN_PATH }}
@ -133,7 +118,7 @@ jobs:
# continue-on-error: true # continue-on-error: true
run: | run: |
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out # (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
go test -tags nobadger,nomysql,nopgx -v -coverprofile="cover-profile.out" -short -race ./... go test -tags nobadger -v -coverprofile="cover-profile.out" -short -race ./...
# echo "status=$?" >> $GITHUB_OUTPUT # echo "status=$?" >> $GITHUB_OUTPUT
# Relevant step if we reinvestigate publishing test/coverage reports # Relevant step if we reinvestigate publishing test/coverage reports
@ -153,21 +138,12 @@ jobs:
s390x-test: s390x-test:
name: test (s390x on IBM Z) name: test (s390x on IBM Z)
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]' if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
steps: steps:
- name: 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 - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@v4
- name: Run Tests - name: Run Tests
run: | run: |
set +e set +e
@ -190,7 +166,7 @@ jobs:
retries=3 retries=3
exit_code=0 exit_code=0
while ((retries > 0)); do while ((retries > 0)); do
CGO_ENABLED=0 go test -p 1 -tags nobadger,nomysql,nopgx -v ./... CGO_ENABLED=0 go test -p 1 -tags nobadger -v ./...
exit_code=$? exit_code=$?
if ((exit_code == 0)); then if ((exit_code == 0)); then
break break
@ -214,25 +190,17 @@ jobs:
goreleaser-check: goreleaser-check:
runs-on: ubuntu-latest 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]' if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
steps: 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 - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@v4
- uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 - uses: goreleaser/goreleaser-action@v6
with: with:
version: latest version: latest
args: check args: check
- name: Install Go - name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@v5
with: with:
go-version: "~1.24" go-version: "~1.24"
check-latest: true check-latest: true
@ -240,7 +208,7 @@ jobs:
run: | run: |
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy version xcaddy version
- uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 - uses: goreleaser/goreleaser-action@v6
with: with:
version: latest version: latest
args: build --single-target --snapshot args: build --single-target --snapshot

View File

@ -10,13 +10,6 @@ on:
- master - master
- 2.* - 2.*
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
permissions:
contents: read
jobs: jobs:
build: build:
strategy: strategy:
@ -43,21 +36,13 @@ jobs:
GO_SEMVER: '~1.24.1' GO_SEMVER: '~1.24.1'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
continue-on-error: true continue-on-error: true
steps: 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 - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@v4
- name: Install Go - name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.GO_SEMVER }} go-version: ${{ matrix.GO_SEMVER }}
check-latest: true check-latest: true

View File

@ -13,10 +13,6 @@ on:
permissions: permissions:
contents: read contents: read
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
jobs: jobs:
# From https://github.com/golangci/golangci-lint-action # From https://github.com/golangci/golangci-lint-action
golangci: golangci:
@ -44,19 +40,14 @@ jobs:
runs-on: ${{ matrix.OS_LABEL }} runs-on: ${{ matrix.OS_LABEL }}
steps: steps:
- name: Harden the runner (Audit all outbound calls) - uses: actions/checkout@v4
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 - uses: actions/setup-go@v5
with:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version: '~1.24' go-version: '~1.24'
check-latest: true check-latest: true
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 uses: golangci/golangci-lint-action@v6
with: with:
version: latest version: latest
@ -67,39 +58,10 @@ jobs:
# only-new-issues: true # only-new-issues: true
govulncheck: govulncheck:
permissions:
contents: read
pull-requests: read
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
with:
egress-policy: audit
- name: govulncheck - name: govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 uses: golang/govulncheck-action@v1
with: with:
go-version-input: '~1.24.1' go-version-input: '~1.24.1'
check-latest: true 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 }}

View File

@ -5,13 +5,6 @@ on:
tags: tags:
- 'v*.*.*' - 'v*.*.*'
env:
# https://github.com/actions/setup-go/issues/491
GOTOOLCHAIN: local
permissions:
contents: read
jobs: jobs:
release: release:
name: Release name: Release
@ -38,24 +31,19 @@ jobs:
contents: write contents: write
steps: 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 - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Install Go - name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 uses: actions/setup-go@v5
with: with:
go-version: ${{ matrix.GO_SEMVER }} go-version: ${{ matrix.GO_SEMVER }}
check-latest: true check-latest: true
# Force fetch upstream tags -- because 65 minutes # Force fetch upstream tags -- because 65 minutes
# tl;dr: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 runs this line: # tl;dr: actions/checkout@v4 runs this line:
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/ # 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: # which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
# git fetch --prune --unshallow # git fetch --prune --unshallow
@ -109,11 +97,11 @@ jobs:
git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1 git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1
- name: Install Cosign - name: Install Cosign
uses: sigstore/cosign-installer@e9a05e6d32d7ed22b5656cd874ef31af58d05bfa # main uses: sigstore/cosign-installer@main
- name: Cosign version - name: Cosign version
run: cosign version run: cosign version
- name: Install Syft - name: Install Syft
uses: anchore/sbom-action/download-syft@9246b90769f852b3a8921f330c59e0b3f439d6e9 # main uses: anchore/sbom-action/download-syft@main
- name: Syft version - name: Syft version
run: syft version run: syft version
- name: Install xcaddy - name: Install xcaddy
@ -122,7 +110,7 @@ jobs:
xcaddy version xcaddy version
# GoReleaser will take care of publishing those artifacts into the release # GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 # v6.3.0 uses: goreleaser/goreleaser-action@v6
with: with:
version: latest version: latest
args: release --clean --timeout 60m args: release --clean --timeout 60m

View File

@ -5,9 +5,6 @@ on:
release: release:
types: [published] types: [published]
permissions:
contents: read
jobs: jobs:
release: release:
name: Release Published name: Release Published
@ -16,20 +13,12 @@ jobs:
os: os:
- ubuntu-latest - ubuntu-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions:
contents: read
pull-requests: read
actions: write
steps: steps:
# See https://github.com/peter-evans/repository-dispatch # 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 - name: Trigger event on caddyserver/dist
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 uses: peter-evans/repository-dispatch@v3
with: with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }} token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/dist repository: caddyserver/dist
@ -37,7 +26,7 @@ jobs:
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}' client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
- name: Trigger event on caddyserver/caddy-docker - name: Trigger event on caddyserver/caddy-docker
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 uses: peter-evans/repository-dispatch@v3
with: with:
token: ${{ secrets.REPO_DISPATCH_TOKEN }} token: ${{ secrets.REPO_DISPATCH_TOKEN }}
repository: caddyserver/caddy-docker repository: caddyserver/caddy-docker

View File

@ -1,86 +0,0 @@
# 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

View File

@ -1,15 +1,27 @@
version: "2" linters-settings:
run: errcheck:
issues-exit-code: 1 exclude-functions:
tests: false - fmt.*
output: - (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
formats: - (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
text: gci:
path: stdout sections:
print-linter-name: true - standard # Standard section: captures all standard packages.
print-issued-lines: true - 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
linters: linters:
default: none disable-all: true
enable: enable:
- asasalint - asasalint
- asciicheck - asciicheck
@ -23,96 +35,148 @@ linters:
- errcheck - errcheck
- errname - errname
- exhaustive - exhaustive
- gci
- gofmt
- goimports
- gofumpt
- gosec - gosec
- gosimple
- govet - govet
- importas
- ineffassign - ineffassign
- importas
- misspell - misspell
- prealloc - prealloc
- promlinter - promlinter
- sloglint - sloglint
- sqlclosecheck - sqlclosecheck
- staticcheck - staticcheck
- tenv
- testableexamples - testableexamples
- testifylint - testifylint
- tparallel - tparallel
- typecheck
- unconvert - unconvert
- unused - unused
- wastedassign - wastedassign
- whitespace - whitespace
- zerologlint - zerologlint
settings: # these are implicitly disabled:
staticcheck: # - containedctx
checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1006", "-QF1008"] # default, and exclude 1 more undesired check # - contextcheck
errcheck: # - cyclop
exclude-functions: # - depguard
- fmt.* # - errchkjson
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject # - errorlint
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray # - exhaustruct
exhaustive: # - execinquery
ignore-enum-types: reflect.Kind|svc.Cmd # - exhaustruct
exclusions: # - forbidigo
generated: lax # - forcetypeassert
presets: # - funlen
- comments # - ginkgolinter
- common-false-positives # - gocheckcompilerdirectives
- legacy # - gochecknoglobals
- std-error-handling # - gochecknoinits
rules: # - gochecksumtype
- linters: # - gocognit
- gosec # - goconst
text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad # - gocritic
- linters: # - gocyclo
- gosec # - godot
text: G107 # we aren't calling unknown URL # - godox
- linters: # - goerr113
- gosec # - goheader
text: G203 # as a web server that's expected to handle any template, this is totally in the hands of the user. # - gomnd
- linters: # - gomoddirectives
- gosec # - gomodguard
text: G204 # we're shelling out to known commands, not relying on user-defined input. # - goprintffuncname
- linters: # - gosmopolitan
- gosec # - grouper
# the choice of weakrand is deliberate, hence the named import "weakrand" # - inamedparam
path: modules/caddyhttp/reverseproxy/selectionpolicies.go # - interfacebloat
text: G404 # - ireturn
- linters: # - lll
- gosec # - loggercheck
path: modules/caddyhttp/reverseproxy/streaming.go # - maintidx
text: G404 # - makezero
- linters: # - mirror
- dupl # - musttag
path: modules/logging/filters.go # - nakedret
- linters: # - nestif
- dupl # - nilerr
path: modules/caddyhttp/matchers.go # - nilnil
- linters: # - nlreturn
- dupl # - noctx
path: modules/caddyhttp/vars.go # - nolintlint
- linters: # - nonamedreturns
- errcheck # - nosprintfhostport
path: _test\.go # - paralleltest
paths: # - perfsprint
- third_party$ # - predeclared
- builtin$ # - protogetter
- examples$ # - reassign
formatters: # - revive
enable: # - rowserrcheck
- gci # - stylecheck
- gofmt # - tagalign
- gofumpt # - tagliatelle
- goimports # - testpackage
settings: # - thelper
gci: # - unparam
sections: # - usestdlibvars
- standard # Standard section: captures all standard packages. # - varnamelen
- default # Default section: contains all imports that could not be matched to another section type. # - wrapcheck
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break. # - wsl
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
custom-order: true run:
exclusions: # default concurrency is a available CPU number.
generated: lax # concurrency: 4 # explicitly omit this value to fully utilize available resources.
paths: timeout: 5m
- third_party$ issues-exit-code: 1
- builtin$ tests: false
- examples$
# 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

View File

@ -1,20 +0,0 @@
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

View File

@ -14,7 +14,6 @@
<p align="center">Caddy is an extensible server platform that uses TLS by default.</p> <p align="center">Caddy is an extensible server platform that uses TLS by default.</p>
<p align="center"> <p align="center">
<a href="https://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a> <a href="https://github.com/caddyserver/caddy/actions/workflows/ci.yml"><img src="https://github.com/caddyserver/caddy/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://www.bestpractices.dev/projects/7141"><img src="https://www.bestpractices.dev/projects/7141/badge"></a>
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a> <a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
<br> <br>
<a href="https://x.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/twitter/follow/caddyserver" alt="@caddyserver on Twitter"></a> <a href="https://x.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/twitter/follow/caddyserver" alt="@caddyserver on Twitter"></a>

View File

@ -221,8 +221,7 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Co
if remote { if remote {
muxWrap.remoteControl = admin.Remote muxWrap.remoteControl = admin.Remote
} else { } else {
// see comment in allowedOrigins() as to why we disable the host check for unix/fd networks muxWrap.enforceHost = !addr.isWildcardInterface()
muxWrap.enforceHost = !addr.isWildcardInterface() && !addr.IsUnixNetwork() && !addr.IsFdNetwork()
muxWrap.allowedOrigins = admin.allowedOrigins(addr) muxWrap.allowedOrigins = admin.allowedOrigins(addr)
muxWrap.enforceOrigin = admin.EnforceOrigin muxWrap.enforceOrigin = admin.EnforceOrigin
} }
@ -311,43 +310,47 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
for _, o := range admin.Origins { for _, o := range admin.Origins {
uniqueOrigins[o] = struct{}{} uniqueOrigins[o] = struct{}{}
} }
// RFC 2616, Section 14.26: if admin.Origins == nil {
// "A client MUST include a Host header field in all HTTP/1.1 request
// messages. If the requested URI does not include an Internet host
// name for the service being requested, then the Host header field MUST
// be given with an empty value."
//
// UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
// Understandable, but frustrating. See:
// https://github.com/golang/go/issues/60374
// See also the discussion here:
// https://github.com/golang/go/issues/61431
//
// We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
// in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
// security checks, the infosec community assures me that it is secure to do
// so, because:
//
// 1) Browsers do not allow access to unix sockets
// 2) DNS is irrelevant to unix sockets
//
// If either of those two statements ever fail to hold true, it is not the
// fault of Caddy.
//
// Thus, we do not fill out allowed origins and do not enforce Host
// requirements for unix sockets. Enforcing it leads to confusion and
// frustration, when UDS have their own permissions from the OS.
// Enforcing host requirements here is effectively security theater,
// and a false sense of security.
//
// See also the discussion in #6832.
if admin.Origins == nil && !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
if addr.isLoopback() { if addr.isLoopback() {
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{} if addr.IsUnixNetwork() || addr.IsFdNetwork() {
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{} // RFC 2616, Section 14.26:
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{} // "A client MUST include a Host header field in all HTTP/1.1 request
} else { // messages. If the requested URI does not include an Internet host
// name for the service being requested, then the Host header field MUST
// be given with an empty value."
//
// UPDATE July 2023: Go broke this by patching a minor security bug in 1.20.6.
// Understandable, but frustrating. See:
// https://github.com/golang/go/issues/60374
// See also the discussion here:
// https://github.com/golang/go/issues/61431
//
// We can no longer conform to RFC 2616 Section 14.26 from either Go or curl
// in purity. (Curl allowed no host between 7.40 and 7.50, but now requires a
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
// security checks, the infosec community assures me that it is secure to do
// so, because:
// 1) Browsers do not allow access to unix sockets
// 2) DNS is irrelevant to unix sockets
//
// I am not quite ready to trust either of those external factors, so instead
// of disabling Host/Origin checks, we now allow specific Host values when
// accessing the admin endpoint over unix sockets. I definitely don't trust
// DNS (e.g. I don't trust 'localhost' to always resolve to the local host),
// and IP shouldn't even be used, but if it is for some reason, I think we can
// at least be reasonably assured that 127.0.0.1 and ::1 route to the local
// machine, meaning that a hypothetical browser origin would have to be on the
// local machine as well.
uniqueOrigins[""] = struct{}{}
uniqueOrigins["127.0.0.1"] = struct{}{}
uniqueOrigins["::1"] = struct{}{}
} else {
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
}
}
if !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{} uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
} }
} }
@ -424,13 +427,6 @@ func replaceLocalAdminServer(cfg *Config, ctx Context) error {
handler := cfg.Admin.newAdminHandler(addr, false, ctx) handler := cfg.Admin.newAdminHandler(addr, false, ctx)
// run the provisioners for loaded modules to make sure local
// state is properly re-initialized in the new admin server
err = cfg.Admin.provisionAdminRouters(ctx)
if err != nil {
return err
}
ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{}) ln, err := addr.Listen(context.TODO(), 0, net.ListenConfig{})
if err != nil { if err != nil {
return err return err
@ -552,13 +548,6 @@ func replaceRemoteAdminServer(ctx Context, cfg *Config) error {
// because we are using TLS authentication instead // because we are using TLS authentication instead
handler := cfg.Admin.newAdminHandler(addr, true, ctx) handler := cfg.Admin.newAdminHandler(addr, true, ctx)
// run the provisioners for loaded modules to make sure local
// state is properly re-initialized in the new admin server
err = cfg.Admin.provisionAdminRouters(ctx)
if err != nil {
return err
}
// create client certificate pool for TLS mutual auth, and extract public keys // create client certificate pool for TLS mutual auth, and extract public keys
// so that we can enforce access controls at the application layer // so that we can enforce access controls at the application layer
clientCertPool := x509.NewCertPool() clientCertPool := x509.NewCertPool()

View File

@ -19,7 +19,6 @@ import (
"crypto/x509" "crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"maps"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
@ -336,7 +335,9 @@ func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
func testGetMetricValue(labels map[string]string) float64 { func testGetMetricValue(labels map[string]string) float64 {
promLabels := prometheus.Labels{} promLabels := prometheus.Labels{}
maps.Copy(promLabels, labels) for k, v := range labels {
promLabels[k] = v
}
metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels) metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels)
if err != nil { if err != nil {
@ -376,7 +377,9 @@ func (m *mockModule) CaddyModule() ModuleInfo {
func TestNewAdminHandlerRouterRegistration(t *testing.T) { func TestNewAdminHandlerRouterRegistration(t *testing.T) {
originalModules := make(map[string]ModuleInfo) originalModules := make(map[string]ModuleInfo)
maps.Copy(originalModules, modules) for k, v := range modules {
originalModules[k] = v
}
defer func() { defer func() {
modules = originalModules modules = originalModules
}() }()
@ -476,7 +479,9 @@ func TestAdminRouterProvisioning(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
originalModules := make(map[string]ModuleInfo) originalModules := make(map[string]ModuleInfo)
maps.Copy(originalModules, modules) for k, v := range modules {
originalModules[k] = v
}
defer func() { defer func() {
modules = originalModules modules = originalModules
}() }()
@ -526,7 +531,6 @@ func TestAdminRouterProvisioning(t *testing.T) {
} }
func TestAllowedOriginsUnixSocket(t *testing.T) { func TestAllowedOriginsUnixSocket(t *testing.T) {
// see comment in allowedOrigins() as to why we do not fill out allowed origins for UDS
tests := []struct { tests := []struct {
name string name string
addr NetworkAddress addr NetworkAddress
@ -539,8 +543,12 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
Network: "unix", Network: "unix",
Host: "/tmp/caddy.sock", Host: "/tmp/caddy.sock",
}, },
origins: nil, // default origins origins: nil, // default origins
expectOrigins: []string{}, expectOrigins: []string{
"", // empty host as per RFC 2616
"127.0.0.1",
"::1",
},
}, },
{ {
name: "unix socket with custom origins", name: "unix socket with custom origins",
@ -570,7 +578,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
}, },
} }
for i, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
admin := AdminConfig{ admin := AdminConfig{
Origins: test.origins, Origins: test.origins,
@ -584,7 +592,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
} }
if len(gotOrigins) != len(test.expectOrigins) { if len(gotOrigins) != len(test.expectOrigins) {
t.Errorf("%d: Expected %d origins but got %d", i, len(test.expectOrigins), len(gotOrigins)) t.Errorf("Expected %d origins but got %d", len(test.expectOrigins), len(gotOrigins))
return return
} }
@ -599,7 +607,7 @@ func TestAllowedOriginsUnixSocket(t *testing.T) {
} }
if !reflect.DeepEqual(expectMap, gotMap) { if !reflect.DeepEqual(expectMap, gotMap) {
t.Errorf("%d: Origins mismatch.\nExpected: %v\nGot: %v", i, test.expectOrigins, gotOrigins) t.Errorf("Origins mismatch.\nExpected: %v\nGot: %v", test.expectOrigins, gotOrigins)
} }
}) })
} }
@ -769,7 +777,9 @@ func (m *mockIssuerModule) CaddyModule() ModuleInfo {
func TestManageIdentity(t *testing.T) { func TestManageIdentity(t *testing.T) {
originalModules := make(map[string]ModuleInfo) originalModules := make(map[string]ModuleInfo)
maps.Copy(originalModules, modules) for k, v := range modules {
originalModules[k] = v
}
defer func() { defer func() {
modules = originalModules modules = originalModules
}() }()

144
caddy.go
View File

@ -81,14 +81,13 @@ type Config struct {
// associated value. // associated value.
AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="` AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
apps map[string]App apps map[string]App
storage certmagic.Storage storage certmagic.Storage
eventEmitter eventEmitter
cancelFunc context.CancelFunc cancelFunc context.CancelFunc
// fileSystems is a dict of fileSystems that will later be loaded from and added to. // filesystems is a dict of filesystems that will later be loaded from and added to.
fileSystems FileSystems filesystems FileSystems
} }
// App is a thing that Caddy runs. // App is a thing that Caddy runs.
@ -408,23 +407,11 @@ func run(newCfg *Config, start bool) (Context, error) {
return ctx, nil 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 // Provision any admin routers which may need to access
// some of the other apps at runtime // some of the other apps at runtime
err = ctx.cfg.Admin.provisionAdminRouters(ctx) err = ctx.cfg.Admin.provisionAdminRouters(ctx)
if err != nil { if err != nil {
globalMetrics.configSuccess.Set(0)
return ctx, err return ctx, err
} }
@ -450,18 +437,14 @@ func run(newCfg *Config, start bool) (Context, error) {
return nil return nil
}() }()
if err != nil { if err != nil {
globalMetrics.configSuccess.Set(0)
return ctx, err return ctx, err
} }
globalMetrics.configSuccess.Set(1) globalMetrics.configSuccess.Set(1)
globalMetrics.configSuccessTime.SetToCurrentTime() globalMetrics.configSuccessTime.SetToCurrentTime()
// TODO: This event is experimental and subject to change.
ctx.emitEvent("started", nil)
// now that the user's config is running, finish setting up anything else, // now that the user's config is running, finish setting up anything else,
// such as remote admin endpoint, config loader, etc. // such as remote admin endpoint, config loader, etc.
err = finishSettingUp(ctx, ctx.cfg) return ctx, finishSettingUp(ctx, ctx.cfg)
return ctx, err
} }
// provisionContext creates a new context from the given configuration and provisions // provisionContext creates a new context from the given configuration and provisions
@ -517,8 +500,16 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
return ctx, err return ctx, err
} }
// start the admin endpoint (and stop any prior one)
if replaceAdminServer {
err = replaceLocalAdminServer(newCfg, ctx)
if err != nil {
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
}
}
// create the new filesystem map // create the new filesystem map
newCfg.fileSystems = &filesystems.FileSystemMap{} newCfg.filesystems = &filesystems.FilesystemMap{}
// prepare the new config for use // prepare the new config for use
newCfg.apps = make(map[string]App) newCfg.apps = make(map[string]App)
@ -548,14 +539,6 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
return ctx, err return ctx, err
} }
// start the admin endpoint (and stop any prior one)
if replaceAdminServer {
err = replaceLocalAdminServer(newCfg, ctx)
if err != nil {
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
}
}
// Load and Provision each app and their submodules // Load and Provision each app and their submodules
err = func() error { err = func() error {
for appName := range newCfg.AppsRaw { for appName := range newCfg.AppsRaw {
@ -713,9 +696,6 @@ func unsyncedStop(ctx Context) {
return return
} }
// TODO: This event is experimental and subject to change.
ctx.emitEvent("stopping", nil)
// stop each app // stop each app
for name, a := range ctx.cfg.apps { for name, a := range ctx.cfg.apps {
err := a.Stop() err := a.Stop()
@ -1058,98 +1038,6 @@ func Version() (simple, full string) {
return return
} }
// Event represents something that has happened or is happening.
// An Event value is not synchronized, so it should be copied if
// being used in goroutines.
//
// EXPERIMENTAL: Events are subject to change.
type Event struct {
// If non-nil, the event has been aborted, meaning
// propagation has stopped to other handlers and
// the code should stop what it was doing. Emitters
// may choose to use this as a signal to adjust their
// code path appropriately.
Aborted error
// The data associated with the event. Usually the
// original emitter will be the only one to set or
// change these values, but the field is exported
// so handlers can have full access if needed.
// However, this map is not synchronized, so
// handlers must not use this map directly in new
// goroutines; instead, copy the map to use it in a
// goroutine. Data may be nil.
Data map[string]any
id uuid.UUID
ts time.Time
name string
origin Module
}
// NewEvent creates a new event, but does not emit the event. To emit an
// event, call Emit() on the current instance of the caddyevents app insteaad.
//
// EXPERIMENTAL: Subject to change.
func NewEvent(ctx Context, name string, data map[string]any) (Event, error) {
id, err := uuid.NewRandom()
if err != nil {
return Event{}, fmt.Errorf("generating new event ID: %v", err)
}
name = strings.ToLower(name)
return Event{
Data: data,
id: id,
ts: time.Now(),
name: name,
origin: ctx.Module(),
}, nil
}
func (e Event) ID() uuid.UUID { return e.id }
func (e Event) Timestamp() time.Time { return e.ts }
func (e Event) Name() string { return e.name }
func (e Event) Origin() Module { return e.origin } // Returns the module that originated the event. May be nil, usually if caddy core emits the event.
// CloudEvent exports event e as a structure that, when
// serialized as JSON, is compatible with the
// CloudEvents spec.
func (e Event) CloudEvent() CloudEvent {
dataJSON, _ := json.Marshal(e.Data)
var source string
if e.Origin() == nil {
source = "caddy"
} else {
source = string(e.Origin().CaddyModule().ID)
}
return CloudEvent{
ID: e.id.String(),
Source: source,
SpecVersion: "1.0",
Type: e.name,
Time: e.ts,
DataContentType: "application/json",
Data: dataJSON,
}
}
// CloudEvent is a JSON-serializable structure that
// is compatible with the CloudEvents specification.
// See https://cloudevents.io.
// EXPERIMENTAL: Subject to change.
type CloudEvent struct {
ID string `json:"id"`
Source string `json:"source"`
SpecVersion string `json:"specversion"`
Type string `json:"type"`
Time time.Time `json:"time"`
DataContentType string `json:"datacontenttype,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
// ErrEventAborted cancels an event.
var ErrEventAborted = errors.New("event aborted")
// ActiveContext returns the currently-active context. // ActiveContext returns the currently-active context.
// This function is experimental and might be changed // This function is experimental and might be changed
// or removed in the future. // or removed in the future.

View File

@ -15,7 +15,6 @@
package caddy package caddy
import ( import (
"context"
"testing" "testing"
"time" "time"
) )
@ -73,21 +72,3 @@ 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)
}
}

View File

@ -68,7 +68,7 @@ func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconf
// TODO: also perform this check on imported files // TODO: also perform this check on imported files
func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) { func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
// replace windows-style newlines to normalize comparison // replace windows-style newlines to normalize comparison
normalizedBody := bytes.ReplaceAll(body, []byte("\r\n"), []byte("\n")) normalizedBody := bytes.Replace(body, []byte("\r\n"), []byte("\n"), -1)
formatted := Format(normalizedBody) formatted := Format(normalizedBody)
if bytes.Equal(formatted, normalizedBody) { if bytes.Equal(formatted, normalizedBody) {

View File

@ -61,8 +61,7 @@ func Format(input []byte) []byte {
heredocMarker []rune heredocMarker []rune
heredocClosingMarker []rune heredocClosingMarker []rune
nesting int // indentation level nesting int // indentation level
withinBackquote bool
) )
write := func(ch rune) { write := func(ch rune) {
@ -89,12 +88,9 @@ func Format(input []byte) []byte {
} }
panic(err) panic(err)
} }
if ch == '`' {
withinBackquote = !withinBackquote
}
// detect whether we have the start of a heredoc // detect whether we have the start of a heredoc
if !quoted && (heredoc == heredocClosed && !heredocEscaped) && if !quoted && !(heredoc != heredocClosed || heredocEscaped) &&
space && last == '<' && ch == '<' { space && last == '<' && ch == '<' {
write(ch) write(ch)
heredoc = heredocOpening heredoc = heredocOpening
@ -240,23 +236,14 @@ func Format(input []byte) []byte {
switch { switch {
case ch == '{': case ch == '{':
openBrace = true openBrace = true
openBraceWritten = false
openBraceSpace = spacePrior && !beginningOfLine openBraceSpace = spacePrior && !beginningOfLine
if openBraceSpace { if openBraceSpace {
write(' ') write(' ')
} }
openBraceWritten = false
if withinBackquote {
write('{')
openBraceWritten = true
continue
}
continue continue
case ch == '}' && (spacePrior || !openBrace): case ch == '}' && (spacePrior || !openBrace):
if withinBackquote {
write('}')
continue
}
if last != '\n' { if last != '\n' {
nextLine() nextLine()
} }

View File

@ -434,16 +434,6 @@ block2 {
} }
`, `,
}, },
{
description: "Preserve braces wrapped by backquotes",
input: "block {respond `All braces should remain: {{now | date \"2006\"}}`}",
expect: "block {respond `All braces should remain: {{now | date \"2006\"}}`}",
},
{
description: "Preserve braces wrapped by quotes",
input: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
expect: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
},
} { } {
// the formatter should output a trailing newline, // the formatter should output a trailing newline,
// even if the tests aren't written to expect that // even if the tests aren't written to expect that

View File

@ -137,7 +137,7 @@ func (l *lexer) next() (bool, error) {
} }
// detect whether we have the start of a heredoc // 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]) == "<<" { len(val) > 1 && string(val[:2]) == "<<" {
// a space means it's just a regular token and not a heredoc // a space means it's just a regular token and not a heredoc
if ch == ' ' { if ch == ' ' {

View File

@ -15,7 +15,6 @@
package httpcaddyfile package httpcaddyfile
import ( import (
"encoding/json"
"fmt" "fmt"
"html" "html"
"net/http" "net/http"
@ -844,18 +843,13 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
return nil, h.Errf("segment was not parsed as a subroute") 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 != "" { if expression != "" {
statusMatcher := caddy.ModuleMap{ statusMatcher := caddy.ModuleMap{
"expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}), "expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}),
} }
subroute.Routes[0].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher} for i := range subroute.Routes {
subroute.Routes[i].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
}
} }
return []ConfigValue{ return []ConfigValue{
{ {
@ -1166,11 +1160,6 @@ func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
if h.NextArg() { if h.NextArg() {
return nil, h.ArgErr() 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 return caddyhttp.VarsMiddleware{"log_skip": true}, nil
} }

View File

@ -16,7 +16,6 @@ package httpcaddyfile
import ( import (
"encoding/json" "encoding/json"
"maps"
"net" "net"
"slices" "slices"
"sort" "sort"
@ -174,12 +173,10 @@ func RegisterDirectiveOrder(dir string, position Positional, standardDir string)
if d != standardDir { if d != standardDir {
continue continue
} }
switch position { if position == Before {
case Before:
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...) newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
case After: } else if position == After {
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...) newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
case First, Last:
} }
break break
} }
@ -368,7 +365,9 @@ func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
// copy existing matcher definitions so we can augment // copy existing matcher definitions so we can augment
// new ones that are defined only in this scope // new ones that are defined only in this scope
matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs)) matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
maps.Copy(matcherDefs, h.matcherDefs) for key, val := range h.matcherDefs {
matcherDefs[key] = val
}
// find and extract any embedded matcher definitions in this scope // find and extract any embedded matcher definitions in this scope
for i := 0; i < len(segments); i++ { for i := 0; i < len(segments); i++ {
@ -484,29 +483,12 @@ func sortRoutes(routes []ConfigValue) {
// we can only confidently compare path lengths if both // we can only confidently compare path lengths if both
// directives have a single path to match (issue #5037) // directives have a single path to match (issue #5037)
if iPathLen > 0 && jPathLen > 0 { if iPathLen > 0 && jPathLen > 0 {
// trim the trailing wildcard if there is one
iPathTrimmed := strings.TrimSuffix(iPM[0], "*")
jPathTrimmed := strings.TrimSuffix(jPM[0], "*")
// if both paths are the same except for a trailing wildcard, // if both paths are the same except for a trailing wildcard,
// sort by the shorter path first (which is more specific) // sort by the shorter path first (which is more specific)
if iPathTrimmed == jPathTrimmed { if strings.TrimSuffix(iPM[0], "*") == strings.TrimSuffix(jPM[0], "*") {
return iPathLen < jPathLen return iPathLen < jPathLen
} }
// we use the trimmed length to compare the paths
// https://github.com/caddyserver/caddy/issues/7012#issuecomment-2870142195
// credit to https://github.com/Hellio404
// for sorts with many items, mixing matchers w/ and w/o wildcards will confuse the sort and result in incorrect orders
iPathLen = len(iPathTrimmed)
jPathLen = len(jPathTrimmed)
// if both paths have the same length, sort lexically
// https://github.com/caddyserver/caddy/pull/7015#issuecomment-2871993588
if iPathLen == jPathLen {
return iPathTrimmed < jPathTrimmed
}
// sort most-specific (longest) path first // sort most-specific (longest) path first
return iPathLen > jPathLen return iPathLen > jPathLen
} }

View File

@ -191,7 +191,7 @@ func (st ServerType) Setup(
metrics, _ := options["metrics"].(*caddyhttp.Metrics) metrics, _ := options["metrics"].(*caddyhttp.Metrics)
for _, s := range servers { for _, s := range servers {
if s.Metrics != nil { if s.Metrics != nil {
metrics = cmp.Or(metrics, &caddyhttp.Metrics{}) metrics = cmp.Or[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{})
metrics = &caddyhttp.Metrics{ metrics = &caddyhttp.Metrics{
PerHost: metrics.PerHost || s.Metrics.PerHost, PerHost: metrics.PerHost || s.Metrics.PerHost,
} }
@ -350,7 +350,7 @@ func (st ServerType) Setup(
// avoid duplicates by sorting + compacting // avoid duplicates by sorting + compacting
sort.Strings(defaultLog.Exclude) sort.Strings(defaultLog.Exclude)
defaultLog.Exclude = slices.Compact(defaultLog.Exclude) defaultLog.Exclude = slices.Compact[[]string, string](defaultLog.Exclude)
} }
} }
// we may have not actually added anything, so remove if empty // we may have not actually added anything, so remove if empty
@ -633,6 +633,12 @@ func (st *ServerType) serversFromPairings(
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig) srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
} }
srv.AutoHTTPS.IgnoreLoadedCerts = true srv.AutoHTTPS.IgnoreLoadedCerts = true
case "prefer_wildcard":
if srv.AutoHTTPS == nil {
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
}
srv.AutoHTTPS.PreferWildcard = true
} }
} }
@ -700,6 +706,16 @@ func (st *ServerType) serversFromPairings(
return specificity(iLongestHost) > specificity(jLongestHost) return specificity(iLongestHost) > specificity(jLongestHost)
}) })
// collect all hosts that have a wildcard in them
wildcardHosts := []string{}
for _, sblock := range p.serverBlocks {
for _, addr := range sblock.parsedKeys {
if strings.HasPrefix(addr.Host, "*.") {
wildcardHosts = append(wildcardHosts, addr.Host[2:])
}
}
}
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
@ -785,13 +801,7 @@ func (st *ServerType) serversFromPairings(
cp.FallbackSNI = fallbackSNI cp.FallbackSNI = fallbackSNI
} }
// only append this policy if it actually changes something, // only append this policy if it actually changes something
// or if the configuration explicitly automates certs for
// these names (this is necessary to hoist a connection policy
// above one that may manually load a wildcard cert that would
// otherwise clobber the automated one; the code that appends
// policies that manually load certs comes later, so they're
// lower in the list)
if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) { if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) {
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp) srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
hasCatchAllTLSConnPolicy = len(hosts) == 0 hasCatchAllTLSConnPolicy = len(hosts) == 0
@ -831,6 +841,18 @@ func (st *ServerType) serversFromPairings(
addressQualifiesForTLS = true addressQualifiesForTLS = true
} }
// If prefer wildcard is enabled, then we add hosts that are
// already covered by the wildcard to the skip list
if addressQualifiesForTLS && srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard {
baseDomain := addr.Host
if idx := strings.Index(baseDomain, "."); idx != -1 {
baseDomain = baseDomain[idx+1:]
}
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
srv.AutoHTTPS.SkipCerts = append(srv.AutoHTTPS.SkipCerts, addr.Host)
}
}
// predict whether auto-HTTPS will add the conn policy for us; if so, we // predict whether auto-HTTPS will add the conn policy for us; if so, we
// may not need to add one for this server // may not need to add one for this server
autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy && autoHTTPSWillAddConnPolicy = autoHTTPSWillAddConnPolicy &&
@ -1061,40 +1083,11 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
// if they're exactly equal in every way, just keep one of them // if they're exactly equal in every way, just keep one of them
if reflect.DeepEqual(cps[i], cps[j]) { if reflect.DeepEqual(cps[i], cps[j]) {
cps = slices.Delete(cps, j, j+1) cps = append(cps[:j], cps[j+1:]...)
i-- i--
break break
} }
// as a special case, if there are adjacent TLS conn policies that are identical except
// by their matchers, and the matchers are specifically just ServerName ("sni") matchers
// (by far the most common), we can combine them into a single policy
if i == j-1 && len(cps[i].MatchersRaw) == 1 && len(cps[j].MatchersRaw) == 1 {
if iSNIMatcherJSON, ok := cps[i].MatchersRaw["sni"]; ok {
if jSNIMatcherJSON, ok := cps[j].MatchersRaw["sni"]; ok {
// position of policies and the matcher criteria check out; if settings are
// the same, then we can combine the policies; we have to unmarshal and
// remarshal the matchers though
if cps[i].SettingsEqual(*cps[j]) {
var iSNIMatcher caddytls.MatchServerName
if err := json.Unmarshal(iSNIMatcherJSON, &iSNIMatcher); err == nil {
var jSNIMatcher caddytls.MatchServerName
if err := json.Unmarshal(jSNIMatcherJSON, &jSNIMatcher); err == nil {
iSNIMatcher = append(iSNIMatcher, jSNIMatcher...)
cps[i].MatchersRaw["sni"], err = json.Marshal(iSNIMatcher)
if err != nil {
return nil, fmt.Errorf("recombining SNI matchers: %v", err)
}
cps = slices.Delete(cps, j, j+1)
i--
break
}
}
}
}
}
}
// if they have the same matcher, try to reconcile each field: either they must // if they have the same matcher, try to reconcile each field: either they must
// be identical, or we have to be able to combine them safely // be identical, or we have to be able to combine them safely
if reflect.DeepEqual(cps[i].MatchersRaw, cps[j].MatchersRaw) { if reflect.DeepEqual(cps[i].MatchersRaw, cps[j].MatchersRaw) {
@ -1196,13 +1189,12 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
} }
} }
cps = slices.Delete(cps, j, j+1) cps = append(cps[:j], cps[j+1:]...)
i-- i--
break break
} }
} }
} }
return cps, nil return cps, nil
} }

View File

@ -246,8 +246,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
switch d.Val() { switch d.Val() {
case "per_host": case "per_host":
serverOpts.Metrics.PerHost = true serverOpts.Metrics.PerHost = true
default:
return nil, d.Errf("unrecognized metrics option '%s'", d.Val())
} }
} }

View File

@ -92,9 +92,11 @@ func (st ServerType) buildTLSApp(
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP) tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
} }
var wildcardHosts []string // collect all hosts that have a wildcard in them, and aren't HTTP // collect all hosts that have a wildcard in them, and arent HTTP
forcedAutomatedNames := make(map[string]struct{}) // explicitly configured to be automated, even if covered by a wildcard wildcardHosts := []string{}
// hosts that have been explicitly marked to be automated,
// even if covered by another wildcard
forcedAutomatedNames := make(map[string]struct{})
for _, p := range pairings { for _, p := range pairings {
var addresses []string var addresses []string
for _, addressWithProtocols := range p.addressesWithProtocols { for _, addressWithProtocols := range p.addressesWithProtocols {
@ -151,7 +153,7 @@ func (st ServerType) buildTLSApp(
ap.OnDemand = true ap.OnDemand = true
} }
// collect hosts that are forced to have certs automated for their specific name // collect hosts that are forced to be automated
if _, ok := sblock.pile["tls.force_automate"]; ok { if _, ok := sblock.pile["tls.force_automate"]; ok {
for _, host := range sblockHosts { for _, host := range sblockHosts {
forcedAutomatedNames[host] = struct{}{} forcedAutomatedNames[host] = struct{}{}
@ -338,7 +340,7 @@ func (st ServerType) buildTLSApp(
combined = reflect.New(reflect.TypeOf(cl)).Elem() combined = reflect.New(reflect.TypeOf(cl)).Elem()
} }
clVal := reflect.ValueOf(cl) clVal := reflect.ValueOf(cl)
for i := range clVal.Len() { for i := 0; i < clVal.Len(); i++ {
combined = reflect.Append(combined, clVal.Index(i)) combined = reflect.Append(combined, clVal.Index(i))
} }
loadersByName[name] = combined.Interface().(caddytls.CertificateLoader) loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
@ -373,9 +375,7 @@ func (st ServerType) buildTLSApp(
return nil, warnings, err return nil, warnings, err
} }
for _, cfg := range ech.Configs { for _, cfg := range ech.Configs {
if cfg.PublicName != "" { ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
}
} }
if tlsApp.Automation == nil { if tlsApp.Automation == nil {
tlsApp.Automation = new(caddytls.AutomationConfig) tlsApp.Automation = new(caddytls.AutomationConfig)
@ -469,7 +469,7 @@ func (st ServerType) buildTLSApp(
globalPreferredChains := options["preferred_chains"] globalPreferredChains := options["preferred_chains"]
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil
if hasGlobalACMEDefaults { if hasGlobalACMEDefaults {
for i := range tlsApp.Automation.Policies { for i := 0; i < len(tlsApp.Automation.Policies); i++ {
ap := tlsApp.Automation.Policies[i] ap := tlsApp.Automation.Policies[i]
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) { if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
// for public names, create default issuers which will later be filled in with configured global defaults // for public names, create default issuers which will later be filled in with configured global defaults

View File

@ -281,7 +281,7 @@ func validateTestPrerequisites(tc *Tester) error {
tc.t.Cleanup(func() { tc.t.Cleanup(func() {
os.Remove(f.Name()) os.Remove(f.Name())
}) })
if _, err := fmt.Fprintf(f, initConfig, tc.config.AdminPort); err != nil { if _, err := f.WriteString(fmt.Sprintf(initConfig, tc.config.AdminPort)); err != nil {
return err return err
} }

View File

@ -12,14 +12,13 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3" "github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v3/acme"
smallstepacme "github.com/smallstep/certificates/acme" smallstepacme "github.com/smallstep/certificates/acme"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/exp/zapslog" "go.uber.org/zap/exp/zapslog"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddytest"
) )
const acmeChallengePort = 9081 const acmeChallengePort = 9081

View File

@ -9,12 +9,11 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest"
"github.com/mholt/acmez/v3" "github.com/mholt/acmez/v3"
"github.com/mholt/acmez/v3/acme" "github.com/mholt/acmez/v3/acme"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/exp/zapslog" "go.uber.org/zap/exp/zapslog"
"github.com/caddyserver/caddy/v2/caddytest"
) )
func TestACMEServerDirectory(t *testing.T) { func TestACMEServerDirectory(t *testing.T) {

View File

@ -1,72 +0,0 @@
{
pki {
ca custom-ca {
name "Custom CA"
}
}
}
acme.example.com {
acme_server {
ca custom-ca
allow {
domains host-1.internal.example.com host-2.internal.example.com
}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"acme.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "custom-ca",
"handler": "acme_server",
"policy": {
"allow": {
"domains": [
"host-1.internal.example.com",
"host-2.internal.example.com"
]
}
}
}
]
}
]
}
],
"terminal": true
}
]
}
}
},
"pki": {
"certificate_authorities": {
"custom-ca": {
"name": "Custom CA"
}
}
}
}
}

View File

@ -1,80 +0,0 @@
{
pki {
ca custom-ca {
name "Custom CA"
}
}
}
acme.example.com {
acme_server {
ca custom-ca
allow {
domains host-1.internal.example.com host-2.internal.example.com
}
deny {
domains dc.internal.example.com
}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"acme.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "custom-ca",
"handler": "acme_server",
"policy": {
"allow": {
"domains": [
"host-1.internal.example.com",
"host-2.internal.example.com"
]
},
"deny": {
"domains": [
"dc.internal.example.com"
]
}
}
}
]
}
]
}
],
"terminal": true
}
]
}
}
},
"pki": {
"certificate_authorities": {
"custom-ca": {
"name": "Custom CA"
}
}
}
}
}

View File

@ -1,71 +0,0 @@
{
pki {
ca custom-ca {
name "Custom CA"
}
}
}
acme.example.com {
acme_server {
ca custom-ca
deny {
domains dc.internal.example.com
}
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"acme.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"ca": "custom-ca",
"handler": "acme_server",
"policy": {
"deny": {
"domains": [
"dc.internal.example.com"
]
}
}
}
]
}
]
}
],
"terminal": true
}
]
}
}
},
"pki": {
"certificate_authorities": {
"custom-ca": {
"name": "Custom CA"
}
}
}
}
}

View File

@ -0,0 +1,109 @@
{
auto_https prefer_wildcard
}
*.example.com {
tls {
dns mock
}
respond "fallback"
}
foo.example.com {
respond "foo"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"foo.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"skip_certificates": [
"foo.example.com"
],
"prefer_wildcard": true
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"*.example.com"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}

View File

@ -0,0 +1,268 @@
{
auto_https prefer_wildcard
}
# Covers two domains
*.one.example.com {
tls {
dns mock
}
respond "one fallback"
}
# Is covered, should not get its own AP
foo.one.example.com {
respond "foo one"
}
# This one has its own tls config so it doesn't get covered (escape hatch)
bar.one.example.com {
respond "bar one"
tls bar@bar.com
}
# Covers nothing but AP gets consolidated with the first
*.two.example.com {
tls {
dns mock
}
respond "two fallback"
}
# Is HTTP so it should not cover
http://*.three.example.com {
respond "three fallback"
}
# Has no wildcard coverage so it gets an AP
foo.three.example.com {
respond "foo three"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"foo.three.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo three",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"foo.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "foo one",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"bar.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "bar one",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.one.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "one fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.two.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "two fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"skip_certificates": [
"foo.one.example.com",
"bar.one.example.com"
],
"prefer_wildcard": true
}
},
"srv1": {
"listen": [
":80"
],
"routes": [
{
"match": [
{
"host": [
"*.three.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "three fallback",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"automatic_https": {
"prefer_wildcard": true
}
}
}
},
"tls": {
"automation": {
"policies": [
{
"subjects": [
"foo.three.example.com"
]
},
{
"subjects": [
"bar.one.example.com"
],
"issuers": [
{
"email": "bar@bar.com",
"module": "acme"
},
{
"ca": "https://acme.zerossl.com/v2/DV90",
"email": "bar@bar.com",
"module": "acme"
}
]
},
{
"subjects": [
"*.one.example.com",
"*.two.example.com"
],
"issuers": [
{
"challenges": {
"dns": {
"provider": {
"name": "mock"
}
}
},
"module": "acme"
}
]
}
]
}
}
}
}

View File

@ -106,29 +106,20 @@ example.com {
"handler": "subroute", "handler": "subroute",
"routes": [ "routes": [
{ {
"group": "group0",
"handle": [ "handle": [
{ {
"handler": "subroute", "handler": "rewrite",
"routes": [ "uri": "/{http.error.status_code}.html"
{ }
"group": "group0", ]
"handle": [ },
{ {
"handler": "rewrite", "handle": [
"uri": "/{http.error.status_code}.html" {
} "handler": "file_server",
] "hide": [
}, "./Caddyfile"
{
"handle": [
{
"handler": "file_server",
"hide": [
"./Caddyfile"
]
}
]
}
] ]
} }
] ]

View File

@ -165,17 +165,8 @@ bar.localhost {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "404 or 410 error",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "404 or 410 error",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [
@ -187,17 +178,8 @@ bar.localhost {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "Error In range [500 .. 599]",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "Error In range [500 .. 599]",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [
@ -226,17 +208,8 @@ bar.localhost {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "404 or 410 error from second site",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "404 or 410 error from second site",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [
@ -248,17 +221,8 @@ bar.localhost {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "Error In range [500 .. 599] from second site",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "Error In range [500 .. 599] from second site",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [

View File

@ -96,17 +96,8 @@ localhost:3010 {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "Error in the [400 .. 499] range",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "Error in the [400 .. 499] range",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [

View File

@ -116,17 +116,8 @@ localhost:2099 {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "Error in the [400 .. 499] range",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "Error in the [400 .. 499] range",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [
@ -138,17 +129,8 @@ localhost:2099 {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "Error code is equal to 500 or in the [300..399] range",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "Error code is equal to 500 or in the [300..399] range",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [

View File

@ -96,17 +96,8 @@ localhost:3010 {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "404 or 410 error",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "404 or 410 error",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [

View File

@ -116,17 +116,8 @@ localhost:2099 {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "Error in the [400 .. 499] range",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "Error in the [400 .. 499] range",
"handler": "static_response"
}
]
}
]
} }
], ],
"match": [ "match": [
@ -138,17 +129,8 @@ localhost:2099 {
{ {
"handle": [ "handle": [
{ {
"handler": "subroute", "body": "Fallback route: code outside the [400..499] range",
"routes": [ "handler": "static_response"
{
"handle": [
{
"body": "Fallback route: code outside the [400..499] range",
"handler": "static_response"
}
]
}
]
} }
] ]
} }

View File

@ -1,260 +0,0 @@
{
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
}
]
}
}
}
}
}
}

View File

@ -1,41 +0,0 @@
:8884
reverse_proxy 127.0.0.1:65535 {
transport http {
forward_proxy_url http://localhost:8080
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"network_proxy": {
"from": "url",
"url": "http://localhost:8080"
},
"protocol": "http"
},
"upstreams": [
{
"dial": "127.0.0.1:65535"
}
]
}
]
}
]
}
}
}
}
}

View File

@ -1,40 +0,0 @@
:8884
reverse_proxy 127.0.0.1:65535 {
transport http {
network_proxy none
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"network_proxy": {
"from": "none"
},
"protocol": "http"
},
"upstreams": [
{
"dial": "127.0.0.1:65535"
}
]
}
]
}
]
}
}
}
}
}

View File

@ -1,41 +0,0 @@
:8884
reverse_proxy 127.0.0.1:65535 {
transport http {
network_proxy url http://localhost:8080
}
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":8884"
],
"routes": [
{
"handle": [
{
"handler": "reverse_proxy",
"transport": {
"network_proxy": {
"from": "url",
"url": "http://localhost:8080"
},
"protocol": "http"
},
"upstreams": [
{
"dial": "127.0.0.1:65535"
}
]
}
]
}
]
}
}
}
}
}

View File

@ -131,7 +131,13 @@ shadowed.example.com {
{ {
"match": { "match": {
"sni": [ "sni": [
"automated1.example.com", "automated1.example.com"
]
}
},
{
"match": {
"sni": [
"automated2.example.com" "automated2.example.com"
] ]
} }

View File

@ -1,87 +0,0 @@
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"
}
},
{}
]
}
}
}
}
}

View File

@ -1,85 +0,0 @@
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"
}
},
{}
]
}
}
}
}
}

View File

@ -1,94 +0,0 @@
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"
}
},
{}
]
}
}
}
}
}

View File

@ -1,87 +0,0 @@
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"
}
},
{}
]
}
}
}
}
}

View File

@ -1,85 +0,0 @@
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"
}
},
{}
]
}
}
}
}
}

View File

@ -1,94 +0,0 @@
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"
}
},
{}
]
}
}
}
}
}

View File

@ -10,6 +10,7 @@ import (
"testing" "testing"
"github.com/caddyserver/caddy/v2/caddytest" "github.com/caddyserver/caddy/v2/caddytest"
_ "github.com/caddyserver/caddy/v2/internal/testmocks" _ "github.com/caddyserver/caddy/v2/internal/testmocks"
) )

View File

@ -615,6 +615,7 @@ func TestReplaceWithReplacementPlaceholder(t *testing.T) {
respond "{query}"`, "caddyfile") respond "{query}"`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz") tester.AssertGetResponse("http://localhost:9080/endpoint?placeholder=baz&foo=bar", 200, "foo=baz&placeholder=baz")
} }
func TestReplaceWithKeyPlaceholder(t *testing.T) { func TestReplaceWithKeyPlaceholder(t *testing.T) {
@ -782,46 +783,6 @@ func TestHandleErrorRangeAndCodes(t *testing.T) {
tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range") 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) { func TestInvalidSiteAddressesAsDirectives(t *testing.T) {
type testCase struct { type testCase struct {
config, expectedError string config, expectedError string

View File

@ -3,11 +3,10 @@ package integration
import ( import (
"context" "context"
"github.com/caddyserver/certmagic"
"github.com/libdns/libdns"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/certmagic"
"github.com/libdns/libdns"
) )
func init() { func init() {
@ -56,9 +55,7 @@ func (MockDNSProvider) SetRecords(ctx context.Context, zone string, recs []libdn
} }
// Interface guard // Interface guard
var ( var _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil)
_ caddyfile.Unmarshaler = (*MockDNSProvider)(nil) var _ certmagic.DNSProvider = (*MockDNSProvider)(nil)
_ certmagic.DNSProvider = (*MockDNSProvider)(nil) var _ caddy.Provisioner = (*MockDNSProvider)(nil)
_ caddy.Provisioner = (*MockDNSProvider)(nil) var _ caddy.Module = (*MockDNSProvider)(nil)
_ caddy.Module = (*MockDNSProvider)(nil)
)

View File

@ -13,10 +13,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/caddyserver/caddy/v2/caddytest"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/h2c" "golang.org/x/net/http2/h2c"
"github.com/caddyserver/caddy/v2/caddytest"
) )
// (see https://github.com/caddyserver/caddy/issues/3556 for use case) // (see https://github.com/caddyserver/caddy/issues/3556 for use case)

View File

@ -24,7 +24,6 @@ import (
"io" "io"
"io/fs" "io/fs"
"log" "log"
"maps"
"net" "net"
"net/http" "net/http"
"os" "os"
@ -172,10 +171,6 @@ func cmdStart(fl Flags) (int, error) {
func cmdRun(fl Flags) (int, error) { func cmdRun(fl Flags) (int, error) {
caddy.TrapSignals() caddy.TrapSignals()
logger := caddy.Log()
undoMaxProcs := setResourceLimits(logger)
defer undoMaxProcs()
configFlag := fl.String("config") configFlag := fl.String("config")
configAdapterFlag := fl.String("adapter") configAdapterFlag := fl.String("adapter")
resumeFlag := fl.Bool("resume") resumeFlag := fl.Bool("resume")
@ -201,18 +196,18 @@ func cmdRun(fl Flags) (int, error) {
config, err = os.ReadFile(caddy.ConfigAutosavePath) config, err = os.ReadFile(caddy.ConfigAutosavePath)
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
// not a bad error; just can't resume if autosave file doesn't exist // not a bad error; just can't resume if autosave file doesn't exist
logger.Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath)) caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
resumeFlag = false resumeFlag = false
} else if err != nil { } else if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} else { } else {
if configFlag == "" { if configFlag == "" {
logger.Info("resuming from last configuration", caddy.Log().Info("resuming from last configuration",
zap.String("autosave_file", caddy.ConfigAutosavePath)) zap.String("autosave_file", caddy.ConfigAutosavePath))
} else { } else {
// if they also specified a config file, user should be aware that we're not // if they also specified a config file, user should be aware that we're not
// using it (doing so could lead to data/config loss by overwriting!) // using it (doing so could lead to data/config loss by overwriting!)
logger.Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration", caddy.Log().Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration",
zap.String("autosave_file", caddy.ConfigAutosavePath)) zap.String("autosave_file", caddy.ConfigAutosavePath))
} }
} }
@ -230,7 +225,7 @@ func cmdRun(fl Flags) (int, error) {
if pidfileFlag != "" { if pidfileFlag != "" {
err := caddy.PIDFile(pidfileFlag) err := caddy.PIDFile(pidfileFlag)
if err != nil { if err != nil {
logger.Error("unable to write PID file", caddy.Log().Error("unable to write PID file",
zap.String("pidfile", pidfileFlag), zap.String("pidfile", pidfileFlag),
zap.Error(err)) zap.Error(err))
} }
@ -241,7 +236,7 @@ func cmdRun(fl Flags) (int, error) {
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err) return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
} }
logger.Info("serving initial configuration") caddy.Log().Info("serving initial configuration")
// if we are to report to another process the successful start // if we are to report to another process the successful start
// of the server, do so now by echoing back contents of stdin // of the server, do so now by echoing back contents of stdin
@ -277,15 +272,15 @@ func cmdRun(fl Flags) (int, error) {
switch runtime.GOOS { switch runtime.GOOS {
case "windows": case "windows":
if os.Getenv("HOME") == "" && os.Getenv("USERPROFILE") == "" && !hasXDG { if os.Getenv("HOME") == "" && os.Getenv("USERPROFILE") == "" && !hasXDG {
logger.Warn("neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy") caddy.Log().Warn("neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy")
} }
case "plan9": case "plan9":
if os.Getenv("home") == "" && !hasXDG { if os.Getenv("home") == "" && !hasXDG {
logger.Warn("$home environment variable is empty - please fix; some assets might be stored in ./caddy") caddy.Log().Warn("$home environment variable is empty - please fix; some assets might be stored in ./caddy")
} }
default: default:
if os.Getenv("HOME") == "" && !hasXDG { if os.Getenv("HOME") == "" && !hasXDG {
logger.Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy") caddy.Log().Warn("$HOME environment variable is empty - please fix; some assets might be stored in ./caddy")
} }
} }
@ -704,7 +699,9 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
if body != nil { if body != nil {
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
} }
maps.Copy(req.Header, headers) for k, v := range headers {
req.Header[k] = v
}
// make an HTTP client that dials our network type, since admin // make an HTTP client that dials our network type, since admin
// endpoints aren't always TCP, which is what the default transport // endpoints aren't always TCP, which is what the default transport

View File

@ -20,7 +20,6 @@ import (
"os" "os"
"regexp" "regexp"
"strings" "strings"
"sync"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
@ -81,16 +80,10 @@ type CommandFunc func(Flags) (int, error)
// Commands returns a list of commands initialised by // Commands returns a list of commands initialised by
// RegisterCommand // RegisterCommand
func Commands() map[string]Command { func Commands() map[string]Command {
commandsMu.RLock()
defer commandsMu.RUnlock()
return commands return commands
} }
var ( var commands = make(map[string]Command)
commandsMu sync.RWMutex
commands = make(map[string]Command)
)
func init() { func init() {
RegisterCommand(Command{ RegisterCommand(Command{
@ -448,7 +441,7 @@ EXPERIMENTAL: May be changed or removed.
}) })
defaultFactory.Use(func(rootCmd *cobra.Command) { defaultFactory.Use(func(rootCmd *cobra.Command) {
manpageCommand := Command{ rootCmd.AddCommand(caddyCmdToCobra(Command{
Name: "manpage", Name: "manpage",
Usage: "--directory <path>", Usage: "--directory <path>",
Short: "Generates the manual pages for Caddy commands", Short: "Generates the manual pages for Caddy commands",
@ -478,12 +471,11 @@ argument of --directory. If the directory does not exist, it will be created.
return caddy.ExitCodeSuccess, nil return caddy.ExitCodeSuccess, nil
}) })
}, },
} }))
// source: https://github.com/spf13/cobra/blob/main/shell_completions.md // source: https://github.com/spf13/cobra/blob/main/shell_completions.md
completionCommand := Command{ rootCmd.AddCommand(&cobra.Command{
Name: "completion", Use: "completion [bash|zsh|fish|powershell]",
Usage: "[bash|zsh|fish|powershell]",
Short: "Generate completion script", Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions: Long: fmt.Sprintf(`To load completions:
@ -524,37 +516,24 @@ argument of --directory. If the directory does not exist, it will be created.
PS> %[1]s completion powershell > %[1]s.ps1 PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile. # and source this file from your PowerShell profile.
`, rootCmd.Root().Name()), `, rootCmd.Root().Name()),
CobraFunc: func(cmd *cobra.Command) { DisableFlagsInUseLine: true,
cmd.DisableFlagsInUseLine = true ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
cmd.ValidArgs = []string{"bash", "zsh", "fish", "powershell"} Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
cmd.Args = cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs) RunE: func(cmd *cobra.Command, args []string) error {
cmd.RunE = func(cmd *cobra.Command, args []string) error { switch args[0] {
switch args[0] { case "bash":
case "bash": return cmd.Root().GenBashCompletion(os.Stdout)
return cmd.Root().GenBashCompletion(os.Stdout) case "zsh":
case "zsh": return cmd.Root().GenZshCompletion(os.Stdout)
return cmd.Root().GenZshCompletion(os.Stdout) case "fish":
case "fish": return cmd.Root().GenFishCompletion(os.Stdout, true)
return cmd.Root().GenFishCompletion(os.Stdout, true) case "powershell":
case "powershell": return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) default:
default: return fmt.Errorf("unrecognized shell: %s", args[0])
return fmt.Errorf("unrecognized shell: %s", args[0])
}
} }
}, },
} })
rootCmd.AddCommand(caddyCmdToCobra(manpageCommand))
rootCmd.AddCommand(caddyCmdToCobra(completionCommand))
// add manpage and completion commands to the map of
// available commands, because they're not registered
// through RegisterCommand.
commandsMu.Lock()
commands[manpageCommand.Name] = manpageCommand
commands[completionCommand.Name] = completionCommand
commandsMu.Unlock()
}) })
} }
@ -573,9 +552,6 @@ argument of --directory. If the directory does not exist, it will be created.
// //
// This function should be used in init(). // This function should be used in init().
func RegisterCommand(cmd Command) { func RegisterCommand(cmd Command) {
commandsMu.Lock()
defer commandsMu.Unlock()
if cmd.Name == "" { if cmd.Name == "" {
panic("command name is required") panic("command name is required")
} }
@ -594,7 +570,6 @@ func RegisterCommand(cmd Command) {
defaultFactory.Use(func(rootCmd *cobra.Command) { defaultFactory.Use(func(rootCmd *cobra.Command) {
rootCmd.AddCommand(caddyCmdToCobra(cmd)) rootCmd.AddCommand(caddyCmdToCobra(cmd))
}) })
commands[cmd.Name] = cmd
} }
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`) var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)

View File

@ -1,39 +0,0 @@
package caddycmd
import (
"maps"
"reflect"
"slices"
"testing"
)
func TestCommandsAreAvailable(t *testing.T) {
// trigger init, and build the default factory, so that
// all commands from this package are available
cmd := defaultFactory.Build()
if cmd == nil {
t.Fatal("default factory failed to build")
}
// check that the default factory has 17 commands; it doesn't
// include the commands registered through calls to init in
// other packages
cmds := Commands()
if len(cmds) != 17 {
t.Errorf("expected 17 commands, got %d", len(cmds))
}
commandNames := slices.Collect(maps.Keys(cmds))
slices.Sort(commandNames)
expectedCommandNames := []string{
"adapt", "add-package", "build-info", "completion",
"environ", "fmt", "list-modules", "manpage",
"reload", "remove-package", "run", "start",
"stop", "storage", "upgrade", "validate", "version",
}
if !reflect.DeepEqual(expectedCommandNames, commandNames) {
t.Errorf("expected %v, got %v", expectedCommandNames, commandNames)
}
}

View File

@ -69,6 +69,30 @@ func Main() {
os.Exit(caddy.ExitCodeFailedStartup) os.Exit(caddy.ExitCodeFailedStartup)
} }
logger := caddy.Log()
// Configure the maximum number of CPUs to use to match the Linux container quota (if any)
// See https://pkg.go.dev/runtime#GOMAXPROCS
undo, err := maxprocs.Set(maxprocs.Logger(logger.Sugar().Infof))
defer undo()
if err != nil {
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
}
// Configure the maximum memory to use to match the Linux container quota (if any) or system memory
// See https://pkg.go.dev/runtime/debug#SetMemoryLimit
_, _ = memlimit.SetGoMemLimitWithOpts(
memlimit.WithLogger(
slog.New(zapslog.NewHandler(logger.Core())),
),
memlimit.WithProvider(
memlimit.ApplyFallback(
memlimit.FromCgroup,
memlimit.FromSystem,
),
),
)
if err := defaultFactory.Build().Execute(); err != nil { if err := defaultFactory.Build().Execute(); err != nil {
var exitError *exitError var exitError *exitError
if errors.As(err, &exitError) { if errors.As(err, &exitError) {
@ -418,7 +442,7 @@ func parseEnvFile(envInput io.Reader) (map[string]string, error) {
// quoted value: support newlines // quoted value: support newlines
if strings.HasPrefix(val, `"`) || strings.HasPrefix(val, "'") { if strings.HasPrefix(val, `"`) || strings.HasPrefix(val, "'") {
quote := string(val[0]) quote := string(val[0])
for !strings.HasSuffix(line, quote) || strings.HasSuffix(line, `\`+quote) { for !(strings.HasSuffix(line, quote) && !strings.HasSuffix(line, `\`+quote)) {
val = strings.ReplaceAll(val, `\`+quote, quote) val = strings.ReplaceAll(val, `\`+quote, quote)
if !scanner.Scan() { if !scanner.Scan() {
break break
@ -464,31 +488,6 @@ func printEnvironment() {
} }
} }
func setResourceLimits(logger *zap.Logger) func() {
// Configure the maximum number of CPUs to use to match the Linux container quota (if any)
// See https://pkg.go.dev/runtime#GOMAXPROCS
undo, err := maxprocs.Set(maxprocs.Logger(logger.Sugar().Infof))
if err != nil {
logger.Warn("failed to set GOMAXPROCS", zap.Error(err))
}
// Configure the maximum memory to use to match the Linux container quota (if any) or system memory
// See https://pkg.go.dev/runtime/debug#SetMemoryLimit
_, _ = memlimit.SetGoMemLimitWithOpts(
memlimit.WithLogger(
slog.New(zapslog.NewHandler(logger.Core())),
),
memlimit.WithProvider(
memlimit.ApplyFallback(
memlimit.FromCgroup,
memlimit.FromSystem,
),
),
)
return undo
}
// StringSlice is a flag.Value that enables repeated use of a string flag. // StringSlice is a flag.Value that enables repeated use of a string flag.
type StringSlice []string type StringSlice []string

View File

@ -235,6 +235,7 @@ func Test_isCaddyfile(t *testing.T) {
wantErr: false, wantErr: false,
}, },
{ {
name: "json is not caddyfile but not error", name: "json is not caddyfile but not error",
args: args{ args: args{
configFile: "./Caddyfile.json", configFile: "./Caddyfile.json",
@ -244,6 +245,7 @@ func Test_isCaddyfile(t *testing.T) {
wantErr: false, wantErr: false,
}, },
{ {
name: "prefix of Caddyfile and ./ with any extension is Caddyfile", name: "prefix of Caddyfile and ./ with any extension is Caddyfile",
args: args{ args: args{
configFile: "./Caddyfile.prd", configFile: "./Caddyfile.prd",
@ -253,6 +255,7 @@ func Test_isCaddyfile(t *testing.T) {
wantErr: false, wantErr: false,
}, },
{ {
name: "prefix of Caddyfile without ./ with any extension is Caddyfile", name: "prefix of Caddyfile without ./ with any extension is Caddyfile",
args: args{ args: args{
configFile: "Caddyfile.prd", configFile: "Caddyfile.prd",

View File

@ -84,7 +84,7 @@ func cmdAddPackage(fl Flags) (int, error) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err) return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err)
} }
// only allow a version to be specified if it's different from the existing version // only allow a version to be specified if it's different from the existing version
if _, ok := pluginPkgs[module]; ok && (version == "" || pluginPkgs[module].Version == version) { if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added") return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added")
} }
pluginPkgs[module] = pluginPackage{Version: version, Path: module} pluginPkgs[module] = pluginPackage{Version: version, Path: module}

View File

@ -91,14 +91,14 @@ func (ctx *Context) OnCancel(f func()) {
ctx.cleanupFuncs = append(ctx.cleanupFuncs, f) ctx.cleanupFuncs = append(ctx.cleanupFuncs, f)
} }
// FileSystems returns a ref to the FilesystemMap. // Filesystems returns a ref to the FilesystemMap.
// EXPERIMENTAL: This API is subject to change. // EXPERIMENTAL: This API is subject to change.
func (ctx *Context) FileSystems() FileSystems { func (ctx *Context) Filesystems() FileSystems {
// if no config is loaded, we use a default filesystemmap, which includes the osfs // if no config is loaded, we use a default filesystemmap, which includes the osfs
if ctx.cfg == nil { if ctx.cfg == nil {
return &filesystems.FileSystemMap{} return &filesystems.FilesystemMap{}
} }
return ctx.cfg.fileSystems return ctx.cfg.filesystems
} }
// Returns the active metrics registry for the context // Returns the active metrics registry for the context
@ -277,14 +277,6 @@ func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error)
return result, nil return result, nil
} }
// emitEvent is a small convenience method so the caddy core can emit events, if the event app is configured.
func (ctx Context) emitEvent(name string, data map[string]any) Event {
if ctx.cfg == nil || ctx.cfg.eventEmitter == nil {
return Event{}
}
return ctx.cfg.eventEmitter.Emit(ctx, name, data)
}
// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]any. // loadModulesFromSomeMap loads modules from val, which must be a type of map[string]any.
// Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the module // Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the module
// name) or as a regular map (key is not the module name, and module name is defined inline). // name) or as a regular map (key is not the module name, and module name is defined inline).
@ -437,14 +429,6 @@ func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error
ctx.moduleInstances[id] = append(ctx.moduleInstances[id], val) ctx.moduleInstances[id] = append(ctx.moduleInstances[id], val)
// if the loaded module happens to be an app that can emit events, store it so the
// core can have access to emit events without an import cycle
if ee, ok := val.(eventEmitter); ok {
if _, ok := ee.(App); ok {
ctx.cfg.eventEmitter = ee
}
}
return val, nil return val, nil
} }
@ -577,11 +561,11 @@ func (ctx Context) Slogger() *slog.Logger {
if err != nil { if err != nil {
panic("config missing, unable to create dev logger: " + err.Error()) panic("config missing, unable to create dev logger: " + err.Error())
} }
return slog.New(zapslog.NewHandler(l.Core())) return slog.New(zapslog.NewHandler(l.Core(), nil))
} }
mod := ctx.Module() mod := ctx.Module()
if mod == nil { if mod == nil {
return slog.New(zapslog.NewHandler(Log().Core())) return slog.New(zapslog.NewHandler(Log().Core(), nil))
} }
return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(), return slog.New(zapslog.NewHandler(ctx.cfg.Logging.Logger(mod).Core(),
zapslog.WithName(string(mod.CaddyModule().ID)), zapslog.WithName(string(mod.CaddyModule().ID)),
@ -616,11 +600,3 @@ func (ctx *Context) WithValue(key, value any) Context {
exitFuncs: ctx.exitFuncs, exitFuncs: ctx.exitFuncs,
} }
} }
// eventEmitter is a small interface that inverts dependencies for
// the caddyevents package, so the core can emit events without an
// import cycle (i.e. the caddy package doesn't have to import
// the caddyevents package, which imports the caddy package).
type eventEmitter interface {
Emit(ctx Context, eventName string, data map[string]any) Event
}

12
go.mod
View File

@ -8,18 +8,18 @@ require (
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/alecthomas/chroma/v2 v2.15.0 github.com/alecthomas/chroma/v2 v2.15.0
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
github.com/caddyserver/certmagic v0.23.0 github.com/caddyserver/certmagic v0.22.0
github.com/caddyserver/zerossl v0.1.3 github.com/caddyserver/zerossl v0.1.3
github.com/cloudflare/circl v1.6.1 github.com/cloudflare/circl v1.6.0
github.com/dustin/go-humanize v1.0.1 github.com/dustin/go-humanize v1.0.1
github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/chi/v5 v5.2.1
github.com/google/cel-go v0.24.1 github.com/google/cel-go v0.24.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/klauspost/compress v1.18.0 github.com/klauspost/compress v1.18.0
github.com/klauspost/cpuid/v2 v2.2.10 github.com/klauspost/cpuid/v2 v2.2.10
github.com/mholt/acmez/v3 v3.1.2 github.com/mholt/acmez/v3 v3.1.0
github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.51.0 github.com/quic-go/quic-go v0.50.0
github.com/smallstep/certificates v0.26.1 github.com/smallstep/certificates v0.26.1
github.com/smallstep/nosql v0.6.1 github.com/smallstep/nosql v0.6.1
github.com/smallstep/truststore v0.13.0 github.com/smallstep/truststore v0.13.0
@ -39,7 +39,7 @@ require (
go.uber.org/zap/exp v0.3.0 go.uber.org/zap/exp v0.3.0
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.36.0
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810
golang.org/x/net v0.38.0 golang.org/x/net v0.37.0
golang.org/x/sync v0.12.0 golang.org/x/sync v0.12.0
golang.org/x/term v0.30.0 golang.org/x/term v0.30.0
golang.org/x/time v0.11.0 golang.org/x/time v0.11.0
@ -116,7 +116,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.3 // indirect github.com/jackc/pgx/v4 v4.18.3 // indirect
github.com/libdns/libdns v1.0.0-beta.1 github.com/libdns/libdns v0.2.3
github.com/manifoldco/promptui v0.9.0 // indirect github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect

24
go.sum
View File

@ -93,8 +93,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/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/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.22.0 h1:hi2skv2jouUw9uQUEyYSTTmqPZPHgf61dOANSIVCLOw=
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4= github.com/caddyserver/certmagic v0.22.0/go.mod h1:Vc0msarAPhOagbDc/SU6M2zbzdwVuZ0lkTh2EqtH4vs=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@ -113,8 +113,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/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.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -327,8 +327,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= github.com/libdns/libdns v0.2.3 h1:ba30K4ObwMGB/QTmqUxf3H4/GmUrCAIkMWejeGl12v8=
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libdns/libdns v0.2.3/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -347,8 +347,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc= github.com/mholt/acmez/v3 v3.1.0 h1:RlOx2SSZ8dIAM5GfkMe8TdaxjjkiHTGorlMUt8GeMzg=
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ= github.com/mholt/acmez/v3 v3.1.0/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= 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 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
@ -397,8 +397,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= 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.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
github.com/quic-go/quic-go v0.51.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ= github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 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/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
@ -633,8 +633,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-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-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=

View File

@ -7,10 +7,10 @@ import (
) )
const ( const (
DefaultFileSystemKey = "default" DefaultFilesystemKey = "default"
) )
var DefaultFileSystem = &wrapperFs{key: DefaultFileSystemKey, FS: OsFS{}} var DefaultFilesystem = &wrapperFs{key: DefaultFilesystemKey, FS: OsFS{}}
// wrapperFs exists so can easily add to wrapperFs down the line // wrapperFs exists so can easily add to wrapperFs down the line
type wrapperFs struct { type wrapperFs struct {
@ -18,24 +18,24 @@ type wrapperFs struct {
fs.FS fs.FS
} }
// FileSystemMap stores a map of filesystems // FilesystemMap stores a map of filesystems
// the empty key will be overwritten to be the default key // the empty key will be overwritten to be the default key
// it includes a default filesystem, based off the os fs // it includes a default filesystem, based off the os fs
type FileSystemMap struct { type FilesystemMap struct {
m sync.Map m sync.Map
} }
// note that the first invocation of key cannot be called in a racy context. // note that the first invocation of key cannot be called in a racy context.
func (f *FileSystemMap) key(k string) string { func (f *FilesystemMap) key(k string) string {
if k == "" { if k == "" {
k = DefaultFileSystemKey k = DefaultFilesystemKey
} }
return k return k
} }
// Register will add the filesystem with key to later be retrieved // Register will add the filesystem with key to later be retrieved
// A call with a nil fs will call unregister, ensuring that a call to Default() will never be nil // A call with a nil fs will call unregister, ensuring that a call to Default() will never be nil
func (f *FileSystemMap) Register(k string, v fs.FS) { func (f *FilesystemMap) Register(k string, v fs.FS) {
k = f.key(k) k = f.key(k)
if v == nil { if v == nil {
f.Unregister(k) f.Unregister(k)
@ -47,23 +47,23 @@ func (f *FileSystemMap) Register(k string, v fs.FS) {
// Unregister will remove the filesystem with key from the filesystem map // Unregister will remove the filesystem with key from the filesystem map
// if the key is the default key, it will set the default to the osFS instead of deleting it // if the key is the default key, it will set the default to the osFS instead of deleting it
// modules should call this on cleanup to be safe // modules should call this on cleanup to be safe
func (f *FileSystemMap) Unregister(k string) { func (f *FilesystemMap) Unregister(k string) {
k = f.key(k) k = f.key(k)
if k == DefaultFileSystemKey { if k == DefaultFilesystemKey {
f.m.Store(k, DefaultFileSystem) f.m.Store(k, DefaultFilesystem)
} else { } else {
f.m.Delete(k) f.m.Delete(k)
} }
} }
// Get will get a filesystem with a given key // Get will get a filesystem with a given key
func (f *FileSystemMap) Get(k string) (v fs.FS, ok bool) { func (f *FilesystemMap) Get(k string) (v fs.FS, ok bool) {
k = f.key(k) k = f.key(k)
c, ok := f.m.Load(strings.TrimSpace(k)) c, ok := f.m.Load(strings.TrimSpace(k))
if !ok { if !ok {
if k == DefaultFileSystemKey { if k == DefaultFilesystemKey {
f.m.Store(k, DefaultFileSystem) f.m.Store(k, DefaultFilesystem)
return DefaultFileSystem, true return DefaultFilesystem, true
} }
return nil, ok return nil, ok
} }
@ -71,7 +71,7 @@ func (f *FileSystemMap) Get(k string) (v fs.FS, ok bool) {
} }
// Default will get the default filesystem in the filesystem map // Default will get the default filesystem in the filesystem map
func (f *FileSystemMap) Default() fs.FS { func (f *FilesystemMap) Default() fs.FS {
val, _ := f.Get(DefaultFileSystemKey) val, _ := f.Get(DefaultFilesystemKey)
return val return val
} }

View File

@ -1,22 +0,0 @@
package internal
import "fmt"
// MaxSizeSubjectsListForLog returns the keys in the map as a slice of maximum length
// maxToDisplay. It is useful for logging domains being managed, for example, since a
// map is typically needed for quick lookup, but a slice is needed for logging, and this
// can be quite a doozy since there may be a huge amount (hundreds of thousands).
func MaxSizeSubjectsListForLog(subjects map[string]struct{}, maxToDisplay int) []string {
numberOfNamesToDisplay := min(len(subjects), maxToDisplay)
domainsToDisplay := make([]string, 0, numberOfNamesToDisplay)
for domain := range subjects {
domainsToDisplay = append(domainsToDisplay, domain)
if len(domainsToDisplay) >= numberOfNamesToDisplay {
break
}
}
if len(subjects) > maxToDisplay {
domainsToDisplay = append(domainsToDisplay, fmt.Sprintf("(and %d more...)", len(subjects)-maxToDisplay))
}
return domainsToDisplay
}

View File

@ -210,7 +210,7 @@ func (na NetworkAddress) IsUnixNetwork() bool {
return IsUnixNetwork(na.Network) return IsUnixNetwork(na.Network)
} }
// IsFdNetwork returns true if na.Network is // IsUnixNetwork returns true if na.Network is
// fd or fdgram. // fd or fdgram.
func (na NetworkAddress) IsFdNetwork() bool { func (na NetworkAddress) IsFdNetwork() bool {
return IsFdNetwork(na.Network) return IsFdNetwork(na.Network)
@ -641,7 +641,7 @@ func RegisterNetwork(network string, getListener ListenerFunc) {
if network == "tcp" || network == "tcp4" || network == "tcp6" || if network == "tcp" || network == "tcp4" || network == "tcp6" ||
network == "udp" || network == "udp4" || network == "udp6" || network == "udp" || network == "udp4" || network == "udp6" ||
network == "unix" || network == "unixpacket" || network == "unixgram" || network == "unix" || network == "unixpacket" || network == "unixgram" ||
strings.HasPrefix(network, "ip:") || strings.HasPrefix(network, "ip4:") || strings.HasPrefix(network, "ip6:") || strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) ||
network == "fd" || network == "fdgram" { network == "fd" || network == "fdgram" {
panic("network type " + network + " is reserved") panic("network type " + network + " is reserved")
} }

View File

@ -30,7 +30,7 @@ func TestSplitNetworkAddress(t *testing.T) {
expectErr bool expectErr bool
}{ }{
{ {
input: "", input: "",
expectHost: "", expectHost: "",
}, },
{ {
@ -41,7 +41,7 @@ func TestSplitNetworkAddress(t *testing.T) {
input: ":", // empty host & empty port input: ":", // empty host & empty port
}, },
{ {
input: "::", input: "::",
expectHost: "::", expectHost: "::",
}, },
{ {
@ -184,8 +184,9 @@ func TestParseNetworkAddress(t *testing.T) {
expectErr bool expectErr bool
}{ }{
{ {
input: "", input: "",
expectAddr: NetworkAddress{}, expectAddr: NetworkAddress{
},
}, },
{ {
input: ":", input: ":",
@ -310,8 +311,9 @@ func TestParseNetworkAddressWithDefaults(t *testing.T) {
expectErr bool expectErr bool
}{ }{
{ {
input: "", input: "",
expectAddr: NetworkAddress{}, expectAddr: NetworkAddress{
},
}, },
{ {
input: ":", input: ":",

View File

@ -20,7 +20,6 @@ import (
"io" "io"
"log" "log"
"os" "os"
"slices"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -162,9 +161,7 @@ func (logging *Logging) setupNewDefault(ctx Context) error {
if err != nil { if err != nil {
return fmt.Errorf("setting up default log: %v", err) return fmt.Errorf("setting up default log: %v", err)
} }
newDefault.logger = zap.New(newDefault.CustomLog.core, options...)
filteringCore := &filteringCore{newDefault.CustomLog.core, newDefault.CustomLog}
newDefault.logger = zap.New(filteringCore, options...)
// redirect the default caddy logs // redirect the default caddy logs
defaultLoggerMu.Lock() defaultLoggerMu.Lock()
@ -493,8 +490,10 @@ func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
if len(cl.Include) > 0 && len(cl.Exclude) > 0 { if len(cl.Include) > 0 && len(cl.Exclude) > 0 {
// prevent intersections // prevent intersections
for _, allow := range cl.Include { for _, allow := range cl.Include {
if slices.Contains(cl.Exclude, allow) { for _, deny := range cl.Exclude {
return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow) if allow == deny {
return fmt.Errorf("include and exclude must not intersect, but found %s in both lists", allow)
}
} }
} }

View File

@ -1,106 +0,0 @@
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddy
import "testing"
func TestCustomLog_loggerAllowed(t *testing.T) {
type fields struct {
BaseLog BaseLog
Include []string
Exclude []string
}
type args struct {
name string
isModule bool
}
tests := []struct {
name string
fields fields
args args
want bool
}{
{
name: "include",
fields: fields{
Include: []string{"foo"},
},
args: args{
name: "foo",
isModule: true,
},
want: true,
},
{
name: "exclude",
fields: fields{
Exclude: []string{"foo"},
},
args: args{
name: "foo",
isModule: true,
},
want: false,
},
{
name: "include and exclude",
fields: fields{
Include: []string{"foo"},
Exclude: []string{"foo"},
},
args: args{
name: "foo",
isModule: true,
},
want: false,
},
{
name: "include and exclude (longer namespace)",
fields: fields{
Include: []string{"foo.bar"},
Exclude: []string{"foo"},
},
args: args{
name: "foo.bar",
isModule: true,
},
want: true,
},
{
name: "excluded module is not printed",
fields: fields{
Include: []string{"admin.api.load"},
Exclude: []string{"admin.api"},
},
args: args{
name: "admin.api",
isModule: false,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cl := &CustomLog{
BaseLog: tt.fields.BaseLog,
Include: tt.fields.Include,
Exclude: tt.fields.Exclude,
}
if got := cl.loggerAllowed(tt.args.name, tt.args.isModule); got != tt.want {
t.Errorf("CustomLog.loggerAllowed() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -18,8 +18,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"net/url"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
@ -362,14 +360,6 @@ func isModuleMapType(typ reflect.Type) bool {
isJSONRawMessage(typ.Elem()) isJSONRawMessage(typ.Elem())
} }
// ProxyFuncProducer is implemented by modules which produce a
// function that returns a URL to use as network proxy. Modules
// in the namespace `caddy.network_proxy` must implement this
// interface.
type ProxyFuncProducer interface {
ProxyFunc() func(*http.Request) (*url.URL, error)
}
var ( var (
modules = make(map[string]ModuleInfo) modules = make(map[string]ModuleInfo)
modulesMu sync.RWMutex modulesMu sync.RWMutex

View File

@ -20,7 +20,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/google/uuid"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@ -204,26 +206,27 @@ func (app *App) On(eventName string, handler Handler) error {
// //
// Note that the data map is not copied, for efficiency. After Emit() is called, the // Note that the data map is not copied, for efficiency. After Emit() is called, the
// data passed in should not be changed in other goroutines. // data passed in should not be changed in other goroutines.
func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) caddy.Event { func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) Event {
logger := app.logger.With(zap.String("name", eventName)) logger := app.logger.With(zap.String("name", eventName))
e, err := caddy.NewEvent(ctx, eventName, data) id, err := uuid.NewRandom()
if err != nil { if err != nil {
logger.Error("failed to create event", zap.Error(err)) logger.Error("failed generating new event ID", zap.Error(err))
} }
var originModule caddy.ModuleInfo eventName = strings.ToLower(eventName)
var originModuleID caddy.ModuleID
var originModuleName string e := Event{
if origin := e.Origin(); origin != nil { Data: data,
originModule = origin.CaddyModule() id: id,
originModuleID = originModule.ID ts: time.Now(),
originModuleName = originModule.String() name: eventName,
origin: ctx.Module(),
} }
logger = logger.With( logger = logger.With(
zap.String("id", e.ID().String()), zap.String("id", e.id.String()),
zap.String("origin", originModuleName)) zap.String("origin", e.origin.CaddyModule().String()))
// add event info to replacer, make sure it's in the context // add event info to replacer, make sure it's in the context
repl, ok := ctx.Context.Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl, ok := ctx.Context.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
@ -236,15 +239,15 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) c
case "event": case "event":
return e, true return e, true
case "event.id": case "event.id":
return e.ID(), true return e.id, true
case "event.name": case "event.name":
return e.Name(), true return e.name, true
case "event.time": case "event.time":
return e.Timestamp(), true return e.ts, true
case "event.time_unix": case "event.time_unix":
return e.Timestamp().UnixMilli(), true return e.ts.UnixMilli(), true
case "event.module": case "event.module":
return originModuleID, true return e.origin.CaddyModule().ID, true
case "event.data": case "event.data":
return e.Data, true return e.Data, true
} }
@ -266,7 +269,7 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) c
// invoke handlers bound to the event by name and also all events; this for loop // invoke handlers bound to the event by name and also all events; this for loop
// iterates twice at most: once for the event name, once for "" (all events) // iterates twice at most: once for the event name, once for "" (all events)
for { for {
moduleID := originModuleID moduleID := e.origin.CaddyModule().ID
// implement propagation up the module tree (i.e. start with "a.b.c" then "a.b" then "a" then "") // implement propagation up the module tree (i.e. start with "a.b.c" then "a.b" then "a" then "")
for { for {
@ -289,7 +292,7 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) c
zap.Any("handler", handler)) zap.Any("handler", handler))
if err := handler.Handle(ctx, e); err != nil { if err := handler.Handle(ctx, e); err != nil {
aborted := errors.Is(err, caddy.ErrEventAborted) aborted := errors.Is(err, ErrAborted)
logger.Error("handler error", logger.Error("handler error",
zap.Error(err), zap.Error(err),
@ -323,9 +326,76 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) c
return e return e
} }
// Event represents something that has happened or is happening.
// An Event value is not synchronized, so it should be copied if
// being used in goroutines.
//
// EXPERIMENTAL: As with the rest of this package, events are
// subject to change.
type Event struct {
// If non-nil, the event has been aborted, meaning
// propagation has stopped to other handlers and
// the code should stop what it was doing. Emitters
// may choose to use this as a signal to adjust their
// code path appropriately.
Aborted error
// The data associated with the event. Usually the
// original emitter will be the only one to set or
// change these values, but the field is exported
// so handlers can have full access if needed.
// However, this map is not synchronized, so
// handlers must not use this map directly in new
// goroutines; instead, copy the map to use it in a
// goroutine.
Data map[string]any
id uuid.UUID
ts time.Time
name string
origin caddy.Module
}
func (e Event) ID() uuid.UUID { return e.id }
func (e Event) Timestamp() time.Time { return e.ts }
func (e Event) Name() string { return e.name }
func (e Event) Origin() caddy.Module { return e.origin }
// CloudEvent exports event e as a structure that, when
// serialized as JSON, is compatible with the
// CloudEvents spec.
func (e Event) CloudEvent() CloudEvent {
dataJSON, _ := json.Marshal(e.Data)
return CloudEvent{
ID: e.id.String(),
Source: e.origin.CaddyModule().String(),
SpecVersion: "1.0",
Type: e.name,
Time: e.ts,
DataContentType: "application/json",
Data: dataJSON,
}
}
// CloudEvent is a JSON-serializable structure that
// is compatible with the CloudEvents specification.
// See https://cloudevents.io.
type CloudEvent struct {
ID string `json:"id"`
Source string `json:"source"`
SpecVersion string `json:"specversion"`
Type string `json:"type"`
Time time.Time `json:"time"`
DataContentType string `json:"datacontenttype,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
// ErrAborted cancels an event.
var ErrAborted = errors.New("event aborted")
// Handler is a type that can handle events. // Handler is a type that can handle events.
type Handler interface { type Handler interface {
Handle(context.Context, caddy.Event) error Handle(context.Context, Event) error
} }
// Interface guards // Interface guards

View File

@ -69,11 +69,11 @@ func (xs *Filesystems) Provision(ctx caddy.Context) error {
} }
// register that module // register that module
ctx.Logger().Debug("registering fs", zap.String("fs", f.Key)) ctx.Logger().Debug("registering fs", zap.String("fs", f.Key))
ctx.FileSystems().Register(f.Key, f.fileSystem) ctx.Filesystems().Register(f.Key, f.fileSystem)
// remember to unregister the module when we are done // remember to unregister the module when we are done
xs.defers = append(xs.defers, func() { xs.defers = append(xs.defers, func() {
ctx.Logger().Debug("unregistering fs", zap.String("fs", f.Key)) ctx.Logger().Debug("unregistering fs", zap.String("fs", f.Key))
ctx.FileSystems().Unregister(f.Key) ctx.Filesystems().Unregister(f.Key)
}) })
} }
return nil return nil

View File

@ -73,7 +73,7 @@ func init() {
// `{http.request.local.host}` | The host (IP) part of the local address the connection arrived on // `{http.request.local.host}` | The host (IP) part of the local address the connection arrived on
// `{http.request.local.port}` | The port part of the local address the connection arrived on // `{http.request.local.port}` | The port part of the local address the connection arrived on
// `{http.request.local}` | The local address the connection arrived on // `{http.request.local}` | The local address the connection arrived on
// `{http.request.remote.host}` | The host (IP) part of the remote client's address, if available (not known with HTTP/3 early data) // `{http.request.remote.host}` | The host (IP) part of the remote client's address
// `{http.request.remote.port}` | The port part of the remote client's address // `{http.request.remote.port}` | The port part of the remote client's address
// `{http.request.remote}` | The address of the remote client // `{http.request.remote}` | The address of the remote client
// `{http.request.scheme}` | The request scheme, typically `http` or `https` // `{http.request.scheme}` | The request scheme, typically `http` or `https`
@ -152,7 +152,7 @@ type App struct {
tlsApp *caddytls.TLS tlsApp *caddytls.TLS
// used temporarily between phases 1 and 2 of auto HTTPS // used temporarily between phases 1 and 2 of auto HTTPS
allCertDomains map[string]struct{} allCertDomains []string
} }
// CaddyModule returns the Caddy module information. // CaddyModule returns the Caddy module information.
@ -207,7 +207,7 @@ func (app *App) Provision(ctx caddy.Context) error {
if srv.Metrics != nil { if srv.Metrics != nil {
srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead") srv.logger.Warn("per-server 'metrics' is deprecated; use 'metrics' in the root 'http' app instead")
app.Metrics = cmp.Or(app.Metrics, &Metrics{ app.Metrics = cmp.Or[*Metrics](app.Metrics, &Metrics{
init: sync.Once{}, init: sync.Once{},
httpMetrics: &httpMetrics{}, httpMetrics: &httpMetrics{},
}) })

View File

@ -25,7 +25,6 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/internal"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
) )
@ -66,6 +65,12 @@ type AutoHTTPSConfig struct {
// enabled. To force automated certificate management // enabled. To force automated certificate management
// regardless of loaded certificates, set this to true. // regardless of loaded certificates, set this to true.
IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"` IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
// If true, automatic HTTPS will prefer wildcard names
// and ignore non-wildcard names if both are available.
// This allows for writing a config with top-level host
// matchers without having those names produce certificates.
PreferWildcard bool `json:"prefer_wildcard,omitempty"`
} }
// automaticHTTPSPhase1 provisions all route matchers, determines // automaticHTTPSPhase1 provisions all route matchers, determines
@ -158,13 +163,26 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
} }
} }
// build the list of domains that could be used with ECH (if enabled) if srv.AutoHTTPS.PreferWildcard {
// so the TLS app can know to publish ECH configs for them wildcards := make(map[string]struct{})
echDomains := make([]string, 0, len(serverDomainSet)) for d := range serverDomainSet {
for d := range serverDomainSet { if strings.HasPrefix(d, "*.") {
echDomains = append(echDomains, d) wildcards[d[2:]] = struct{}{}
}
}
for d := range serverDomainSet {
if strings.HasPrefix(d, "*.") {
continue
}
base := d
if idx := strings.Index(d, "."); idx != -1 {
base = d[idx+1:]
}
if _, ok := wildcards[base]; ok {
delete(serverDomainSet, d)
}
}
} }
app.tlsApp.RegisterServerNames(echDomains)
// nothing more to do here if there are no domains that qualify for // nothing more to do here if there are no domains that qualify for
// automatic HTTPS and there are no explicit TLS connection policies: // automatic HTTPS and there are no explicit TLS connection policies:
@ -187,6 +205,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
// for all the hostnames we found, filter them so we have // for all the hostnames we found, filter them so we have
// a deduplicated list of names for which to obtain certs // a deduplicated list of names for which to obtain certs
// (only if cert management not disabled for this server) // (only if cert management not disabled for this server)
var echDomains []string
if srv.AutoHTTPS.DisableCerts { if srv.AutoHTTPS.DisableCerts {
logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName)) logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
} else { } else {
@ -213,10 +232,14 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
} }
uniqueDomainsForCerts[d] = struct{}{} uniqueDomainsForCerts[d] = struct{}{}
echDomains = append(echDomains, d)
} }
} }
} }
// let the TLS server know we have some hostnames that could be protected behind ECH
app.tlsApp.RegisterServerNames(echDomains)
// tell the server to use TLS if it is not already doing so // tell the server to use TLS if it is not already doing so
if srv.TLSConnPolicies == nil { if srv.TLSConnPolicies == nil {
srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)} srv.TLSConnPolicies = caddytls.ConnectionPolicies{new(caddytls.ConnectionPolicy)}
@ -265,10 +288,19 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
} }
} }
// we now have a list of all the unique names for which we need certs // we now have a list of all the unique names for which we need certs;
// turn the set into a slice so that phase 2 can use it
app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
var internal, tailscale []string var internal, tailscale []string
uniqueDomainsLoop: uniqueDomainsLoop:
for d := range uniqueDomainsForCerts { for d := range uniqueDomainsForCerts {
if !isTailscaleDomain(d) {
// whether or not there is already an automation policy for this
// name, we should add it to the list to manage a cert for it,
// unless it's a Tailscale domain, because we don't manage those
app.allCertDomains = append(app.allCertDomains, d)
}
// some names we've found might already have automation policies // some names we've found might already have automation policies
// explicitly specified for them; we should exclude those from // explicitly specified for them; we should exclude those from
// our hidden/implicit policy, since applying a name to more than // our hidden/implicit policy, since applying a name to more than
@ -307,7 +339,6 @@ uniqueDomainsLoop:
} }
if isTailscaleDomain(d) { if isTailscaleDomain(d) {
tailscale = append(tailscale, d) tailscale = append(tailscale, d)
delete(uniqueDomainsForCerts, d) // not managed by us; handled separately
} else if shouldUseInternal(d) { } else if shouldUseInternal(d) {
internal = append(internal, d) internal = append(internal, d)
} }
@ -343,7 +374,7 @@ uniqueDomainsLoop:
// match on known domain names, unless it's our special case of a // match on known domain names, unless it's our special case of a
// catch-all which is an empty string (common among catch-all sites // catch-all which is an empty string (common among catch-all sites
// that enable on-demand TLS for yet-unknown domain names) // that enable on-demand TLS for yet-unknown domain names)
if len(domains) != 1 || domains[0] != "" { if !(len(domains) == 1 && domains[0] == "") {
matcherSet = append(matcherSet, MatchHost(domains)) matcherSet = append(matcherSet, MatchHost(domains))
} }
@ -437,9 +468,6 @@ redirServersLoop:
} }
} }
// persist the domains/IPs we're managing certs for through provisioning/startup
app.allCertDomains = uniqueDomainsForCerts
logger.Debug("adjusted config", logger.Debug("adjusted config",
zap.Reflect("tls", app.tlsApp), zap.Reflect("tls", app.tlsApp),
zap.Reflect("http", app)) zap.Reflect("http", app))
@ -742,7 +770,7 @@ func (app *App) automaticHTTPSPhase2() error {
return nil return nil
} }
app.logger.Info("enabling automatic TLS certificate management", app.logger.Info("enabling automatic TLS certificate management",
zap.Strings("domains", internal.MaxSizeSubjectsListForLog(app.allCertDomains, 1000)), zap.Strings("domains", app.allCertDomains),
) )
err := app.tlsApp.Manage(app.allCertDomains) err := app.tlsApp.Manage(app.allCertDomains)
if err != nil { if err != nil {

View File

@ -236,7 +236,10 @@ func (c *Cache) makeRoom() {
// the cache is on a long tail, we can save a lot of CPU // 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 // time by doing a whole bunch of deletions now and then
// we won't have to do them again for a while // we won't have to do them again for a while
numToDelete := max(len(c.cache)/10, 1) numToDelete := len(c.cache) / 10
if numToDelete < 1 {
numToDelete = 1
}
for deleted := 0; deleted <= numToDelete; deleted++ { for deleted := 0; deleted <= numToDelete; deleted++ {
// Go maps are "nondeterministic" not actually random, // Go maps are "nondeterministic" not actually random,
// so although we could just chop off the "front" of the // so although we could just chop off the "front" of the

View File

@ -37,10 +37,6 @@ func init() {
// `{http.auth.user.*}` placeholders may be set for any authentication // `{http.auth.user.*}` placeholders may be set for any authentication
// modules that provide user metadata. // modules that provide user metadata.
// //
// In case of an error, the placeholder `{http.auth.<provider>.error}`
// will be set to the error message returned by the authentication
// provider.
//
// Its API is still experimental and may be subject to change. // Its API is still experimental and may be subject to change.
type Authentication struct { type Authentication struct {
// A set of authentication providers. If none are specified, // A set of authentication providers. If none are specified,
@ -75,7 +71,6 @@ func (a *Authentication) Provision(ctx caddy.Context) error {
} }
func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
var user User var user User
var authed bool var authed bool
var err error var err error
@ -85,9 +80,6 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
if c := a.logger.Check(zapcore.ErrorLevel, "auth provider returned error"); c != nil { if c := a.logger.Check(zapcore.ErrorLevel, "auth provider returned error"); c != nil {
c.Write(zap.String("provider", provName), zap.Error(err)) c.Write(zap.String("provider", provName), zap.Error(err))
} }
// Set the error from the authentication provider in a placeholder,
// so it can be used in the handle_errors directive.
repl.Set("http.auth."+provName+".error", err.Error())
continue continue
} }
if authed { if authed {
@ -98,6 +90,7 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated")) return caddyhttp.Error(http.StatusUnauthorized, fmt.Errorf("not authenticated"))
} }
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
repl.Set("http.auth.user.id", user.ID) repl.Set("http.auth.user.id", user.ID)
for k, v := range user.Metadata { for k, v := range user.Metadata {
repl.Set("http.auth.user."+k, v) repl.Set("http.auth.user."+k, v)

View File

@ -26,7 +26,7 @@
<path d="M9 7l4 0"/> <path d="M9 7l4 0"/>
<path d="M9 11l4 0"/> <path d="M9 11l4 0"/>
</svg> </svg>
{{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg" ".avif"}} {{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg"}}
{{- if eq .Tpl.Layout "grid"}} {{- if eq .Tpl.Layout "grid"}}
<img loading="lazy" src="{{.Name | pathEscape}}"> <img loading="lazy" src="{{.Name | pathEscape}}">
{{- else}} {{- else}}
@ -802,7 +802,7 @@ footer {
<b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}} <b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}}
</span> </span>
<span class="meta-item"> <span class="meta-item">
<b>{{.HumanTotalFileSize}}</b> total <b>{{.HumanTotalFileSize}}</b> total
</span> </span>
{{- if ne 0 .Limit}} {{- if ne 0 .Limit}}
<span class="meta-item"> <span class="meta-item">
@ -828,96 +828,6 @@ footer {
</svg> </svg>
Grid Grid
</a> </a>
{{- if and (eq .Layout "grid") (eq .Sort "name") (ne .Order "asc")}}
<a href="?sort=name&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<text x="2" y="10" font-size="9" fill="currentColor">Z</text>
<text x="2" y="20" font-size="9" fill="currentColor">A</text>
<path d="M13 4v12"></path>
<path d="M12 16l1 2l1 -2"></path>
</svg>
</a>
{{- else if and (eq .Layout "grid") (eq .Sort "name") (ne .Order "desc")}}
<a href="?sort=name&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<text x="2" y="10" font-size="9" fill="currentColor">A</text>
<text x="2" y="20" font-size="9" fill="currentColor">Z</text>
<path d="M13 4v12"></path>
<path d="M12 16l1 2l1 -2"></path>
</svg>
</a>
{{- else if and (eq .Layout "grid")}}
<a href="?sort=name&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<text x="2" y="20" font-size="9" fill="currentColor">A</text>
<text x="2" y="10" font-size="9" fill="currentColor">Z</text>
<path d="M13 4v12"></path>
<path d="M12 16l1 2l1 -2"></path>
</svg>
</a>
{{- end}}
{{- if and (eq .Layout "grid") (eq .Sort "size") (ne .Order "asc")}}
<a href="?sort=size&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="4" width="4" height="3" rx="0.4" ry="0.4"></rect>
<rect x="2" y="10" width="8" height="3" rx="0.4" ry="0.4"></rect>
<rect x="2" y="16" width="12" height="3" rx="0.4" ry="0.4"></rect>
<path d="M18 4v12"></path>
<path d="M17 16l1 2l1 -2"></path>
</svg>
</a>
{{- else if and (eq .Layout "grid") (eq .Sort "size") (ne .Order "desc")}}
<a href="?sort=size&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="4" width="12" height="3" rx="0.4" ry="0.4"></rect>
<rect x="2" y="10" width="8" height="3" rx="0.4" ry="0.4"></rect>
<rect x="2" y="16" width="4" height="3" rx="0.4" ry="0.4"></rect>
<path d="M18 4v12"></path>
<path d="M17 16l1 2l1 -2"></path>
</svg>
</a>
{{- else if and (eq .Layout "grid")}}
<a href="?sort=size&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="4" width="4" height="3" rx="0.4" ry="0.4"></rect>
<rect x="2" y="10" width="8" height="3" rx="0.4" ry="0.4"></rect>
<rect x="2" y="16" width="12" height="3" rx="0.4" ry="0.4"></rect>
<path d="M18 4v12"></path>
<path d="M17 16l1 2l1 -2"></path>
</svg>
</a>
{{- end}}
{{- if and (eq .Layout "grid") (eq .Sort "time") (ne .Order "asc")}}
<a href="?sort=time&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="11" r="8"></circle>
<line x1="9" y1="12" x2="9" y2="7" stroke-linecap="round"></line>
<line x1="9" y1="12" x2="12" y2="12" stroke-linecap="round"></line>
<path d="M20 4v12"></path>
<path d="M19 16l1 2l1 -2"></path>
</svg>
</a>
{{- else if and (eq .Layout "grid") (eq .Sort "time") (ne .Order "desc")}}
<a href="?sort=time&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="11" r="8"></circle>
<line x1="9" y1="12" x2="9" y2="7" stroke-linecap="round"></line>
<line x1="9" y1="12" x2="12" y2="12" stroke-linecap="round"></line>
<path d="M20 4v12"></path>
<path d="M19 5l1 -2l1 2"></path>
</svg>
</a>
{{- else if and (eq .Layout "grid")}}
<a href="?sort=time&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}&layout=grid">
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="11" r="8"></circle>
<line x1="9" y1="12" x2="9" y2="7" stroke-linecap="round"></line>
<line x1="9" y1="12" x2="12" y2="12" stroke-linecap="round"></line>
<path d="M20 4v12"></path>
<path d="M19 16l1 2l1 -2"></path>
</svg>
</a>
{{- end}}
</div> </div>
<div class='listing{{if eq .Layout "grid"}} grid{{end}}'> <div class='listing{{if eq .Layout "grid"}} grid{{end}}'>
{{- if eq .Layout "grid"}} {{- if eq .Layout "grid"}}
@ -958,7 +868,7 @@ footer {
</svg> </svg>
</a> </a>
{{- end}} {{- end}}
{{- if and (eq .Sort "name") (ne .Order "desc")}} {{- if and (eq .Sort "name") (ne .Order "desc")}}
<a href="?sort=name&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}"> <a href="?sort=name&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">
Name Name

View File

@ -252,7 +252,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander {
} }
for _, arg := range args { for _, arg := range args {
if !isCELStringLiteral(arg) && !isCELCaddyPlaceholderCall(arg) { if !(isCELStringLiteral(arg) || isCELCaddyPlaceholderCall(arg)) {
return nil, &common.Error{ return nil, &common.Error{
Location: eh.OffsetLocation(arg.ID()), Location: eh.OffsetLocation(arg.ID()),
Message: "matcher only supports repeated string literal arguments", Message: "matcher only supports repeated string literal arguments",
@ -274,7 +274,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander {
func (m *MatchFile) Provision(ctx caddy.Context) error { func (m *MatchFile) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger() m.logger = ctx.Logger()
m.fsmap = ctx.FileSystems() m.fsmap = ctx.Filesystems()
if m.Root == "" { if m.Root == "" {
m.Root = "{http.vars.root}" m.Root = "{http.vars.root}"
@ -616,16 +616,15 @@ func isCELTryFilesLiteral(e ast.Expr) bool {
return false return false
} }
mapKeyStr := mapKey.AsLiteral().ConvertToType(types.StringType).Value() mapKeyStr := mapKey.AsLiteral().ConvertToType(types.StringType).Value()
switch mapKeyStr { if mapKeyStr == "try_files" || mapKeyStr == "split_path" {
case "try_files", "split_path":
if !isCELStringListLiteral(mapVal) { if !isCELStringListLiteral(mapVal) {
return false return false
} }
case "try_policy", "root": } else if mapKeyStr == "try_policy" || mapKeyStr == "root" {
if !(isCELStringExpr(mapVal)) { if !(isCELStringExpr(mapVal)) {
return false return false
} }
default: } else {
return false return false
} }
} }

View File

@ -117,7 +117,7 @@ func TestFileMatcher(t *testing.T) {
}, },
} { } {
m := &MatchFile{ m := &MatchFile{
fsmap: &filesystems.FileSystemMap{}, fsmap: &filesystems.FilesystemMap{},
Root: "./testdata", Root: "./testdata",
TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/"}, TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/"},
} }
@ -229,7 +229,7 @@ func TestPHPFileMatcher(t *testing.T) {
}, },
} { } {
m := &MatchFile{ m := &MatchFile{
fsmap: &filesystems.FileSystemMap{}, fsmap: &filesystems.FilesystemMap{},
Root: "./testdata", Root: "./testdata",
TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php"}, TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php"},
SplitPath: []string{".php"}, SplitPath: []string{".php"},
@ -273,7 +273,7 @@ func TestPHPFileMatcher(t *testing.T) {
func TestFirstSplit(t *testing.T) { func TestFirstSplit(t *testing.T) {
m := MatchFile{ m := MatchFile{
SplitPath: []string{".php"}, SplitPath: []string{".php"},
fsmap: &filesystems.FileSystemMap{}, fsmap: &filesystems.FilesystemMap{},
} }
actual, remainder := m.firstSplit("index.PHP/somewhere") actual, remainder := m.firstSplit("index.PHP/somewhere")
expected := "index.PHP" expected := "index.PHP"

View File

@ -186,7 +186,7 @@ func (FileServer) CaddyModule() caddy.ModuleInfo {
func (fsrv *FileServer) Provision(ctx caddy.Context) error { func (fsrv *FileServer) Provision(ctx caddy.Context) error {
fsrv.logger = ctx.Logger() fsrv.logger = ctx.Logger()
fsrv.fsmap = ctx.FileSystems() fsrv.fsmap = ctx.Filesystems()
if fsrv.FileSystem == "" { if fsrv.FileSystem == "" {
fsrv.FileSystem = "{http.vars.fs}" fsrv.FileSystem = "{http.vars.fs}"
@ -300,10 +300,8 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
info, err := fs.Stat(fileSystem, filename) info, err := fs.Stat(fileSystem, filename)
if err != nil { if err != nil {
err = fsrv.mapDirOpenError(fileSystem, err, filename) err = fsrv.mapDirOpenError(fileSystem, err, filename)
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid) {
return fsrv.notFound(w, r, next) return fsrv.notFound(w, r, next)
} else if errors.Is(err, fs.ErrInvalid) {
return caddyhttp.Error(http.StatusBadRequest, err)
} else if errors.Is(err, fs.ErrPermission) { } else if errors.Is(err, fs.ErrPermission) {
return caddyhttp.Error(http.StatusForbidden, err) return caddyhttp.Error(http.StatusForbidden, err)
} }
@ -613,11 +611,6 @@ func (fsrv *FileServer) mapDirOpenError(fileSystem fs.FS, originalErr error, nam
return originalErr return originalErr
} }
var pathErr *fs.PathError
if errors.As(originalErr, &pathErr) {
return fs.ErrInvalid
}
parts := strings.Split(name, separator) parts := strings.Split(name, separator)
for i := range parts { for i := range parts {
if parts[i] == "" { if parts[i] == "" {

View File

@ -78,7 +78,7 @@ func (h Handler) Validate() error {
return err return err
} }
} }
if h.Response != nil && h.Response.HeaderOps != nil { if h.Response != nil {
err := h.Response.validate() err := h.Response.validate()
if err != nil { if err != nil {
return err return err
@ -133,9 +133,6 @@ type HeaderOps struct {
// Provision sets up the header operations. // Provision sets up the header operations.
func (ops *HeaderOps) Provision(_ caddy.Context) error { func (ops *HeaderOps) Provision(_ caddy.Context) error {
if ops == nil {
return nil // it's possible no ops are configured; fix #6893
}
for fieldName, replacements := range ops.Replace { for fieldName, replacements := range ops.Replace {
for i, r := range replacements { for i, r := range replacements {
if r.SearchRegexp == "" { if r.SearchRegexp == "" {

View File

@ -118,11 +118,6 @@ func (irh interceptedResponseHandler) WriteHeader(statusCode int) {
irh.ResponseRecorder.WriteHeader(statusCode) irh.ResponseRecorder.WriteHeader(statusCode)
} }
// EXPERIMENTAL: Subject to change or removal.
func (irh interceptedResponseHandler) Unwrap() http.ResponseWriter {
return irh.ResponseRecorder
}
// EXPERIMENTAL: Subject to change or removal. // EXPERIMENTAL: Subject to change or removal.
func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
buf := bufPool.Get().(*bytes.Buffer) buf := bufPool.Get().(*bytes.Buffer)

View File

@ -552,6 +552,7 @@ func (MatchPath) matchPatternWithEscapeSequence(escapedPath, matchPath string) b
if iPattern >= len(matchPath) || iPath >= len(escapedPath) { if iPattern >= len(matchPath) || iPath >= len(escapedPath) {
break break
} }
// get the next character from the request path // get the next character from the request path
pathCh := string(escapedPath[iPath]) pathCh := string(escapedPath[iPath])
@ -1341,8 +1342,6 @@ func (m *MatchTLS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
case "early_data": case "early_data":
var false bool var false bool
m.HandshakeComplete = &false m.HandshakeComplete = &false
default:
return d.Errf("unrecognized option '%s'", d.Val())
} }
} }
if d.NextArg() { if d.NextArg() {

View File

@ -9,9 +9,8 @@ import (
"sync" "sync"
"testing" "testing"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/prometheus/client_golang/prometheus/testutil"
) )
func TestServerNameFromContext(t *testing.T) { func TestServerNameFromContext(t *testing.T) {

View File

@ -363,13 +363,13 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
} }
} }
switch key { switch {
case "http.shutting_down": case key == "http.shutting_down":
server := req.Context().Value(ServerCtxKey).(*Server) server := req.Context().Value(ServerCtxKey).(*Server)
server.shutdownAtMu.RLock() server.shutdownAtMu.RLock()
defer server.shutdownAtMu.RUnlock() defer server.shutdownAtMu.RUnlock()
return !server.shutdownAt.IsZero(), true return !server.shutdownAt.IsZero(), true
case "http.time_until_shutdown": case key == "http.time_until_shutdown":
server := req.Context().Value(ServerCtxKey).(*Server) server := req.Context().Value(ServerCtxKey).(*Server)
server.shutdownAtMu.RLock() server.shutdownAtMu.RLock()
defer server.shutdownAtMu.RUnlock() defer server.shutdownAtMu.RUnlock()

View File

@ -68,12 +68,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
} }
rb.WriteTimeout = timeout rb.WriteTimeout = timeout
case "set":
var setStr string
if !h.AllArgs(&setStr) {
return nil, h.ArgErr()
}
rb.Set = setStr
default: default:
return nil, h.Errf("unrecognized request_body subdirective '%s'", h.Val()) return nil, h.Errf("unrecognized request_body subdirective '%s'", h.Val())
} }

View File

@ -18,7 +18,6 @@ import (
"errors" "errors"
"io" "io"
"net/http" "net/http"
"strings"
"time" "time"
"go.uber.org/zap" "go.uber.org/zap"
@ -44,10 +43,6 @@ type RequestBody struct {
// EXPERIMENTAL. Subject to change/removal. // EXPERIMENTAL. Subject to change/removal.
WriteTimeout time.Duration `json:"write_timeout,omitempty"` WriteTimeout time.Duration `json:"write_timeout,omitempty"`
// This field permit to replace body on the fly
// EXPERIMENTAL. Subject to change/removal.
Set string `json:"set,omitempty"`
logger *zap.Logger logger *zap.Logger
} }
@ -65,18 +60,6 @@ func (rb *RequestBody) Provision(ctx caddy.Context) error {
} }
func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
if rb.Set != "" {
if r.Body != nil {
err := r.Body.Close()
if err != nil {
return err
}
}
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
replacedBody := repl.ReplaceAll(rb.Set, "")
r.Body = io.NopCloser(strings.NewReader(replacedBody))
r.ContentLength = int64(len(replacedBody))
}
if r.Body == nil { if r.Body == nil {
return next.ServeHTTP(w, r) return next.ServeHTTP(w, r)
} }

View File

@ -1,84 +0,0 @@
package reverseproxy
import (
"io"
"testing"
)
type zeroReader struct{}
func (zeroReader) Read(p []byte) (int, error) {
for i := range p {
p[i] = 0
}
return len(p), nil
}
func TestBuffering(t *testing.T) {
var (
h Handler
zr zeroReader
)
type args struct {
body io.ReadCloser
limit int64
}
tests := []struct {
name string
args args
resultCheck func(io.ReadCloser, int64, args) bool
}{
{
name: "0 limit, body is returned as is",
args: args{
body: io.NopCloser(&zr),
limit: 0,
},
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
return res == args.body && read == args.limit && read == 0
},
},
{
name: "negative limit, body is read completely",
args: args{
body: io.NopCloser(io.LimitReader(&zr, 100)),
limit: -1,
},
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
brc, ok := res.(bodyReadCloser)
return ok && brc.body == nil && brc.buf.Len() == 100 && read == 100
},
},
{
name: "positive limit, body is read partially",
args: args{
body: io.NopCloser(io.LimitReader(&zr, 100)),
limit: 50,
},
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
brc, ok := res.(bodyReadCloser)
return ok && brc.body != nil && brc.buf.Len() == 50 && read == 50
},
},
{
name: "positive limit, body is read completely",
args: args{
body: io.NopCloser(io.LimitReader(&zr, 100)),
limit: 101,
},
resultCheck: func(res io.ReadCloser, read int64, args args) bool {
brc, ok := res.(bodyReadCloser)
return ok && brc.body == nil && brc.buf.Len() == 100 && read == 100
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res, read := h.bufferedBody(tt.args.body, tt.args.limit)
if !tt.resultCheck(res, read, tt.args) {
t.Error("Handler.bufferedBody() test failed")
return
}
})
}
}

View File

@ -33,7 +33,6 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers" "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/caddyserver/caddy/v2/modules/internal/network"
) )
func init() { func init() {
@ -665,10 +664,9 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if d.NextArg() { if d.NextArg() {
return d.ArgErr() return d.ArgErr()
} }
switch subdir { if subdir == "request_buffers" {
case "request_buffers":
h.RequestBuffers = size h.RequestBuffers = size
case "response_buffers": } else if subdir == "response_buffers" {
h.ResponseBuffers = size h.ResponseBuffers = size
} }
@ -981,9 +979,7 @@ func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error
// read_buffer <size> // read_buffer <size>
// write_buffer <size> // write_buffer <size>
// max_response_header <size> // max_response_header <size>
// network_proxy <module> { // forward_proxy_url <url>
// ...
// }
// dial_timeout <duration> // dial_timeout <duration>
// dial_fallback_delay <duration> // dial_fallback_delay <duration>
// response_header_timeout <duration> // response_header_timeout <duration>
@ -994,9 +990,6 @@ func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error
// tls_insecure_skip_verify // tls_insecure_skip_verify
// tls_timeout <duration> // tls_timeout <duration>
// tls_trusted_ca_certs <cert_files...> // tls_trusted_ca_certs <cert_files...>
// tls_trust_pool <module> {
// ...
// }
// tls_server_name <sni> // tls_server_name <sni>
// tls_renegotiation <level> // tls_renegotiation <level>
// tls_except_ports <ports...> // tls_except_ports <ports...>
@ -1075,24 +1068,10 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
} }
case "forward_proxy_url": case "forward_proxy_url":
caddy.Log().Warn("The 'forward_proxy_url' field is deprecated. Use 'network_proxy <url>' instead.")
if !d.NextArg() { if !d.NextArg() {
return d.ArgErr() return d.ArgErr()
} }
u := network.ProxyFromURL{URL: d.Val()} h.ForwardProxyURL = d.Val()
h.NetworkProxyRaw = caddyconfig.JSONModuleObject(u, "from", "url", nil)
case "network_proxy":
if !d.NextArg() {
return d.ArgErr()
}
modStem := d.Val()
modID := "caddy.network_proxy." + modStem
unm, err := caddyfile.UnmarshalModule(d, modID)
if err != nil {
return err
}
h.NetworkProxyRaw = caddyconfig.JSONModuleObject(unm, "from", modStem, nil)
case "dial_timeout": case "dial_timeout":
if !d.NextArg() { if !d.NextArg() {

View File

@ -122,10 +122,9 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
} }
} }
if fromAddr.Port == "" { if fromAddr.Port == "" {
switch fromAddr.Scheme { if fromAddr.Scheme == "http" {
case "http":
fromAddr.Port = httpPort fromAddr.Port = httpPort
case "https": } else if fromAddr.Scheme == "https" {
fromAddr.Port = httpsPort fromAddr.Port = httpsPort
} }
} }

View File

@ -17,7 +17,6 @@ package fastcgi
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"slices"
"strconv" "strconv"
"strings" "strings"
@ -315,7 +314,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// if the index is turned off, we skip the redirect and try_files // if the index is turned off, we skip the redirect and try_files
if indexFile != "off" { if indexFile != "off" {
var dirRedir bool dirRedir := false
dirIndex := "{http.request.uri.path}/" + indexFile dirIndex := "{http.request.uri.path}/" + indexFile
tryPolicy := "first_exist_fallback" tryPolicy := "first_exist_fallback"
@ -329,7 +328,13 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
tryPolicy = "" tryPolicy = ""
} }
dirRedir = slices.Contains(tryFiles, dirIndex) for _, tf := range tryFiles {
if tf == dirIndex {
dirRedir = true
break
}
}
} }
if dirRedir { if dirRedir {

View File

@ -309,9 +309,7 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
} }
}() }()
repl := caddy.NewReplacer() networkAddr, err := caddy.NewReplacer().ReplaceOrErr(upstream.Dial, true, true)
networkAddr, err := repl.ReplaceOrErr(upstream.Dial, true, true)
if err != nil { if err != nil {
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "invalid use of placeholders in dial address for active health checks"); c != nil { if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "invalid use of placeholders in dial address for active health checks"); c != nil {
c.Write( c.Write(
@ -346,24 +344,14 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
return return
} }
hostAddr := addr.JoinHostPort(0) hostAddr := addr.JoinHostPort(0)
dialAddr := hostAddr
if addr.IsUnixNetwork() || addr.IsFdNetwork() { if addr.IsUnixNetwork() || addr.IsFdNetwork() {
// this will be used as the Host portion of a http.Request URL, and // this will be used as the Host portion of a http.Request URL, and
// paths to socket files would produce an error when creating URL, // paths to socket files would produce an error when creating URL,
// so use a fake Host value instead; unix sockets are usually local // so use a fake Host value instead; unix sockets are usually local
hostAddr = "localhost" hostAddr = "localhost"
} }
err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: dialAddr}, hostAddr, networkAddr, upstream)
// Fill in the dial info for the upstream
// If the upstream is set, use that instead
dialInfoUpstream := upstream
if h.HealthChecks.Active.Upstream != "" {
dialInfoUpstream = &Upstream{
Dial: h.HealthChecks.Active.Upstream,
}
}
dialInfo, _ := dialInfoUpstream.fillDialInfo(repl)
err = h.doActiveHealthCheck(dialInfo, hostAddr, networkAddr, upstream)
if err != nil { if err != nil {
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health check failed"); c != nil { if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health check failed"); c != nil {
c.Write( c.Write(
@ -484,7 +472,7 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networ
markHealthy := func() { markHealthy := func() {
// increment passes and then check if it has reached the threshold to be healthy // increment passes and then check if it has reached the threshold to be healthy
err := upstream.countHealthPass(1) err := upstream.Host.countHealthPass(1)
if err != nil { if err != nil {
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count active health pass"); c != nil { if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count active health pass"); c != nil {
c.Write( c.Write(

View File

@ -17,6 +17,7 @@ package reverseproxy
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"net/netip" "net/netip"
"strconv" "strconv"
"sync/atomic" "sync/atomic"
@ -99,7 +100,8 @@ func (u *Upstream) Full() bool {
// fillDialInfo returns a filled DialInfo for upstream u, using the request // fillDialInfo returns a filled DialInfo for upstream u, using the request
// context. Note that the returned value is not a pointer. // context. Note that the returned value is not a pointer.
func (u *Upstream) fillDialInfo(repl *caddy.Replacer) (DialInfo, error) { func (u *Upstream) fillDialInfo(r *http.Request) (DialInfo, error) {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
var addr caddy.NetworkAddress var addr caddy.NetworkAddress
// use provided dial address // use provided dial address

View File

@ -24,6 +24,7 @@ import (
weakrand "math/rand" weakrand "math/rand"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"reflect" "reflect"
"slices" "slices"
@ -37,10 +38,8 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/caddy/v2/modules/caddytls" "github.com/caddyserver/caddy/v2/modules/caddytls"
"github.com/caddyserver/caddy/v2/modules/internal/network"
) )
func init() { func init() {
@ -91,7 +90,6 @@ type HTTPTransport struct {
// forward_proxy_url -> upstream // forward_proxy_url -> upstream
// //
// Default: http.ProxyFromEnvironment // Default: http.ProxyFromEnvironment
// DEPRECATED: Use NetworkProxyRaw|`network_proxy` instead. Subject to removal.
ForwardProxyURL string `json:"forward_proxy_url,omitempty"` ForwardProxyURL string `json:"forward_proxy_url,omitempty"`
// How long to wait before timing out trying to connect to // How long to wait before timing out trying to connect to
@ -143,22 +141,6 @@ type HTTPTransport struct {
// The pre-configured underlying HTTP transport. // The pre-configured underlying HTTP transport.
Transport *http.Transport `json:"-"` Transport *http.Transport `json:"-"`
// The module that provides the network (forward) proxy
// URL that the HTTP transport will use to proxy
// requests to the upstream. See [http.Transport.Proxy](https://pkg.go.dev/net/http#Transport.Proxy)
// for information regarding supported protocols.
//
// Providing a value to this parameter results in requests
// flowing through the reverse_proxy in the following way:
//
// User Agent ->
// reverse_proxy ->
// [proxy provided by the module] -> upstream
//
// If nil, defaults to reading the `HTTP_PROXY`,
// `HTTPS_PROXY`, and `NO_PROXY` environment variables.
NetworkProxyRaw json.RawMessage `json:"network_proxy,omitempty" caddy:"namespace=caddy.network_proxy inline_key=from"`
h2cTransport *http2.Transport h2cTransport *http2.Transport
h3Transport *http3.Transport // TODO: EXPERIMENTAL (May 2024) h3Transport *http3.Transport // TODO: EXPERIMENTAL (May 2024)
} }
@ -346,22 +328,16 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
} }
// negotiate any HTTP/SOCKS proxy for the HTTP transport // negotiate any HTTP/SOCKS proxy for the HTTP transport
proxy := http.ProxyFromEnvironment var proxy func(*http.Request) (*url.URL, error)
if h.ForwardProxyURL != "" { if h.ForwardProxyURL != "" {
caddyCtx.Logger().Warn("forward_proxy_url is deprecated; use network_proxy instead") pUrl, err := url.Parse(h.ForwardProxyURL)
u := network.ProxyFromURL{URL: h.ForwardProxyURL}
h.NetworkProxyRaw = caddyconfig.JSONModuleObject(u, "from", "url", nil)
}
if len(h.NetworkProxyRaw) != 0 {
proxyMod, err := caddyCtx.LoadModule(h, "NetworkProxyRaw")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load network_proxy module: %v", err) return nil, fmt.Errorf("failed to parse transport proxy url: %v", err)
}
if m, ok := proxyMod.(caddy.ProxyFuncProducer); ok {
proxy = m.ProxyFunc()
} else {
return nil, fmt.Errorf("network_proxy module is not `(func(*http.Request) (*url.URL, error))``")
} }
caddyCtx.Logger().Info("setting transport proxy url", zap.String("url", h.ForwardProxyURL))
proxy = http.ProxyURL(pUrl)
} else {
proxy = http.ProxyFromEnvironment
} }
rt := &http.Transport{ rt := &http.Transport{
@ -382,36 +358,6 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
if err != nil { if err != nil {
return nil, fmt.Errorf("making TLS client config: %v", err) return nil, fmt.Errorf("making TLS client config: %v", err)
} }
// servername has a placeholder, so we need to replace it
if strings.Contains(h.TLS.ServerName, "{") {
rt.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
// reuses the dialer from above to establish a plaintext connection
conn, err := dialContext(ctx, network, addr)
if err != nil {
return nil, err
}
// but add our own handshake logic
repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
tlsConfig := rt.TLSClientConfig.Clone()
tlsConfig.ServerName = repl.ReplaceAll(tlsConfig.ServerName, "")
tlsConn := tls.Client(conn, tlsConfig)
// complete the handshake before returning the connection
if rt.TLSHandshakeTimeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, rt.TLSHandshakeTimeout)
defer cancel()
}
err = tlsConn.HandshakeContext(ctx)
if err != nil {
_ = tlsConn.Close()
return nil, err
}
return tlsConn, nil
}
}
} }
if h.KeepAlive != nil { if h.KeepAlive != nil {
@ -483,9 +429,45 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
return rt, nil return rt, nil
} }
// replaceTLSServername checks TLS servername to see if it needs replacing
// if it does need replacing, it creates a new cloned HTTPTransport object to avoid any races
// and does the replacing of the TLS servername on that and returns the new object
// if no replacement is necessary it returns the original
func (h *HTTPTransport) replaceTLSServername(repl *caddy.Replacer) *HTTPTransport {
// check whether we have TLS and need to replace the servername in the TLSClientConfig
if h.TLSEnabled() && strings.Contains(h.TLS.ServerName, "{") {
// make a new h, "copy" the parts we don't need to touch, add a new *tls.Config and replace servername
newtransport := &HTTPTransport{
Resolver: h.Resolver,
TLS: h.TLS,
KeepAlive: h.KeepAlive,
Compression: h.Compression,
MaxConnsPerHost: h.MaxConnsPerHost,
DialTimeout: h.DialTimeout,
FallbackDelay: h.FallbackDelay,
ResponseHeaderTimeout: h.ResponseHeaderTimeout,
ExpectContinueTimeout: h.ExpectContinueTimeout,
MaxResponseHeaderSize: h.MaxResponseHeaderSize,
WriteBufferSize: h.WriteBufferSize,
ReadBufferSize: h.ReadBufferSize,
Versions: h.Versions,
Transport: h.Transport.Clone(),
h2cTransport: h.h2cTransport,
}
newtransport.Transport.TLSClientConfig.ServerName = repl.ReplaceAll(newtransport.Transport.TLSClientConfig.ServerName, "")
return newtransport
}
return h
}
// RoundTrip implements http.RoundTripper. // RoundTrip implements http.RoundTripper.
func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) { func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
h.SetScheme(req) // Try to replace TLS servername if needed
repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
transport := h.replaceTLSServername(repl)
transport.SetScheme(req)
// use HTTP/3 if enabled (TODO: This is EXPERIMENTAL) // use HTTP/3 if enabled (TODO: This is EXPERIMENTAL)
if h.h3Transport != nil { if h.h3Transport != nil {
@ -501,7 +483,7 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return h.h2cTransport.RoundTrip(req) return h.h2cTransport.RoundTrip(req)
} }
return h.Transport.RoundTrip(req) return transport.Transport.RoundTrip(req)
} }
// SetScheme ensures that the outbound request req // SetScheme ensures that the outbound request req
@ -528,7 +510,13 @@ func (h *HTTPTransport) shouldUseTLS(req *http.Request) bool {
} }
port := req.URL.Port() port := req.URL.Port()
return !slices.Contains(h.TLS.ExceptPorts, port) for i := range h.TLS.ExceptPorts {
if h.TLS.ExceptPorts[i] == port {
return false
}
}
return true
} }
// TLSEnabled returns true if TLS is enabled. // TLSEnabled returns true if TLS is enabled.
@ -640,7 +628,7 @@ func (t *TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error)
return nil, fmt.Errorf("getting tls app: %v", err) return nil, fmt.Errorf("getting tls app: %v", err)
} }
tlsApp := tlsAppIface.(*caddytls.TLS) tlsApp := tlsAppIface.(*caddytls.TLS)
err = tlsApp.Manage(map[string]struct{}{t.ClientCertificateAutomate: {}}) err = tlsApp.Manage([]string{t.ClientCertificateAutomate})
if err != nil { if err != nil {
return nil, fmt.Errorf("managing client certificate: %v", err) return nil, fmt.Errorf("managing client certificate: %v", err)
} }

View File

@ -532,7 +532,7 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
// the dial address may vary per-request if placeholders are // the dial address may vary per-request if placeholders are
// used, so perform those replacements here; the resulting // used, so perform those replacements here; the resulting
// DialInfo struct should have valid network address syntax // DialInfo struct should have valid network address syntax
dialInfo, err := upstream.fillDialInfo(repl) dialInfo, err := upstream.fillDialInfo(r)
if err != nil { if err != nil {
return true, fmt.Errorf("making dial info: %v", err) return true, fmt.Errorf("making dial info: %v", err)
} }
@ -1150,7 +1150,7 @@ func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, retries int
// we have to assume the upstream received the request, and // we have to assume the upstream received the request, and
// retries need to be carefully decided, because some requests // retries need to be carefully decided, because some requests
// are not idempotent // are not idempotent
if !isDialError && (!isHandlerError || !errors.Is(herr, errNoUpstream)) { if !isDialError && !(isHandlerError && errors.Is(herr, errNoUpstream)) {
if lb.RetryMatch == nil && req.Method != "GET" { if lb.RetryMatch == nil && req.Method != "GET" {
// by default, don't retry requests if they aren't GET // by default, don't retry requests if they aren't GET
return false return false
@ -1234,10 +1234,6 @@ func (h Handler) provisionUpstream(upstream *Upstream) {
// then returns a reader for the buffer along with how many bytes were buffered. Always close // then returns a reader for the buffer along with how many bytes were buffered. Always close
// the return value when done with it, just like if it was the original body! If limit is 0 // the return value when done with it, just like if it was the original body! If limit is 0
// (which it shouldn't be), this function returns its input; i.e. is a no-op, for safety. // (which it shouldn't be), this function returns its input; i.e. is a no-op, for safety.
// Otherwise, it returns bodyReadCloser, the original body will be closed and body will be nil
// if it's explicitly configured to buffer all or EOF is reached when reading.
// TODO: the error during reading is discarded if the limit is negative, should the error be propagated
// to upstream/downstream?
func (h Handler) bufferedBody(originalBody io.ReadCloser, limit int64) (io.ReadCloser, int64) { func (h Handler) bufferedBody(originalBody io.ReadCloser, limit int64) (io.ReadCloser, int64) {
if limit == 0 { if limit == 0 {
return originalBody, 0 return originalBody, 0

View File

@ -219,7 +219,10 @@ func (r RandomChoiceSelection) Validate() error {
// Select returns an available host, if any. // Select returns an available host, if any.
func (r RandomChoiceSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream { func (r RandomChoiceSelection) Select(pool UpstreamPool, _ *http.Request, _ http.ResponseWriter) *Upstream {
k := min(r.Choose, len(pool)) k := r.Choose
if k > len(pool) {
k = len(pool)
}
choices := make([]*Upstream, k) choices := make([]*Upstream, k)
for i, upstream := range pool { for i, upstream := range pool {
if !upstream.Available() { if !upstream.Available() {
@ -805,7 +808,7 @@ func leastRequests(upstreams []*Upstream) *Upstream {
return nil return nil
} }
var best []*Upstream var best []*Upstream
bestReqs := -1 var bestReqs int = -1
for _, upstream := range upstreams { for _, upstream := range upstreams {
if upstream == nil { if upstream == nil {
continue continue

View File

@ -52,4 +52,5 @@ func TestResolveIpVersion(t *testing.T) {
t.Errorf("resolveIpVersion(): Expected %s got %s", test.expectedIpVersion, ipVersion) t.Errorf("resolveIpVersion(): Expected %s got %s", test.expectedIpVersion, ipVersion)
} }
} }
} }

View File

@ -377,7 +377,11 @@ func buildQueryString(qs string, repl *caddy.Replacer) string {
// performed in normalized/unescaped space. // performed in normalized/unescaped space.
func trimPathPrefix(escapedPath, prefix string) string { func trimPathPrefix(escapedPath, prefix string) string {
var iPath, iPrefix int var iPath, iPrefix int
for iPath < len(escapedPath) && iPrefix < len(prefix) { for {
if iPath >= len(escapedPath) || iPrefix >= len(prefix) {
break
}
prefixCh := prefix[iPrefix] prefixCh := prefix[iPrefix]
ch := string(escapedPath[iPath]) ch := string(escapedPath[iPath])

View File

@ -171,7 +171,6 @@ func BenchmarkServer_LogRequest_WithTrace(b *testing.B) {
s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false) s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false)
} }
} }
func TestServer_TrustedRealClientIP_NoTrustedHeaders(t *testing.T) { func TestServer_TrustedRealClientIP_NoTrustedHeaders(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil) req := httptest.NewRequest("GET", "/", nil)
req.RemoteAddr = "192.0.2.1:12345" req.RemoteAddr = "192.0.2.1:12345"

View File

@ -22,7 +22,6 @@ import (
"net/http" "net/http"
"net/textproto" "net/textproto"
"os" "os"
"slices"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
@ -324,7 +323,13 @@ func cmdRespond(fl caddycmd.Flags) (int, error) {
// figure out if status code was explicitly specified; this lets // figure out if status code was explicitly specified; this lets
// us set a non-zero value as the default but is a little hacky // us set a non-zero value as the default but is a little hacky
statusCodeFlagSpecified := slices.Contains(os.Args, "--status") var statusCodeFlagSpecified bool
for _, fl := range os.Args {
if fl == "--status" {
statusCodeFlagSpecified = true
break
}
}
// try to determine what kind of parameter the unnamed argument is // try to determine what kind of parameter the unnamed argument is
if arg != "" { if arg != "" {

View File

@ -15,6 +15,8 @@
package acmeserver package acmeserver
import ( import (
"time"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddypki" "github.com/caddyserver/caddy/v2/modules/caddypki"
@ -72,10 +74,14 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
if !h.NextArg() { if !h.NextArg() {
return nil, h.ArgErr() return nil, h.ArgErr()
} }
dur, err := caddy.ParseDuration(h.Val()) dur, err := caddy.ParseDuration(h.Val())
if err != nil { if err != nil {
return nil, err return nil, err
} }
if d := time.Duration(ca.IntermediateLifetime); d > 0 && dur > d {
return nil, h.Errf("certificate lifetime (%s) exceeds intermediate certificate lifetime (%s)", dur, d)
}
acmeServer.Lifetime = caddy.Duration(dur) acmeServer.Lifetime = caddy.Duration(dur)
case "resolvers": case "resolvers":
acmeServer.Resolvers = h.RemainingArgs() acmeServer.Resolvers = h.RemainingArgs()
@ -91,17 +97,19 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
acmeServer.Policy.AllowWildcardNames = true acmeServer.Policy.AllowWildcardNames = true
case "allow": case "allow":
r := &RuleSet{} r := &RuleSet{}
for nesting := h.Nesting(); h.NextBlock(nesting); { for h.Next() {
if h.CountRemainingArgs() == 0 { for h.NextBlock(h.Nesting() - 1) {
return nil, h.ArgErr() // TODO: if h.CountRemainingArgs() == 0 {
} return nil, h.ArgErr() // TODO:
switch h.Val() { }
case "domains": switch h.Val() {
r.Domains = append(r.Domains, h.RemainingArgs()...) case "domains":
case "ip_ranges": r.Domains = append(r.Domains, h.RemainingArgs()...)
r.IPRanges = append(r.IPRanges, h.RemainingArgs()...) case "ip_ranges":
default: r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
return nil, h.Errf("unrecognized 'allow' subdirective: %s", h.Val()) default:
return nil, h.Errf("unrecognized 'allow' subdirective: %s", h.Val())
}
} }
} }
if acmeServer.Policy == nil { if acmeServer.Policy == nil {
@ -110,17 +118,19 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
acmeServer.Policy.Allow = r acmeServer.Policy.Allow = r
case "deny": case "deny":
r := &RuleSet{} r := &RuleSet{}
for nesting := h.Nesting(); h.NextBlock(nesting); { for h.Next() {
if h.CountRemainingArgs() == 0 { for h.NextBlock(h.Nesting() - 1) {
return nil, h.ArgErr() // TODO: if h.CountRemainingArgs() == 0 {
} return nil, h.ArgErr() // TODO:
switch h.Val() { }
case "domains": switch h.Val() {
r.Domains = append(r.Domains, h.RemainingArgs()...) case "domains":
case "ip_ranges": r.Domains = append(r.Domains, h.RemainingArgs()...)
r.IPRanges = append(r.IPRanges, h.RemainingArgs()...) case "ip_ranges":
default: r.IPRanges = append(r.IPRanges, h.RemainingArgs()...)
return nil, h.Errf("unrecognized 'deny' subdirective: %s", h.Val()) default:
return nil, h.Errf("unrecognized 'deny' subdirective: %s", h.Val())
}
} }
} }
if acmeServer.Policy == nil { if acmeServer.Policy == nil {

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