mirror of
https://github.com/caddyserver/caddy.git
synced 2025-10-19 15:53:17 +00:00
Compare commits
197 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f5f25d845a | ||
![]() |
1ce2a13ad1 | ||
![]() |
d7185fd002 | ||
![]() |
7fb39ec1e5 | ||
![]() |
10ac7da037 | ||
![]() |
d115cd1042 | ||
![]() |
de6b78009b | ||
![]() |
2ec28bca43 | ||
![]() |
178294e9d7 | ||
![]() |
13a4ec7597 | ||
![]() |
2f1d270968 | ||
![]() |
3c003deec6 | ||
![]() |
afbdcec08b | ||
![]() |
65e0ddc221 | ||
![]() |
b2ab419922 | ||
![]() |
bc0e184130 | ||
![]() |
1e82f9652e | ||
![]() |
25be2f26fc | ||
![]() |
0c8798fce3 | ||
![]() |
f5c3094050 | ||
![]() |
39ace450de | ||
![]() |
0ba8786b35 | ||
![]() |
bcd4055e89 | ||
![]() |
b462615439 | ||
![]() |
012b4b3d40 | ||
![]() |
d9cc24f3df | ||
![]() |
38848f7f25 | ||
![]() |
5473eb95d8 | ||
![]() |
2d0f3f887b | ||
![]() |
39357d3e5c | ||
![]() |
3553cfb6ad | ||
![]() |
806fef85be | ||
![]() |
6d73d85c1f | ||
![]() |
e0a8f9541d | ||
![]() |
b866a9e099 | ||
![]() |
1db26128a6 | ||
![]() |
02c9f0ff90 | ||
![]() |
63ec1f4e1c | ||
![]() |
293de94f34 | ||
![]() |
d8d359eca2 | ||
![]() |
11a95cee6d | ||
![]() |
b7c022a61a | ||
![]() |
5e2953670e | ||
![]() |
551f793700 | ||
![]() |
4564261d83 | ||
![]() |
16fe83c7af | ||
![]() |
3723e89585 | ||
![]() |
14a63a26b9 | ||
![]() |
67debd0e11 | ||
![]() |
b9710c6af4 | ||
![]() |
493898d9bd | ||
![]() |
1c596e3c5a | ||
![]() |
f11c780fdc | ||
![]() |
fdf610850b | ||
![]() |
5125fbed41 | ||
![]() |
b15ed9b084 | ||
![]() |
05acc5131e | ||
![]() |
7590c9ca1b | ||
![]() |
b898873b90 | ||
![]() |
09b53a753c | ||
![]() |
4bfc3b95b5 | ||
![]() |
49dac61b07 | ||
![]() |
19ff47a63b | ||
![]() |
007f4066f6 | ||
![]() |
42c888ee1d | ||
![]() |
731e6c2482 | ||
![]() |
0badb071ef | ||
![]() |
e4447c4ba7 | ||
![]() |
5bc2afbbb6 | ||
![]() |
4fd2acb5c9 | ||
![]() |
3bd413546b | ||
![]() |
5b727bde29 | ||
![]() |
fe41ff3c5b | ||
![]() |
b7ae39e906 | ||
![]() |
ab3b2d64ba | ||
![]() |
6de2c9e135 | ||
![]() |
8ba7eefd07 | ||
![]() |
291987ac23 | ||
![]() |
790f3e0885 | ||
![]() |
bbf1dfcea2 | ||
![]() |
aff88d4b26 | ||
![]() |
1209b5c566 | ||
![]() |
a067fb1760 | ||
![]() |
77dd12cc78 | ||
![]() |
c712cfcd76 | ||
![]() |
33c88bd2bb | ||
![]() |
11c6daecd7 | ||
![]() |
3b4d966fba | ||
![]() |
070d454c0d | ||
![]() |
2f0fc62b34 | ||
![]() |
3d0b4fac5a | ||
![]() |
1a0f168b6e | ||
![]() |
7a33f481f1 | ||
![]() |
e633d013f6 | ||
![]() |
fe26751491 | ||
![]() |
4b01d77b81 | ||
![]() |
0f209f62eb | ||
![]() |
1481c0411a | ||
![]() |
092913a7a5 | ||
![]() |
7099892958 | ||
![]() |
45c9341deb | ||
![]() |
e039a5bb5c | ||
![]() |
5b2eb66418 | ||
![]() |
a76d005a94 | ||
![]() |
8524386737 | ||
![]() |
94147caf31 | ||
![]() |
716d72e475 | ||
![]() |
44d078b670 | ||
![]() |
051e73aefc | ||
![]() |
9f7148392a | ||
![]() |
320c57291d | ||
![]() |
aa3d20be3e | ||
![]() |
54d03ced48 | ||
![]() |
89ed5f44de | ||
![]() |
105eee671c | ||
![]() |
737936c06b | ||
![]() |
a6d488a15b | ||
![]() |
fb22a26b1a | ||
![]() |
1bfa111552 | ||
![]() |
35c8c2d92d | ||
![]() |
0b2802faa4 | ||
![]() |
5be77d07ab | ||
![]() |
137711ae3e | ||
![]() |
f297bc0a04 | ||
![]() |
6c38ae7381 | ||
![]() |
def9db1f16 | ||
![]() |
ce926b87ed | ||
![]() |
b06a9496d1 | ||
![]() |
9becf61a9f | ||
![]() |
5a6b2f8d1d | ||
![]() |
ea77a9ab67 | ||
![]() |
7672b7848f | ||
![]() |
86c620fb4e | ||
![]() |
782a3c7ac6 | ||
![]() |
173573035c | ||
![]() |
7b1f00c330 | ||
![]() |
8dc76676fb | ||
![]() |
e276994174 | ||
![]() |
b3e692ed09 | ||
![]() |
55c89ccf2a | ||
![]() |
1f8dab572c | ||
![]() |
2ac09fdb20 | ||
![]() |
dccf3d8982 | ||
![]() |
af2d33afbb | ||
![]() |
39262f8663 | ||
![]() |
49f9af9a4a | ||
![]() |
d57ab215a2 | ||
![]() |
f4432a306a | ||
![]() |
220cd1c2bc | ||
![]() |
1975408d89 | ||
![]() |
4ebcfed9c9 | ||
![]() |
d2a2311bfd | ||
![]() |
adbe7f87e6 | ||
![]() |
19876208c7 | ||
![]() |
a686f7c346 | ||
![]() |
84364ffcd0 | ||
![]() |
1641e76fd7 | ||
![]() |
bc3d497739 | ||
![]() |
a807fe0659 | ||
![]() |
3207769232 | ||
![]() |
481bc80d6e | ||
![]() |
3644ee31ca | ||
![]() |
d7764dfdbb | ||
![]() |
eacd7720e9 | ||
![]() |
02e348f911 | ||
![]() |
ca37c0b05f | ||
![]() |
8861eae223 | ||
![]() |
fd4de7e0ae | ||
![]() |
0d7c63920d | ||
![]() |
6a8d4f1d60 | ||
![]() |
d7621fdbe6 | ||
![]() |
172136a0a0 | ||
![]() |
22563a70eb | ||
![]() |
9b74a53e51 | ||
![]() |
932dac157a | ||
![]() |
96c5c554c1 | ||
![]() |
9283770f68 | ||
![]() |
9996d6a70b | ||
![]() |
cfc3af6749 | ||
![]() |
904a0fa368 | ||
![]() |
d7872c3bfa | ||
![]() |
066d770409 | ||
![]() |
1115158616 | ||
![]() |
7b8f3505e3 | ||
![]() |
30743c361a | ||
![]() |
8d748bee71 | ||
![]() |
99073eaa33 | ||
![]() |
e7da3b267b | ||
![]() |
9e0e5a4b4c | ||
![]() |
2c4295ee48 | ||
![]() |
1f35a8a402 | ||
![]() |
0e570e0cc7 | ||
![]() |
e48b75843b | ||
![]() |
1f927d6b07 | ||
![]() |
50778b5542 | ||
![]() |
1bd567d7ad | ||
![]() |
34cff4af7d |
212 changed files with 9497 additions and 2378 deletions
31
.github/ISSUE_TEMPLATE/ISSUE.yml
vendored
Normal file
31
.github/ISSUE_TEMPLATE/ISSUE.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
name: Issue
|
||||||
|
description: An actionable development item, like a bug report or feature request
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for opening an issue! This is for actionable development items like bug reports and feature requests.
|
||||||
|
If you have a question about using Caddy, please [post on our forums](https://caddy.community) instead.
|
||||||
|
- type: textarea
|
||||||
|
id: content
|
||||||
|
attributes:
|
||||||
|
label: Issue Details
|
||||||
|
placeholder: Describe the issue here. Be specific by providing complete logs and minimal instructions to reproduce, or a thoughtful proposal, etc.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: assistance-disclosure
|
||||||
|
attributes:
|
||||||
|
label: Assistance Disclosure
|
||||||
|
description: "Our project allows assistance by AI/LLM tools as long as it is disclosed and described so we can better respond. Please certify whether you have used any such tooling related to this issue:"
|
||||||
|
options:
|
||||||
|
-
|
||||||
|
- AI used
|
||||||
|
- AI not used
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: assistance-description
|
||||||
|
attributes:
|
||||||
|
label: If AI was used, describe the extent to which it was used.
|
||||||
|
description: 'Examples: "ChatGPT translated from my native language" or "Claude proposed this change/feature"'
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Caddy forum
|
||||||
|
url: https://caddy.community
|
||||||
|
about: If you have questions (or answers!) about using Caddy, please use our forum
|
4
.github/SECURITY.md
vendored
4
.github/SECURITY.md
vendored
|
@ -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 email Matt Holt (the author) directly: matt at dyanim dot com.
|
When you are ready, please submit a [new private vulnerability report](https://github.com/caddyserver/caddy/security/advisories/new).
|
||||||
|
|
||||||
Please don't encrypt the email body. It only makes the process more complicated.
|
Please don't encrypt the message. It only makes the process more complicated.
|
||||||
|
|
||||||
Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you.
|
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.
|
||||||
|
|
||||||
|
|
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
|
@ -3,5 +3,20 @@ version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
open-pull-requests-limit: 1
|
||||||
|
groups:
|
||||||
|
actions-deps:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
open-pull-requests-limit: 1
|
||||||
|
groups:
|
||||||
|
all-updates:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "monthly"
|
||||||
|
|
29
.github/pull_request_template.md
vendored
Normal file
29
.github/pull_request_template.md
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Assistance Disclosure
|
||||||
|
<!--
|
||||||
|
Thank you for contributing! Please note:
|
||||||
|
|
||||||
|
The use of AI/LLM tools is allowed so long as it is disclosed, so
|
||||||
|
that we can provide better code review and maintain project quality.
|
||||||
|
|
||||||
|
If you used AI/LLM tooling in any way related to this PR, please
|
||||||
|
let us know to what extent it was utilized.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
"No AI was used."
|
||||||
|
"I wrote the code, but Claude generated the tests."
|
||||||
|
"I consulted ChatGPT for a solution, but I authored/coded it myself."
|
||||||
|
"Cody generated the code, and I verified it is correct."
|
||||||
|
"Copilot provided tab completion for code and comments."
|
||||||
|
|
||||||
|
We expect that you have vetted your contributions for correctness.
|
||||||
|
Additionally, signing our CLA certifies that you have the rights to
|
||||||
|
contribute this change.
|
||||||
|
|
||||||
|
Replace the text below with your disclosure:
|
||||||
|
-->
|
||||||
|
|
||||||
|
_This PR is missing an assistance disclosure._
|
30
.github/workflows/ai.yml
vendored
Normal file
30
.github/workflows/ai.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
name: AI Moderator
|
||||||
|
permissions: read-all
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_review_comment:
|
||||||
|
types: [created]
|
||||||
|
jobs:
|
||||||
|
spam-detection:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
models: read
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||||
|
- uses: github/ai-moderator@6bcdb2a79c2e564db8d76d7d4439d91a044c4eb6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
spam-label: 'spam'
|
||||||
|
ai-label: 'ai-generated'
|
||||||
|
minimize-detected-comments: true
|
||||||
|
# Built-in prompt configuration (all enabled by default)
|
||||||
|
enable-spam-detection: true
|
||||||
|
enable-link-spam-detection: true
|
||||||
|
enable-ai-detection: true
|
||||||
|
# custom-prompt-path: '.github/prompts/my-custom.prompt.yml' # Optional
|
71
.github/workflows/ci.yml
vendored
71
.github/workflows/ci.yml
vendored
|
@ -12,6 +12,14 @@ on:
|
||||||
- master
|
- master
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
|
env:
|
||||||
|
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -23,17 +31,13 @@ jobs:
|
||||||
- mac
|
- mac
|
||||||
- windows
|
- windows
|
||||||
go:
|
go:
|
||||||
- '1.22'
|
- '1.25'
|
||||||
- '1.23'
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.22'
|
- go: '1.25'
|
||||||
GO_SEMVER: '~1.22.3'
|
GO_SEMVER: '~1.25.0'
|
||||||
|
|
||||||
- go: '1.23'
|
|
||||||
GO_SEMVER: '~1.23.0'
|
|
||||||
|
|
||||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||||
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
|
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
|
||||||
|
@ -55,13 +59,21 @@ 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:
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.GO_SEMVER }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
@ -99,7 +111,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
run: |
|
run: |
|
||||||
go build -tags nobadger -trimpath -ldflags="-w -s" -v
|
go build -trimpath -ldflags="-w -s" -v
|
||||||
|
|
||||||
- name: Smoke test Caddy
|
- name: Smoke test Caddy
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
|
@ -108,7 +120,7 @@ jobs:
|
||||||
./caddy stop
|
./caddy stop
|
||||||
|
|
||||||
- name: Publish Build Artifact
|
- name: Publish Build Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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 }}
|
||||||
|
@ -122,7 +134,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 -v -coverprofile="cover-profile.out" -short -race ./...
|
go test -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
|
||||||
|
@ -142,12 +154,21 @@ 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@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
allowed-endpoints: ci-s390x.caddyserver.com:22
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
set +e
|
set +e
|
||||||
|
@ -170,7 +191,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 -v ./...
|
CGO_ENABLED=0 go test -p 1 -v ./...
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
if ((exit_code == 0)); then
|
if ((exit_code == 0)); then
|
||||||
break
|
break
|
||||||
|
@ -194,25 +215,33 @@ 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: Checkout code
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: actions/checkout@v4
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: goreleaser/goreleaser-action@v6
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
|
- uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: check
|
args: check
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||||
with:
|
with:
|
||||||
go-version: "~1.23"
|
go-version: "~1.25"
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Install xcaddy
|
- name: Install xcaddy
|
||||||
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@v6
|
- uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: build --single-target --snapshot
|
args: build --single-target --snapshot
|
||||||
|
|
35
.github/workflows/cross-build.yml
vendored
35
.github/workflows/cross-build.yml
vendored
|
@ -10,6 +10,15 @@ on:
|
||||||
- master
|
- master
|
||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
|
env:
|
||||||
|
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
|
||||||
|
CGO_ENABLED: '0'
|
||||||
|
# https://github.com/actions/setup-go/issues/491
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -27,26 +36,30 @@ jobs:
|
||||||
- 'darwin'
|
- 'darwin'
|
||||||
- 'netbsd'
|
- 'netbsd'
|
||||||
go:
|
go:
|
||||||
- '1.22'
|
- '1.25'
|
||||||
- '1.23'
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.22'
|
- go: '1.25'
|
||||||
GO_SEMVER: '~1.22.3'
|
GO_SEMVER: '~1.25.0'
|
||||||
|
|
||||||
- go: '1.23'
|
|
||||||
GO_SEMVER: '~1.23.0'
|
|
||||||
|
|
||||||
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@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.GO_SEMVER }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
@ -63,11 +76,9 @@ jobs:
|
||||||
|
|
||||||
- name: Run Build
|
- name: Run Build
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goos == 'aix' && 'ppc64' || 'amd64' }}
|
GOARCH: ${{ matrix.goos == 'aix' && 'ppc64' || 'amd64' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
run: |
|
run: go build -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
||||||
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
|
||||||
|
|
52
.github/workflows/lint.yml
vendored
52
.github/workflows/lint.yml
vendored
|
@ -13,6 +13,10 @@ 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:
|
||||||
|
@ -40,14 +44,19 @@ jobs:
|
||||||
runs-on: ${{ matrix.OS_LABEL }}
|
runs-on: ${{ matrix.OS_LABEL }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
- uses: actions/setup-go@v5
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
with:
|
with:
|
||||||
go-version: '~1.23'
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||||
|
with:
|
||||||
|
go-version: '~1.25'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|
||||||
|
@ -58,10 +67,39 @@ 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: govulncheck
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
uses: golang/govulncheck-action@v1
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
with:
|
with:
|
||||||
go-version-input: '~1.23.0'
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: govulncheck
|
||||||
|
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
|
||||||
|
with:
|
||||||
|
go-version-input: '~1.25.0'
|
||||||
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@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: 'Checkout Repository'
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
- name: 'Dependency Review'
|
||||||
|
uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4.8.0
|
||||||
|
with:
|
||||||
|
comment-summary-in-pr: on-failure
|
||||||
|
# https://github.com/actions/dependency-review-action/issues/430#issuecomment-1468975566
|
||||||
|
base-ref: ${{ github.event.pull_request.base.sha || 'master' }}
|
||||||
|
head-ref: ${{ github.event.pull_request.head.sha || github.ref }}
|
||||||
|
|
30
.github/workflows/release.yml
vendored
30
.github/workflows/release.yml
vendored
|
@ -5,6 +5,13 @@ 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
|
||||||
|
@ -13,13 +20,13 @@ jobs:
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
go:
|
go:
|
||||||
- '1.23'
|
- '1.25'
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.23'
|
- go: '1.25'
|
||||||
GO_SEMVER: '~1.23.0'
|
GO_SEMVER: '~1.25.0'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
||||||
|
@ -31,19 +38,24 @@ jobs:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||||
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@v4 runs this line:
|
# tl;dr: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 runs this line:
|
||||||
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
# 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
|
||||||
|
@ -97,11 +109,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@main
|
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # 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@main
|
uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # main
|
||||||
- name: Syft version
|
- name: Syft version
|
||||||
run: syft version
|
run: syft version
|
||||||
- name: Install xcaddy
|
- name: Install xcaddy
|
||||||
|
@ -110,7 +122,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@v6
|
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean --timeout 60m
|
args: release --clean --timeout 60m
|
||||||
|
|
17
.github/workflows/release_published.yml
vendored
17
.github/workflows/release_published.yml
vendored
|
@ -5,6 +5,9 @@ on:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release Published
|
name: Release Published
|
||||||
|
@ -13,12 +16,20 @@ 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@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Trigger event on caddyserver/dist
|
- name: Trigger event on caddyserver/dist
|
||||||
uses: peter-evans/repository-dispatch@v3
|
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||||
repository: caddyserver/dist
|
repository: caddyserver/dist
|
||||||
|
@ -26,7 +37,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@v3
|
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||||
repository: caddyserver/caddy-docker
|
repository: caddyserver/caddy-docker
|
||||||
|
|
86
.github/workflows/scorecard.yml
vendored
Normal file
86
.github/workflows/scorecard.yml
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||||
|
# by a third-party and are governed by separate terms of service, privacy
|
||||||
|
# policy, and support documentation.
|
||||||
|
|
||||||
|
name: OpenSSF Scorecard supply-chain security
|
||||||
|
on:
|
||||||
|
# For Branch-Protection check. Only the default branch is supported. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||||
|
branch_protection_rule:
|
||||||
|
# To guarantee Maintained check is occasionally updated. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||||
|
schedule:
|
||||||
|
- cron: '20 2 * * 5'
|
||||||
|
push:
|
||||||
|
branches: [ "master", "2.*" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master", "2.*" ]
|
||||||
|
|
||||||
|
|
||||||
|
# Declare default permissions as read only.
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analysis:
|
||||||
|
name: Scorecard analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
|
||||||
|
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
|
||||||
|
permissions:
|
||||||
|
# Needed to upload the results to code-scanning dashboard.
|
||||||
|
security-events: write
|
||||||
|
# Needed to publish results and get a badge (see publish_results below).
|
||||||
|
id-token: write
|
||||||
|
# Uncomment the permissions below if installing in a private repository.
|
||||||
|
# contents: read
|
||||||
|
# actions: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden the runner (Audit all outbound calls)
|
||||||
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: "Run analysis"
|
||||||
|
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||||
|
with:
|
||||||
|
results_file: results.sarif
|
||||||
|
results_format: sarif
|
||||||
|
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||||
|
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||||
|
# - you are installing Scorecard on a *private* repository
|
||||||
|
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
|
||||||
|
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||||
|
|
||||||
|
# Public repositories:
|
||||||
|
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||||
|
# - Allows the repository to include the Scorecard badge.
|
||||||
|
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||||
|
# For private repositories:
|
||||||
|
# - `publish_results` will always be set to `false`, regardless
|
||||||
|
# of the value entered here.
|
||||||
|
publish_results: true
|
||||||
|
|
||||||
|
# (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore
|
||||||
|
# file_mode: git
|
||||||
|
|
||||||
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
|
# format to the repository Actions tab.
|
||||||
|
- name: "Upload artifact"
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
with:
|
||||||
|
name: SARIF file
|
||||||
|
path: results.sarif
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||||
|
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||||
|
- name: "Upload to code-scanning"
|
||||||
|
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5
|
||||||
|
with:
|
||||||
|
sarif_file: results.sarif
|
222
.golangci.yml
222
.golangci.yml
|
@ -1,27 +1,19 @@
|
||||||
linters-settings:
|
version: "2"
|
||||||
errcheck:
|
run:
|
||||||
exclude-functions:
|
issues-exit-code: 1
|
||||||
- fmt.*
|
tests: false
|
||||||
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
|
build-tags:
|
||||||
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
|
- nobadger
|
||||||
gci:
|
- nomysql
|
||||||
sections:
|
- nopgx
|
||||||
- standard # Standard section: captures all standard packages.
|
output:
|
||||||
- default # Default section: contains all imports that could not be matched to another section type.
|
formats:
|
||||||
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
|
text:
|
||||||
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
|
path: stdout
|
||||||
# Skip generated files.
|
print-linter-name: true
|
||||||
# Default: true
|
print-issued-lines: 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:
|
||||||
disable-all: true
|
default: none
|
||||||
enable:
|
enable:
|
||||||
- asasalint
|
- asasalint
|
||||||
- asciicheck
|
- asciicheck
|
||||||
|
@ -35,148 +27,96 @@ linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
- errname
|
- errname
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- gci
|
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
- gofumpt
|
|
||||||
- gosec
|
- gosec
|
||||||
- gosimple
|
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
|
||||||
- importas
|
- importas
|
||||||
|
- ineffassign
|
||||||
- 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
|
||||||
# these are implicitly disabled:
|
settings:
|
||||||
# - containedctx
|
staticcheck:
|
||||||
# - contextcheck
|
checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1006", "-QF1008"] # default, and exclude 1 more undesired check
|
||||||
# - cyclop
|
errcheck:
|
||||||
# - depguard
|
exclude-functions:
|
||||||
# - errchkjson
|
- fmt.*
|
||||||
# - errorlint
|
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
|
||||||
# - exhaustruct
|
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
|
||||||
# - execinquery
|
exhaustive:
|
||||||
# - exhaustruct
|
ignore-enum-types: reflect.Kind|svc.Cmd
|
||||||
# - forbidigo
|
exclusions:
|
||||||
# - forcetypeassert
|
generated: lax
|
||||||
# - funlen
|
presets:
|
||||||
# - ginkgolinter
|
- comments
|
||||||
# - gocheckcompilerdirectives
|
- common-false-positives
|
||||||
# - gochecknoglobals
|
- legacy
|
||||||
# - gochecknoinits
|
- std-error-handling
|
||||||
# - gochecksumtype
|
rules:
|
||||||
# - gocognit
|
- linters:
|
||||||
# - goconst
|
|
||||||
# - gocritic
|
|
||||||
# - gocyclo
|
|
||||||
# - godot
|
|
||||||
# - godox
|
|
||||||
# - goerr113
|
|
||||||
# - goheader
|
|
||||||
# - gomnd
|
|
||||||
# - gomoddirectives
|
|
||||||
# - gomodguard
|
|
||||||
# - goprintffuncname
|
|
||||||
# - gosmopolitan
|
|
||||||
# - grouper
|
|
||||||
# - inamedparam
|
|
||||||
# - interfacebloat
|
|
||||||
# - ireturn
|
|
||||||
# - lll
|
|
||||||
# - loggercheck
|
|
||||||
# - maintidx
|
|
||||||
# - makezero
|
|
||||||
# - mirror
|
|
||||||
# - musttag
|
|
||||||
# - nakedret
|
|
||||||
# - nestif
|
|
||||||
# - nilerr
|
|
||||||
# - nilnil
|
|
||||||
# - nlreturn
|
|
||||||
# - noctx
|
|
||||||
# - nolintlint
|
|
||||||
# - nonamedreturns
|
|
||||||
# - nosprintfhostport
|
|
||||||
# - paralleltest
|
|
||||||
# - perfsprint
|
|
||||||
# - predeclared
|
|
||||||
# - protogetter
|
|
||||||
# - reassign
|
|
||||||
# - revive
|
|
||||||
# - rowserrcheck
|
|
||||||
# - stylecheck
|
|
||||||
# - tagalign
|
|
||||||
# - tagliatelle
|
|
||||||
# - testpackage
|
|
||||||
# - thelper
|
|
||||||
# - unparam
|
|
||||||
# - usestdlibvars
|
|
||||||
# - varnamelen
|
|
||||||
# - wrapcheck
|
|
||||||
# - wsl
|
|
||||||
|
|
||||||
run:
|
|
||||||
# default concurrency is a available CPU number.
|
|
||||||
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
|
|
||||||
timeout: 5m
|
|
||||||
issues-exit-code: 1
|
|
||||||
tests: false
|
|
||||||
|
|
||||||
# output configuration options
|
|
||||||
output:
|
|
||||||
formats:
|
|
||||||
- format: 'colored-line-number'
|
|
||||||
print-issued-lines: true
|
|
||||||
print-linter-name: true
|
|
||||||
|
|
||||||
issues:
|
|
||||||
exclude-rules:
|
|
||||||
- text: 'G115' # TODO: Either we should fix the issues or nuke the linter if it's bad
|
|
||||||
linters:
|
|
||||||
- gosec
|
- gosec
|
||||||
# we aren't calling unknown URL
|
text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad
|
||||||
- text: 'G107' # G107: Url provided to HTTP request as taint input
|
- linters:
|
||||||
linters:
|
|
||||||
- gosec
|
- gosec
|
||||||
# as a web server that's expected to handle any template, this is totally in the hands of the user.
|
text: G107 # we aren't calling unknown URL
|
||||||
- text: 'G203' # G203: Use of unescaped data in HTML templates
|
- linters:
|
||||||
linters:
|
|
||||||
- gosec
|
- gosec
|
||||||
# we're shelling out to known commands, not relying on user-defined input.
|
text: G203 # as a web server that's expected to handle any template, this is totally in the hands of the user.
|
||||||
- text: 'G204' # G204: Audit use of command execution
|
- linters:
|
||||||
linters:
|
- gosec
|
||||||
|
text: G204 # we're shelling out to known commands, not relying on user-defined input.
|
||||||
|
- linters:
|
||||||
- gosec
|
- gosec
|
||||||
# the choice of weakrand is deliberate, hence the named import "weakrand"
|
# the choice of weakrand is deliberate, hence the named import "weakrand"
|
||||||
- path: modules/caddyhttp/reverseproxy/selectionpolicies.go
|
path: modules/caddyhttp/reverseproxy/selectionpolicies.go
|
||||||
text: 'G404' # G404: Insecure random number source (rand)
|
text: G404
|
||||||
linters:
|
- linters:
|
||||||
- gosec
|
- gosec
|
||||||
- path: modules/caddyhttp/reverseproxy/streaming.go
|
path: modules/caddyhttp/reverseproxy/streaming.go
|
||||||
text: 'G404' # G404: Insecure random number source (rand)
|
text: G404
|
||||||
linters:
|
- linters:
|
||||||
- gosec
|
|
||||||
- path: modules/logging/filters.go
|
|
||||||
linters:
|
|
||||||
- dupl
|
- dupl
|
||||||
- path: modules/caddyhttp/matchers.go
|
path: modules/logging/filters.go
|
||||||
linters:
|
- linters:
|
||||||
- dupl
|
- dupl
|
||||||
- path: modules/caddyhttp/vars.go
|
path: modules/caddyhttp/matchers.go
|
||||||
linters:
|
- linters:
|
||||||
- dupl
|
- dupl
|
||||||
- path: _test\.go
|
path: modules/caddyhttp/vars.go
|
||||||
linters:
|
- linters:
|
||||||
- errcheck
|
- errcheck
|
||||||
|
path: _test\.go
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
formatters:
|
||||||
|
enable:
|
||||||
|
- gci
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard # Standard section: captures all standard packages.
|
||||||
|
- default # Default section: contains all imports that could not be matched to another section type.
|
||||||
|
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
|
||||||
|
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
|
||||||
|
custom-order: true
|
||||||
|
exclusions:
|
||||||
|
generated: lax
|
||||||
|
paths:
|
||||||
|
- third_party$
|
||||||
|
- builtin$
|
||||||
|
- examples$
|
||||||
|
|
|
@ -111,7 +111,7 @@ archives:
|
||||||
- id: default
|
- id: default
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
formats: zip
|
||||||
name_template: >-
|
name_template: >-
|
||||||
{{ .ProjectName }}_
|
{{ .ProjectName }}_
|
||||||
{{- .Version }}_
|
{{- .Version }}_
|
||||||
|
|
20
.pre-commit-config.yaml
Normal file
20
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
|
rev: v8.16.3
|
||||||
|
hooks:
|
||||||
|
- id: gitleaks
|
||||||
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
|
rev: v1.52.2
|
||||||
|
hooks:
|
||||||
|
- id: golangci-lint-config-verify
|
||||||
|
- id: golangci-lint
|
||||||
|
- id: golangci-lint-fmt
|
||||||
|
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||||
|
rev: 3.0.0
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.4.0
|
||||||
|
hooks:
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: trailing-whitespace
|
12
README.md
12
README.md
|
@ -14,9 +14,10 @@
|
||||||
<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://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" alt="@caddyserver on Twitter"></a>
|
<a href="https://x.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/twitter/follow/caddyserver" alt="@caddyserver on Twitter"></a>
|
||||||
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
|
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
|
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
- Fully-managed local CA for internal names & IPs
|
- Fully-managed local CA for internal names & IPs
|
||||||
- Can coordinate with other Caddy instances in a cluster
|
- Can coordinate with other Caddy instances in a cluster
|
||||||
- Multi-issuer fallback
|
- Multi-issuer fallback
|
||||||
|
- Encrypted ClientHello (ECH) support
|
||||||
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
|
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
|
||||||
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates
|
- **Production-ready** after serving trillions of requests and managing millions of TLS certificates
|
||||||
- **Scales to hundreds of thousands of sites** as proven in production
|
- **Scales to hundreds of thousands of sites** as proven in production
|
||||||
|
@ -87,7 +89,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- [Go 1.22.3 or newer](https://golang.org/dl/)
|
- [Go 1.25.0 or newer](https://golang.org/dl/)
|
||||||
|
|
||||||
### For development
|
### For development
|
||||||
|
|
||||||
|
@ -176,7 +178,7 @@ The docs are also open source. You can contribute to them here: https://github.c
|
||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
- We advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com/my/contact-us?dd=caddy) before help is needed.
|
- We advise companies using Caddy to secure a support contract through [Ardan Labs](https://www.ardanlabs.com) before help is needed.
|
||||||
|
|
||||||
- A [sponsorship](https://github.com/sponsors/mholt) goes a long way! We can offer private help to sponsors. If Caddy is benefitting your company, please consider a sponsorship. This not only helps fund full-time work to ensure the longevity of the project, it provides your company the resources, support, and discounts you need; along with being a great look for your company to your customers and potential customers!
|
- A [sponsorship](https://github.com/sponsors/mholt) goes a long way! We can offer private help to sponsors. If Caddy is benefitting your company, please consider a sponsorship. This not only helps fund full-time work to ensure the longevity of the project, it provides your company the resources, support, and discounts you need; along with being a great look for your company to your customers and potential customers!
|
||||||
|
|
||||||
|
@ -192,8 +194,8 @@ Matthew Holt began developing Caddy in 2014 while studying computer science at B
|
||||||
|
|
||||||
**The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH.
|
**The name "Caddy" is trademarked.** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". Caddy is a registered trademark of Stack Holdings GmbH.
|
||||||
|
|
||||||
- _Project on Twitter: [@caddyserver](https://twitter.com/caddyserver)_
|
- _Project on X: [@caddyserver](https://x.com/caddyserver)_
|
||||||
- _Author on Twitter: [@mholt6](https://twitter.com/mholt6)_
|
- _Author on X: [@mholt6](https://x.com/mholt6)_
|
||||||
|
|
||||||
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.
|
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.
|
||||||
|
|
||||||
|
|
60
admin.go
60
admin.go
|
@ -221,7 +221,8 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool, _ Co
|
||||||
if remote {
|
if remote {
|
||||||
muxWrap.remoteControl = admin.Remote
|
muxWrap.remoteControl = admin.Remote
|
||||||
} else {
|
} else {
|
||||||
muxWrap.enforceHost = !addr.isWildcardInterface()
|
// see comment in allowedOrigins() as to why we disable the host check for unix/fd networks
|
||||||
|
muxWrap.enforceHost = !addr.isWildcardInterface() && !addr.IsUnixNetwork() && !addr.IsFdNetwork()
|
||||||
muxWrap.allowedOrigins = admin.allowedOrigins(addr)
|
muxWrap.allowedOrigins = admin.allowedOrigins(addr)
|
||||||
muxWrap.enforceOrigin = admin.EnforceOrigin
|
muxWrap.enforceOrigin = admin.EnforceOrigin
|
||||||
}
|
}
|
||||||
|
@ -310,9 +311,6 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
||||||
for _, o := range admin.Origins {
|
for _, o := range admin.Origins {
|
||||||
uniqueOrigins[o] = struct{}{}
|
uniqueOrigins[o] = struct{}{}
|
||||||
}
|
}
|
||||||
if admin.Origins == nil {
|
|
||||||
if addr.isLoopback() {
|
|
||||||
if addr.IsUnixNetwork() || addr.IsFdNetwork() {
|
|
||||||
// RFC 2616, Section 14.26:
|
// RFC 2616, Section 14.26:
|
||||||
// "A client MUST include a Host header field in all HTTP/1.1 request
|
// "A client MUST include a Host header field in all HTTP/1.1 request
|
||||||
// messages. If the requested URI does not include an Internet host
|
// messages. If the requested URI does not include an Internet host
|
||||||
|
@ -330,27 +328,26 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
||||||
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
|
// bogus host; see https://superuser.com/a/925610.) If we disable Host/Origin
|
||||||
// security checks, the infosec community assures me that it is secure to do
|
// security checks, the infosec community assures me that it is secure to do
|
||||||
// so, because:
|
// so, because:
|
||||||
|
//
|
||||||
// 1) Browsers do not allow access to unix sockets
|
// 1) Browsers do not allow access to unix sockets
|
||||||
// 2) DNS is irrelevant to unix sockets
|
// 2) DNS is irrelevant to unix sockets
|
||||||
//
|
//
|
||||||
// I am not quite ready to trust either of those external factors, so instead
|
// If either of those two statements ever fail to hold true, it is not the
|
||||||
// of disabling Host/Origin checks, we now allow specific Host values when
|
// fault of Caddy.
|
||||||
// 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),
|
// Thus, we do not fill out allowed origins and do not enforce Host
|
||||||
// and IP shouldn't even be used, but if it is for some reason, I think we can
|
// requirements for unix sockets. Enforcing it leads to confusion and
|
||||||
// at least be reasonably assured that 127.0.0.1 and ::1 route to the local
|
// frustration, when UDS have their own permissions from the OS.
|
||||||
// machine, meaning that a hypothetical browser origin would have to be on the
|
// Enforcing host requirements here is effectively security theater,
|
||||||
// local machine as well.
|
// and a false sense of security.
|
||||||
uniqueOrigins[""] = struct{}{}
|
//
|
||||||
uniqueOrigins["127.0.0.1"] = struct{}{}
|
// See also the discussion in #6832.
|
||||||
uniqueOrigins["::1"] = struct{}{}
|
if admin.Origins == nil && !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
|
||||||
} else {
|
if addr.isLoopback() {
|
||||||
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
||||||
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
||||||
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
if !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
|
|
||||||
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -427,6 +424,13 @@ 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
|
||||||
|
@ -548,6 +552,13 @@ 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()
|
||||||
|
@ -935,7 +946,7 @@ func (h adminHandler) originAllowed(origin *url.URL) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// etagHasher returns a the hasher we used on the config to both
|
// etagHasher returns the hasher we used on the config to both
|
||||||
// produce and verify ETags.
|
// produce and verify ETags.
|
||||||
func etagHasher() hash.Hash { return xxhash.New() }
|
func etagHasher() hash.Hash { return xxhash.New() }
|
||||||
|
|
||||||
|
@ -1018,6 +1029,13 @@ func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this request changed the config, clear the last
|
||||||
|
// config info we have stored, if it is different from
|
||||||
|
// the original source.
|
||||||
|
ClearLastConfigIfDifferent(
|
||||||
|
r.Header.Get("Caddy-Config-Source-File"),
|
||||||
|
r.Header.Get("Caddy-Config-Source-Adapter"))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return APIError{
|
return APIError{
|
||||||
HTTPStatus: http.StatusMethodNotAllowed,
|
HTTPStatus: http.StatusMethodNotAllowed,
|
||||||
|
@ -1139,7 +1157,7 @@ traverseLoop:
|
||||||
return fmt.Errorf("[%s] invalid array index '%s': %v",
|
return fmt.Errorf("[%s] invalid array index '%s': %v",
|
||||||
path, idxStr, err)
|
path, idxStr, err)
|
||||||
}
|
}
|
||||||
if idx < 0 || idx >= len(arr) {
|
if idx < 0 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) {
|
||||||
return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr)
|
return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
732
admin_test.go
732
admin_test.go
|
@ -15,12 +15,20 @@
|
||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testCfg = []byte(`{
|
var testCfg = []byte(`{
|
||||||
|
@ -141,11 +149,9 @@ func TestLoadConcurrent(t *testing.T) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
_ = Load(testCfg, true)
|
_ = Load(testCfg, true)
|
||||||
wg.Done()
|
})
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
@ -199,7 +205,723 @@ func TestETags(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkLoad(b *testing.B) {
|
func BenchmarkLoad(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for b.Loop() {
|
||||||
Load(testCfg, true)
|
Load(testCfg, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdminHandlerErrorHandling(t *testing.T) {
|
||||||
|
initAdminMetrics()
|
||||||
|
|
||||||
|
handler := adminHandler{
|
||||||
|
mux: http.NewServeMux(),
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.mux.Handle("/error", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := fmt.Errorf("test error")
|
||||||
|
handler.handleError(w, r, err)
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/error", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code == http.StatusOK {
|
||||||
|
t.Error("expected error response, got success")
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiErr APIError
|
||||||
|
if err := json.NewDecoder(rr.Body).Decode(&apiErr); err != nil {
|
||||||
|
t.Fatalf("decoding response: %v", err)
|
||||||
|
}
|
||||||
|
if apiErr.Message != "test error" {
|
||||||
|
t.Errorf("expected error message 'test error', got '%s'", apiErr.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAdminMetrics() {
|
||||||
|
if adminMetrics.requestErrors != nil {
|
||||||
|
prometheus.Unregister(adminMetrics.requestErrors)
|
||||||
|
}
|
||||||
|
if adminMetrics.requestCount != nil {
|
||||||
|
prometheus.Unregister(adminMetrics.requestCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
adminMetrics.requestErrors = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: "caddy",
|
||||||
|
Subsystem: "admin_http",
|
||||||
|
Name: "request_errors_total",
|
||||||
|
Help: "Number of errors that occurred handling admin endpoint requests",
|
||||||
|
}, []string{"handler", "path", "method"})
|
||||||
|
|
||||||
|
adminMetrics.requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Namespace: "caddy",
|
||||||
|
Subsystem: "admin_http",
|
||||||
|
Name: "requests_total",
|
||||||
|
Help: "Count of requests to the admin endpoint",
|
||||||
|
}, []string{"handler", "path", "code", "method"}) // Added code and method labels
|
||||||
|
|
||||||
|
prometheus.MustRegister(adminMetrics.requestErrors)
|
||||||
|
prometheus.MustRegister(adminMetrics.requestCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
|
||||||
|
initAdminMetrics()
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Listen: "localhost:2019",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := replaceLocalAdminServer(cfg, Context{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("setting up admin server: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
stopAdminServer(localAdminServer)
|
||||||
|
}()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
method string
|
||||||
|
expectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "stop endpoint wrong method",
|
||||||
|
path: "/stop",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedStatus: http.StatusMethodNotAllowed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config endpoint wrong content-type",
|
||||||
|
path: "/config/",
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "config ID missing ID",
|
||||||
|
path: "/id/",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(test.method, fmt.Sprintf("http://localhost:2019%s", test.path), nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
localAdminServer.Handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != test.expectedStatus {
|
||||||
|
t.Errorf("expected status %d but got %d", test.expectedStatus, rr.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
metricValue := testGetMetricValue(map[string]string{
|
||||||
|
"path": test.path,
|
||||||
|
"handler": "admin",
|
||||||
|
"method": test.method,
|
||||||
|
})
|
||||||
|
if metricValue != 1 {
|
||||||
|
t.Errorf("expected error metric to be incremented once, got %v", metricValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetMetricValue(labels map[string]string) float64 {
|
||||||
|
promLabels := prometheus.Labels{}
|
||||||
|
maps.Copy(promLabels, labels)
|
||||||
|
|
||||||
|
metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pb := &dto.Metric{}
|
||||||
|
metric.Write(pb)
|
||||||
|
return pb.GetCounter().GetValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockRouter struct {
|
||||||
|
routes []AdminRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockRouter) Routes() []AdminRoute {
|
||||||
|
return m.routes
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockModule struct {
|
||||||
|
mockRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockModule) CaddyModule() ModuleInfo {
|
||||||
|
return ModuleInfo{
|
||||||
|
ID: "admin.api.mock",
|
||||||
|
New: func() Module {
|
||||||
|
mm := &mockModule{
|
||||||
|
mockRouter: mockRouter{
|
||||||
|
routes: m.routes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return mm
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAdminHandlerRouterRegistration(t *testing.T) {
|
||||||
|
originalModules := make(map[string]ModuleInfo)
|
||||||
|
maps.Copy(originalModules, modules)
|
||||||
|
defer func() {
|
||||||
|
modules = originalModules
|
||||||
|
}()
|
||||||
|
|
||||||
|
mockRoute := AdminRoute{
|
||||||
|
Pattern: "/mock",
|
||||||
|
Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
mock := &mockModule{
|
||||||
|
mockRouter: mockRouter{
|
||||||
|
routes: []AdminRoute{mockRoute},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
RegisterModule(mock)
|
||||||
|
|
||||||
|
addr, err := ParseNetworkAddress("localhost:2019")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
admin := &AdminConfig{
|
||||||
|
EnforceOrigin: false,
|
||||||
|
}
|
||||||
|
handler := admin.newAdminHandler(addr, false, Context{})
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/mock", nil)
|
||||||
|
req.Host = "localhost:2019"
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if rr.Code != http.StatusOK {
|
||||||
|
t.Errorf("Expected status code %d but got %d", http.StatusOK, rr.Code)
|
||||||
|
t.Logf("Response body: %s", rr.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(admin.routers) != 1 {
|
||||||
|
t.Errorf("Expected 1 router to be stored, got %d", len(admin.routers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockProvisionableRouter struct {
|
||||||
|
mockRouter
|
||||||
|
provisionErr error
|
||||||
|
provisioned bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockProvisionableRouter) Provision(Context) error {
|
||||||
|
m.provisioned = true
|
||||||
|
return m.provisionErr
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockProvisionableModule struct {
|
||||||
|
*mockProvisionableRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockProvisionableModule) CaddyModule() ModuleInfo {
|
||||||
|
return ModuleInfo{
|
||||||
|
ID: "admin.api.mock_provision",
|
||||||
|
New: func() Module {
|
||||||
|
mm := &mockProvisionableModule{
|
||||||
|
mockProvisionableRouter: &mockProvisionableRouter{
|
||||||
|
mockRouter: m.mockRouter,
|
||||||
|
provisionErr: m.provisionErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return mm
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdminRouterProvisioning(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
provisionErr error
|
||||||
|
wantErr bool
|
||||||
|
routersAfter int // expected number of routers after provisioning
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "successful provisioning",
|
||||||
|
provisionErr: nil,
|
||||||
|
wantErr: false,
|
||||||
|
routersAfter: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provisioning error",
|
||||||
|
provisionErr: fmt.Errorf("provision failed"),
|
||||||
|
wantErr: true,
|
||||||
|
routersAfter: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
originalModules := make(map[string]ModuleInfo)
|
||||||
|
maps.Copy(originalModules, modules)
|
||||||
|
defer func() {
|
||||||
|
modules = originalModules
|
||||||
|
}()
|
||||||
|
|
||||||
|
mockRoute := AdminRoute{
|
||||||
|
Pattern: "/mock",
|
||||||
|
Handler: AdminHandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create provisionable module
|
||||||
|
mock := &mockProvisionableModule{
|
||||||
|
mockProvisionableRouter: &mockProvisionableRouter{
|
||||||
|
mockRouter: mockRouter{
|
||||||
|
routes: []AdminRoute{mockRoute},
|
||||||
|
},
|
||||||
|
provisionErr: test.provisionErr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
RegisterModule(mock)
|
||||||
|
|
||||||
|
admin := &AdminConfig{}
|
||||||
|
addr, err := ParseNetworkAddress("localhost:2019")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = admin.newAdminHandler(addr, false, Context{})
|
||||||
|
err = admin.provisionAdminRouters(Context{})
|
||||||
|
|
||||||
|
if test.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error but got nil")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(admin.routers) != test.routersAfter {
|
||||||
|
t.Errorf("Expected %d routers after provisioning, got %d", test.routersAfter, len(admin.routers))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllowedOriginsUnixSocket(t *testing.T) {
|
||||||
|
// see comment in allowedOrigins() as to why we do not fill out allowed origins for UDS
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
addr NetworkAddress
|
||||||
|
origins []string
|
||||||
|
expectOrigins []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unix socket with default origins",
|
||||||
|
addr: NetworkAddress{
|
||||||
|
Network: "unix",
|
||||||
|
Host: "/tmp/caddy.sock",
|
||||||
|
},
|
||||||
|
origins: nil, // default origins
|
||||||
|
expectOrigins: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unix socket with custom origins",
|
||||||
|
addr: NetworkAddress{
|
||||||
|
Network: "unix",
|
||||||
|
Host: "/tmp/caddy.sock",
|
||||||
|
},
|
||||||
|
origins: []string{"example.com"},
|
||||||
|
expectOrigins: []string{
|
||||||
|
"example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tcp socket on localhost gets all loopback addresses",
|
||||||
|
addr: NetworkAddress{
|
||||||
|
Network: "tcp",
|
||||||
|
Host: "localhost",
|
||||||
|
StartPort: 2019,
|
||||||
|
EndPort: 2019,
|
||||||
|
},
|
||||||
|
origins: nil,
|
||||||
|
expectOrigins: []string{
|
||||||
|
"localhost:2019",
|
||||||
|
"[::1]:2019",
|
||||||
|
"127.0.0.1:2019",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
admin := AdminConfig{
|
||||||
|
Origins: test.origins,
|
||||||
|
}
|
||||||
|
|
||||||
|
got := admin.allowedOrigins(test.addr)
|
||||||
|
|
||||||
|
var gotOrigins []string
|
||||||
|
for _, u := range got {
|
||||||
|
gotOrigins = append(gotOrigins, u.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gotOrigins) != len(test.expectOrigins) {
|
||||||
|
t.Errorf("%d: Expected %d origins but got %d", i, len(test.expectOrigins), len(gotOrigins))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expectMap := make(map[string]struct{})
|
||||||
|
for _, origin := range test.expectOrigins {
|
||||||
|
expectMap[origin] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
gotMap := make(map[string]struct{})
|
||||||
|
for _, origin := range gotOrigins {
|
||||||
|
gotMap[origin] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expectMap, gotMap) {
|
||||||
|
t.Errorf("%d: Origins mismatch.\nExpected: %v\nGot: %v", i, test.expectOrigins, gotOrigins)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplaceRemoteAdminServer(t *testing.T) {
|
||||||
|
const testCert = `MIIDCTCCAfGgAwIBAgIUXsqJ1mY8pKlHQtI3HJ23x2eZPqwwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDEwMTAwMDAwMFoXDTI0MDEw
|
||||||
|
MTAwMDAwMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
|
||||||
|
AAOCAQ8AMIIBCgKCAQEA4O4S6BSoYcoxvRqI+h7yPOjF6KjntjzVVm9M+uHK4lzX
|
||||||
|
F1L3pSxJ2nDD4wZEV3FJ5yFOHVFqkG2vXG3BIczOlYG7UeNmKbQnKc5kZj3HGUrS
|
||||||
|
VGEktA4OJbeZhhWP15gcXN5eDM2eH3g9BFXVX6AURxLiUXzhNBUEZuj/OEyH9yEF
|
||||||
|
/qPCE+EjzVvWxvBXwgz/io4r4yok/Vq/bxJ6FlV6R7DX5oJSXyO0VEHZPi9DIyNU
|
||||||
|
kK3F/r4U1sWiJGWOs8i3YQWZ2ejh1C0aLFZpPcCGGgMNpoF31gyYP6ZuPDUyCXsE
|
||||||
|
g36UUw1JHNtIXYcLhnXuqj4A8TybTDpgXLqvwA9DBQIDAQABo1MwUTAdBgNVHQ4E
|
||||||
|
FgQUc13z30pFC63rr/HGKOE7E82vjXwwHwYDVR0jBBgwFoAUc13z30pFC63rr/HG
|
||||||
|
KOE7E82vjXwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAHO3j
|
||||||
|
oeiUXXJ7xD4P8Wj5t9d+E8lE1Xv1Dk3Z+EdG5+dan+RcToE42JJp9zB7FIh5Qz8g
|
||||||
|
W77LAjqh5oyqz3A2VJcyVgfE3uJP1R1mJM7JfGHf84QH4TZF2Q1RZY4SZs0VQ6+q
|
||||||
|
5wSlIZ4NXDy4Q4XkIJBGS61wT8IzYFXYBpx4PCP1Qj0PIE4sevEGwjsBIgxK307o
|
||||||
|
BxF8AWe6N6e4YZmQLGjQ+SeH0iwZb6vpkHyAY8Kj2hvK+cq2P7vU3VGi0t3r1F8L
|
||||||
|
IvrXHCvO2BMNJ/1UK1M4YNX8LYJqQhg9hEsIROe1OE/m3VhxIYMJI+qZXk9yHfgJ
|
||||||
|
vq+SH04xKhtFudVBAQ==`
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil config",
|
||||||
|
cfg: nil,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil admin config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: nil,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil remote config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid listen address",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Remote: &RemoteAdmin{
|
||||||
|
Listen: "invalid:address",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{},
|
||||||
|
Remote: &RemoteAdmin{
|
||||||
|
Listen: "localhost:2021",
|
||||||
|
AccessControl: []*AdminAccess{
|
||||||
|
{
|
||||||
|
PublicKeys: []string{testCert},
|
||||||
|
Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid certificate",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{},
|
||||||
|
Remote: &RemoteAdmin{
|
||||||
|
Listen: "localhost:2021",
|
||||||
|
AccessControl: []*AdminAccess{
|
||||||
|
{
|
||||||
|
PublicKeys: []string{"invalid-cert-data"},
|
||||||
|
Permissions: []AdminPermissions{{Methods: []string{"GET"}, Paths: []string{"/test"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ctx := Context{
|
||||||
|
Context: context.Background(),
|
||||||
|
cfg: test.cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.cfg != nil {
|
||||||
|
test.cfg.storage = &certmagic.FileStorage{Path: t.TempDir()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.cfg != nil && test.cfg.Admin != nil && test.cfg.Admin.Identity != nil {
|
||||||
|
identityCertCache = certmagic.NewCache(certmagic.CacheOptions{
|
||||||
|
GetConfigForCert: func(certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
|
return &certmagic.Config{}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err := replaceRemoteAdminServer(ctx, test.cfg)
|
||||||
|
|
||||||
|
if test.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error but got nil")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
if remoteAdminServer != nil {
|
||||||
|
_ = stopAdminServer(remoteAdminServer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockIssuer struct {
|
||||||
|
configSet *certmagic.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||||
|
return &certmagic.IssuedCertificate{
|
||||||
|
Certificate: []byte(csr.Raw),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockIssuer) SetConfig(cfg *certmagic.Config) {
|
||||||
|
m.configSet = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockIssuer) IssuerKey() string {
|
||||||
|
return "mock"
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockIssuerModule struct {
|
||||||
|
*mockIssuer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockIssuerModule) CaddyModule() ModuleInfo {
|
||||||
|
return ModuleInfo{
|
||||||
|
ID: "tls.issuance.acme",
|
||||||
|
New: func() Module {
|
||||||
|
return &mockIssuerModule{mockIssuer: new(mockIssuer)}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManageIdentity(t *testing.T) {
|
||||||
|
originalModules := make(map[string]ModuleInfo)
|
||||||
|
maps.Copy(originalModules, modules)
|
||||||
|
defer func() {
|
||||||
|
modules = originalModules
|
||||||
|
}()
|
||||||
|
|
||||||
|
RegisterModule(&mockIssuerModule{})
|
||||||
|
|
||||||
|
certPEM := []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDujCCAqKgAwIBAgIIE31FZVaPXTUwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
|
||||||
|
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
|
||||||
|
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTQwMTI5MTMyNzQzWhcNMTQwNTI5MDAwMDAw
|
||||||
|
WjBpMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
|
||||||
|
TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEYMBYGA1UEAwwPbWFp
|
||||||
|
bC5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3lcub2pUwkjC
|
||||||
|
5GJQA2ZZfJJi6d1QHhEmkX9VxKYGp6gagZuRqJWy9TXP6++1ZzQQxqZLD0TkuxZ9
|
||||||
|
8i9Nz00000CCBjCCAQQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMGgG
|
||||||
|
CCsGAQUFBwEBBFwwWjArBggrBgEFBQcwAoYfaHR0cDovL3BraS5nb29nbGUuY29t
|
||||||
|
L0dJQUcyLmNydDArBggrBgEFBQcwAYYfaHR0cDovL2NsaWVudHMxLmdvb2dsZS5j
|
||||||
|
b20vb2NzcDAdBgNVHQ4EFgQUiJxtimAuTfwb+aUtBn5UYKreKvMwDAYDVR0TAQH/
|
||||||
|
BAIwADAfBgNVHSMEGDAWgBRK3QYWG7z2aLV29YG2u2IaulqBLzAXBgNVHREEEDAO
|
||||||
|
ggxtYWlsLmdvb2dsZTANBgkqhkiG9w0BAQUFAAOCAQEAMP6IWgNGZE8wP9TjFjSZ
|
||||||
|
3mmW3A1eIr0CuPwNZ2LJ5ZD1i70ojzcj4I9IdP5yPg9CAEV4hNASbM1LzfC7GmJE
|
||||||
|
tPzW5tRmpKVWZGRgTgZI8Hp/xZXMwLh9ZmXV4kESFAGj5G5FNvJyUV7R5Eh+7OZX
|
||||||
|
7G4jJ4ZGJh+5jzN9HdJJHQHGYNIYOzC7+HH9UMwCjX9vhQ4RjwFZJThS2Yb+y7pb
|
||||||
|
9yxTJZoXC6J0H5JpnZb7kZEJ+Xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
keyPEM := []byte(`-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
|
||||||
|
...
|
||||||
|
-----END PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
testStorage := certmagic.FileStorage{Path: t.TempDir()}
|
||||||
|
err := testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = testStorage.Store(context.Background(), "localhost/localhost.key", keyPEM)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg *Config
|
||||||
|
wantErr bool
|
||||||
|
checkState func(*testing.T, *Config)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil config",
|
||||||
|
cfg: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil admin config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil identity config",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default issuer when none specified",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{
|
||||||
|
Identifiers: []string{"localhost"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
storage: &testStorage,
|
||||||
|
},
|
||||||
|
checkState: func(t *testing.T, cfg *Config) {
|
||||||
|
if len(cfg.Admin.Identity.issuers) == 0 {
|
||||||
|
t.Error("Expected at least 1 issuer to be configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule); !ok {
|
||||||
|
t.Error("Expected mock issuer to be configured")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom issuer",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{
|
||||||
|
Identifiers: []string{"localhost"},
|
||||||
|
IssuersRaw: []json.RawMessage{
|
||||||
|
json.RawMessage(`{"module": "acme"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
storage: &certmagic.FileStorage{Path: "testdata"},
|
||||||
|
},
|
||||||
|
checkState: func(t *testing.T, cfg *Config) {
|
||||||
|
if len(cfg.Admin.Identity.issuers) != 1 {
|
||||||
|
t.Fatalf("Expected 1 issuer, got %d", len(cfg.Admin.Identity.issuers))
|
||||||
|
}
|
||||||
|
mockIss, ok := cfg.Admin.Identity.issuers[0].(*mockIssuerModule)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected mock issuer")
|
||||||
|
}
|
||||||
|
if mockIss.configSet == nil {
|
||||||
|
t.Error("Issuer config was not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid issuer module",
|
||||||
|
cfg: &Config{
|
||||||
|
Admin: &AdminConfig{
|
||||||
|
Identity: &IdentityConfig{
|
||||||
|
Identifiers: []string{"localhost"},
|
||||||
|
IssuersRaw: []json.RawMessage{
|
||||||
|
json.RawMessage(`{"module": "doesnt_exist"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if identityCertCache != nil {
|
||||||
|
// Reset the cert cache before each test
|
||||||
|
identityCertCache.Stop()
|
||||||
|
identityCertCache = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := Context{
|
||||||
|
Context: context.Background(),
|
||||||
|
cfg: test.cfg,
|
||||||
|
moduleInstances: make(map[string][]Module),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := manageIdentity(ctx, test.cfg)
|
||||||
|
|
||||||
|
if test.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error but got nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.checkState != nil {
|
||||||
|
test.checkState(t, test.cfg)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
241
caddy.go
241
caddy.go
|
@ -82,12 +82,16 @@ type Config struct {
|
||||||
AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
|
AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
|
||||||
|
|
||||||
apps map[string]App
|
apps map[string]App
|
||||||
|
|
||||||
|
// failedApps is a map of apps that failed to provision with their underlying error.
|
||||||
|
failedApps map[string]error
|
||||||
storage certmagic.Storage
|
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.
|
||||||
|
@ -407,11 +411,23 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,14 +453,18 @@ 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.
|
||||||
return ctx, finishSettingUp(ctx, ctx.cfg)
|
err = finishSettingUp(ctx, ctx.cfg)
|
||||||
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// provisionContext creates a new context from the given configuration and provisions
|
// provisionContext creates a new context from the given configuration and provisions
|
||||||
|
@ -500,19 +520,12 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
|
||||||
return ctx, err
|
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)
|
||||||
|
newCfg.failedApps = make(map[string]error)
|
||||||
|
|
||||||
// set up global storage and make it CertMagic's default storage, too
|
// set up global storage and make it CertMagic's default storage, too
|
||||||
err = func() error {
|
err = func() error {
|
||||||
|
@ -539,6 +552,14 @@ 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 {
|
||||||
|
@ -696,6 +717,9 @@ 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()
|
||||||
|
@ -725,8 +749,10 @@ func Validate(cfg *Config) error {
|
||||||
// Errors are logged along the way, and an appropriate exit
|
// Errors are logged along the way, and an appropriate exit
|
||||||
// code is emitted.
|
// code is emitted.
|
||||||
func exitProcess(ctx context.Context, logger *zap.Logger) {
|
func exitProcess(ctx context.Context, logger *zap.Logger) {
|
||||||
// let the rest of the program know we're quitting
|
// let the rest of the program know we're quitting; only do it once
|
||||||
atomic.StoreInt32(exiting, 1)
|
if !atomic.CompareAndSwapInt32(exiting, 0, 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// give the OS or service/process manager our 2 weeks' notice: we quit
|
// give the OS or service/process manager our 2 weeks' notice: we quit
|
||||||
if err := notify.Stopping(); err != nil {
|
if err := notify.Stopping(); err != nil {
|
||||||
|
@ -949,11 +975,11 @@ func Version() (simple, full string) {
|
||||||
if CustomVersion != "" {
|
if CustomVersion != "" {
|
||||||
full = CustomVersion
|
full = CustomVersion
|
||||||
simple = CustomVersion
|
simple = CustomVersion
|
||||||
return
|
return simple, full
|
||||||
}
|
}
|
||||||
full = "unknown"
|
full = "unknown"
|
||||||
simple = "unknown"
|
simple = "unknown"
|
||||||
return
|
return simple, full
|
||||||
}
|
}
|
||||||
// find the Caddy module in the dependency list
|
// find the Caddy module in the dependency list
|
||||||
for _, dep := range bi.Deps {
|
for _, dep := range bi.Deps {
|
||||||
|
@ -1033,9 +1059,101 @@ func Version() (simple, full string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return simple, full
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Event represents something that has happened or is happening.
|
||||||
|
// An Event value is not synchronized, so it should be copied if
|
||||||
|
// being used in goroutines.
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL: Events are subject to change.
|
||||||
|
type Event struct {
|
||||||
|
// If non-nil, the event has been aborted, meaning
|
||||||
|
// propagation has stopped to other handlers and
|
||||||
|
// the code should stop what it was doing. Emitters
|
||||||
|
// may choose to use this as a signal to adjust their
|
||||||
|
// code path appropriately.
|
||||||
|
Aborted error
|
||||||
|
|
||||||
|
// The data associated with the event. Usually the
|
||||||
|
// original emitter will be the only one to set or
|
||||||
|
// change these values, but the field is exported
|
||||||
|
// so handlers can have full access if needed.
|
||||||
|
// However, this map is not synchronized, so
|
||||||
|
// handlers must not use this map directly in new
|
||||||
|
// goroutines; instead, copy the map to use it in a
|
||||||
|
// goroutine. Data may be nil.
|
||||||
|
Data map[string]any
|
||||||
|
|
||||||
|
id uuid.UUID
|
||||||
|
ts time.Time
|
||||||
|
name string
|
||||||
|
origin Module
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEvent creates a new event, but does not emit the event. To emit an
|
||||||
|
// event, call Emit() on the current instance of the caddyevents app insteaad.
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL: Subject to change.
|
||||||
|
func NewEvent(ctx Context, name string, data map[string]any) (Event, error) {
|
||||||
|
id, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return Event{}, fmt.Errorf("generating new event ID: %v", err)
|
||||||
|
}
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
return Event{
|
||||||
|
Data: data,
|
||||||
|
id: id,
|
||||||
|
ts: time.Now(),
|
||||||
|
name: name,
|
||||||
|
origin: ctx.Module(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) ID() uuid.UUID { return e.id }
|
||||||
|
func (e Event) Timestamp() time.Time { return e.ts }
|
||||||
|
func (e Event) Name() string { return e.name }
|
||||||
|
func (e Event) Origin() Module { return e.origin } // Returns the module that originated the event. May be nil, usually if caddy core emits the event.
|
||||||
|
|
||||||
|
// CloudEvent exports event e as a structure that, when
|
||||||
|
// serialized as JSON, is compatible with the
|
||||||
|
// CloudEvents spec.
|
||||||
|
func (e Event) CloudEvent() CloudEvent {
|
||||||
|
dataJSON, _ := json.Marshal(e.Data)
|
||||||
|
var source string
|
||||||
|
if e.Origin() == nil {
|
||||||
|
source = "caddy"
|
||||||
|
} else {
|
||||||
|
source = string(e.Origin().CaddyModule().ID)
|
||||||
|
}
|
||||||
|
return CloudEvent{
|
||||||
|
ID: e.id.String(),
|
||||||
|
Source: source,
|
||||||
|
SpecVersion: "1.0",
|
||||||
|
Type: e.name,
|
||||||
|
Time: e.ts,
|
||||||
|
DataContentType: "application/json",
|
||||||
|
Data: dataJSON,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloudEvent is a JSON-serializable structure that
|
||||||
|
// is compatible with the CloudEvents specification.
|
||||||
|
// See https://cloudevents.io.
|
||||||
|
// EXPERIMENTAL: Subject to change.
|
||||||
|
type CloudEvent struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
SpecVersion string `json:"specversion"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
DataContentType string `json:"datacontenttype,omitempty"`
|
||||||
|
Data json.RawMessage `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrEventAborted cancels an event.
|
||||||
|
var ErrEventAborted = errors.New("event aborted")
|
||||||
|
|
||||||
// ActiveContext returns the currently-active context.
|
// 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.
|
||||||
|
@ -1079,6 +1197,91 @@ var (
|
||||||
rawCfgMu sync.RWMutex
|
rawCfgMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// lastConfigFile and lastConfigAdapter remember the source config
|
||||||
|
// file and adapter used when Caddy was started via the CLI "run" command.
|
||||||
|
// These are consulted by the SIGUSR1 handler to attempt reloading from
|
||||||
|
// the same source. They are intentionally not set for other entrypoints
|
||||||
|
// such as "caddy start" or subcommands like file-server.
|
||||||
|
var (
|
||||||
|
lastConfigMu sync.RWMutex
|
||||||
|
lastConfigFile string
|
||||||
|
lastConfigAdapter string
|
||||||
|
)
|
||||||
|
|
||||||
|
// reloadFromSourceFunc is the type of stored callback
|
||||||
|
// which is called when we receive a SIGUSR1 signal.
|
||||||
|
type reloadFromSourceFunc func(file, adapter string) error
|
||||||
|
|
||||||
|
// reloadFromSourceCallback is the stored callback
|
||||||
|
// which is called when we receive a SIGUSR1 signal.
|
||||||
|
var reloadFromSourceCallback reloadFromSourceFunc
|
||||||
|
|
||||||
|
// errReloadFromSourceUnavailable is returned when no reload-from-source callback is set.
|
||||||
|
var errReloadFromSourceUnavailable = errors.New("reload from source unavailable in this process") //nolint:unused
|
||||||
|
|
||||||
|
// SetLastConfig records the given source file and adapter as the
|
||||||
|
// last-known external configuration source. Intended to be called
|
||||||
|
// only when starting via "caddy run --config <file> --adapter <adapter>".
|
||||||
|
func SetLastConfig(file, adapter string, fn reloadFromSourceFunc) {
|
||||||
|
lastConfigMu.Lock()
|
||||||
|
lastConfigFile = file
|
||||||
|
lastConfigAdapter = adapter
|
||||||
|
reloadFromSourceCallback = fn
|
||||||
|
lastConfigMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearLastConfigIfDifferent clears the recorded last-config if the provided
|
||||||
|
// source file/adapter do not match the recorded last-config. If both srcFile
|
||||||
|
// and srcAdapter are empty, the last-config is cleared.
|
||||||
|
func ClearLastConfigIfDifferent(srcFile, srcAdapter string) {
|
||||||
|
if (srcFile != "" || srcAdapter != "") && lastConfigMatches(srcFile, srcAdapter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SetLastConfig("", "", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLastConfig returns the last-known config file and adapter.
|
||||||
|
func getLastConfig() (file, adapter string, fn reloadFromSourceFunc) {
|
||||||
|
lastConfigMu.RLock()
|
||||||
|
f, a, cb := lastConfigFile, lastConfigAdapter, reloadFromSourceCallback
|
||||||
|
lastConfigMu.RUnlock()
|
||||||
|
return f, a, cb
|
||||||
|
}
|
||||||
|
|
||||||
|
// lastConfigMatches returns true if the provided source file and/or adapter
|
||||||
|
// matches the recorded last-config. Matching rules (in priority order):
|
||||||
|
// 1. If srcAdapter is provided and differs from the recorded adapter, no match.
|
||||||
|
// 2. If srcFile exactly equals the recorded file, match.
|
||||||
|
// 3. If both sides can be made absolute and equal, match.
|
||||||
|
// 4. If basenames are equal, match.
|
||||||
|
func lastConfigMatches(srcFile, srcAdapter string) bool {
|
||||||
|
lf, la, _ := getLastConfig()
|
||||||
|
|
||||||
|
// If adapter is provided, it must match.
|
||||||
|
if srcAdapter != "" && srcAdapter != la {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick equality check.
|
||||||
|
if srcFile == lf {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try absolute path comparison.
|
||||||
|
sAbs, sErr := filepath.Abs(srcFile)
|
||||||
|
lAbs, lErr := filepath.Abs(lf)
|
||||||
|
if sErr == nil && lErr == nil && sAbs == lAbs {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final fallback: basename equality.
|
||||||
|
if filepath.Base(srcFile) == filepath.Base(lf) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// errSameConfig is returned if the new config is the same
|
// errSameConfig is returned if the new config is the same
|
||||||
// as the old one. This isn't usually an actual, actionable
|
// as the old one. This isn't usually an actual, actionable
|
||||||
// error; it's mostly a sentinel value.
|
// error; it's mostly a sentinel value.
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -72,3 +73,21 @@ func TestParseDuration(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEvent_CloudEvent_NilOrigin(t *testing.T) {
|
||||||
|
ctx, _ := NewContext(Context{Context: context.Background()}) // module will be nil by default
|
||||||
|
event, err := NewEvent(ctx, "started", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewEvent() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should not panic
|
||||||
|
ce := event.CloudEvent()
|
||||||
|
|
||||||
|
if ce.Source != "caddy" {
|
||||||
|
t.Errorf("Expected CloudEvent Source to be 'caddy', got '%s'", ce.Source)
|
||||||
|
}
|
||||||
|
if ce.Type != "started" {
|
||||||
|
t.Errorf("Expected CloudEvent Type to be 'started', got '%s'", ce.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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.Replace(body, []byte("\r\n"), []byte("\n"), -1)
|
normalizedBody := bytes.ReplaceAll(body, []byte("\r\n"), []byte("\n"))
|
||||||
|
|
||||||
formatted := Format(normalizedBody)
|
formatted := Format(normalizedBody)
|
||||||
if bytes.Equal(formatted, normalizedBody) {
|
if bytes.Equal(formatted, normalizedBody) {
|
||||||
|
|
|
@ -308,9 +308,9 @@ func (d *Dispenser) CountRemainingArgs() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemainingArgs loads any more arguments (tokens on the same line)
|
// RemainingArgs loads any more arguments (tokens on the same line)
|
||||||
// into a slice and returns them. Open curly brace tokens also indicate
|
// into a slice of strings and returns them. Open curly brace tokens
|
||||||
// the end of arguments, and the curly brace is not included in
|
// also indicate the end of arguments, and the curly brace is not
|
||||||
// the return value nor is it loaded.
|
// included in the return value nor is it loaded.
|
||||||
func (d *Dispenser) RemainingArgs() []string {
|
func (d *Dispenser) RemainingArgs() []string {
|
||||||
var args []string
|
var args []string
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
|
@ -320,9 +320,9 @@ func (d *Dispenser) RemainingArgs() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemainingArgsRaw loads any more arguments (tokens on the same line,
|
// RemainingArgsRaw loads any more arguments (tokens on the same line,
|
||||||
// retaining quotes) into a slice and returns them. Open curly brace
|
// retaining quotes) into a slice of strings and returns them.
|
||||||
// tokens also indicate the end of arguments, and the curly brace is
|
// Open curly brace tokens also indicate the end of arguments,
|
||||||
// not included in the return value nor is it loaded.
|
// and the curly brace is not included in the return value nor is it loaded.
|
||||||
func (d *Dispenser) RemainingArgsRaw() []string {
|
func (d *Dispenser) RemainingArgsRaw() []string {
|
||||||
var args []string
|
var args []string
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
|
@ -331,6 +331,18 @@ func (d *Dispenser) RemainingArgsRaw() []string {
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemainingArgsAsTokens loads any more arguments (tokens on the same line)
|
||||||
|
// into a slice of Token-structs and returns them. Open curly brace tokens
|
||||||
|
// also indicate the end of arguments, and the curly brace is not included
|
||||||
|
// in the return value nor is it loaded.
|
||||||
|
func (d *Dispenser) RemainingArgsAsTokens() []Token {
|
||||||
|
var args []Token
|
||||||
|
for d.NextArg() {
|
||||||
|
args = append(args, d.Token())
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
// NewFromNextSegment returns a new dispenser with a copy of
|
// NewFromNextSegment returns a new dispenser with a copy of
|
||||||
// the tokens from the current token until the end of the
|
// the tokens from the current token until the end of the
|
||||||
// "directive" whether that be to the end of the line or
|
// "directive" whether that be to the end of the line or
|
||||||
|
|
|
@ -274,6 +274,66 @@ func TestDispenser_RemainingArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDispenser_RemainingArgsAsTokens(t *testing.T) {
|
||||||
|
input := `dir1 arg1 arg2 arg3
|
||||||
|
dir2 arg4 arg5
|
||||||
|
dir3 arg6 { arg7
|
||||||
|
dir4`
|
||||||
|
d := NewTestDispenser(input)
|
||||||
|
|
||||||
|
d.Next() // dir1
|
||||||
|
|
||||||
|
args := d.RemainingArgsAsTokens()
|
||||||
|
|
||||||
|
tokenTexts := make([]string, 0, len(args))
|
||||||
|
for _, arg := range args {
|
||||||
|
tokenTexts = append(tokenTexts, arg.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(tokenTexts, expected) {
|
||||||
|
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Next() // dir2
|
||||||
|
|
||||||
|
args = d.RemainingArgsAsTokens()
|
||||||
|
|
||||||
|
tokenTexts = tokenTexts[:0]
|
||||||
|
for _, arg := range args {
|
||||||
|
tokenTexts = append(tokenTexts, arg.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(tokenTexts, expected) {
|
||||||
|
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Next() // dir3
|
||||||
|
|
||||||
|
args = d.RemainingArgsAsTokens()
|
||||||
|
tokenTexts = tokenTexts[:0]
|
||||||
|
for _, arg := range args {
|
||||||
|
tokenTexts = append(tokenTexts, arg.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if expected := []string{"arg6"}; !reflect.DeepEqual(tokenTexts, expected) {
|
||||||
|
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Next() // {
|
||||||
|
d.Next() // arg7
|
||||||
|
d.Next() // dir4
|
||||||
|
|
||||||
|
args = d.RemainingArgsAsTokens()
|
||||||
|
tokenTexts = tokenTexts[:0]
|
||||||
|
for _, arg := range args {
|
||||||
|
tokenTexts = append(tokenTexts, arg.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) != 0 {
|
||||||
|
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", []string{}, tokenTexts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDispenser_ArgErr_Err(t *testing.T) {
|
func TestDispenser_ArgErr_Err(t *testing.T) {
|
||||||
input := `dir1 {
|
input := `dir1 {
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ func Format(input []byte) []byte {
|
||||||
heredocClosingMarker []rune
|
heredocClosingMarker []rune
|
||||||
|
|
||||||
nesting int // indentation level
|
nesting int // indentation level
|
||||||
|
withinBackquote bool
|
||||||
)
|
)
|
||||||
|
|
||||||
write := func(ch rune) {
|
write := func(ch rune) {
|
||||||
|
@ -88,9 +89,12 @@ 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
|
||||||
|
@ -220,7 +224,7 @@ func Format(input []byte) []byte {
|
||||||
openBrace = false
|
openBrace = false
|
||||||
if beginningOfLine {
|
if beginningOfLine {
|
||||||
indent()
|
indent()
|
||||||
} else if !openBraceSpace {
|
} else if !openBraceSpace || !unicode.IsSpace(last) {
|
||||||
write(' ')
|
write(' ')
|
||||||
}
|
}
|
||||||
write('{')
|
write('{')
|
||||||
|
@ -236,14 +240,23 @@ 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 && newLines == 0 {
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -432,6 +432,31 @@ block2 {
|
||||||
heredoc \<<HEREDOC
|
heredoc \<<HEREDOC
|
||||||
respond "More than one space will be eaten" 200
|
respond "More than one space will be eaten" 200
|
||||||
}
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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`}}\"}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No trailing space on line before env variable",
|
||||||
|
input: `{
|
||||||
|
a
|
||||||
|
|
||||||
|
{$ENV_VAR}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expect: `{
|
||||||
|
a
|
||||||
|
|
||||||
|
{$ENV_VAR}
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
|
|
@ -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 == ' ' {
|
||||||
|
@ -323,7 +323,8 @@ func (l *lexer) finalizeHeredoc(val []rune, marker string) ([]rune, error) {
|
||||||
|
|
||||||
// if the padding doesn't match exactly at the start then we can't safely strip
|
// if the padding doesn't match exactly at the start then we can't safely strip
|
||||||
if index != 0 {
|
if index != 0 {
|
||||||
return nil, fmt.Errorf("mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker", marker, l.line+lineNum+1, lineText, paddingToStrip)
|
cleanLineText := strings.TrimRight(lineText, "\r\n")
|
||||||
|
return nil, fmt.Errorf("mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker", marker, l.line+lineNum+1, cleanLineText, paddingToStrip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip, then append the line, with the newline, to the output.
|
// strip, then append the line, with the newline, to the output.
|
||||||
|
|
|
@ -379,28 +379,23 @@ func (p *parser) doImport(nesting int) error {
|
||||||
if len(blockTokens) > 0 {
|
if len(blockTokens) > 0 {
|
||||||
// use such tokens to create a new dispenser, and then use it to parse each block
|
// use such tokens to create a new dispenser, and then use it to parse each block
|
||||||
bd := NewDispenser(blockTokens)
|
bd := NewDispenser(blockTokens)
|
||||||
|
|
||||||
|
// one iteration processes one sub-block inside the import
|
||||||
for bd.Next() {
|
for bd.Next() {
|
||||||
// see if we can grab a key
|
currentMappingKey := bd.Val()
|
||||||
var currentMappingKey string
|
|
||||||
if bd.Val() == "{" {
|
if currentMappingKey == "{" {
|
||||||
return p.Err("anonymous blocks are not supported")
|
return p.Err("anonymous blocks are not supported")
|
||||||
}
|
}
|
||||||
currentMappingKey = bd.Val()
|
|
||||||
currentMappingTokens := []Token{}
|
// load up all arguments (if there even are any)
|
||||||
// read all args until end of line / {
|
currentMappingTokens := bd.RemainingArgsAsTokens()
|
||||||
if bd.NextArg() {
|
|
||||||
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
// load up the entire block
|
||||||
for bd.NextArg() {
|
|
||||||
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
|
||||||
}
|
|
||||||
// TODO(elee1766): we don't enter another mapping here because it's annoying to extract the { and } properly.
|
|
||||||
// maybe someone can do that in the future
|
|
||||||
} else {
|
|
||||||
// attempt to enter a block and add tokens to the currentMappingTokens
|
|
||||||
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
|
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
|
||||||
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
blockMapping[currentMappingKey] = currentMappingTokens
|
blockMapping[currentMappingKey] = currentMappingTokens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,29 +533,24 @@ func (p *parser) doImport(nesting int) error {
|
||||||
}
|
}
|
||||||
// if it is {block}, we substitute with all tokens in the block
|
// if it is {block}, we substitute with all tokens in the block
|
||||||
// if it is {blocks.*}, we substitute with the tokens in the mapping for the *
|
// if it is {blocks.*}, we substitute with the tokens in the mapping for the *
|
||||||
var skip bool
|
|
||||||
var tokensToAdd []Token
|
var tokensToAdd []Token
|
||||||
|
foundBlockDirective := false
|
||||||
switch {
|
switch {
|
||||||
case token.Text == "{block}":
|
case token.Text == "{block}":
|
||||||
|
foundBlockDirective = true
|
||||||
tokensToAdd = blockTokens
|
tokensToAdd = blockTokens
|
||||||
case strings.HasPrefix(token.Text, "{blocks.") && strings.HasSuffix(token.Text, "}"):
|
case strings.HasPrefix(token.Text, "{blocks.") && strings.HasSuffix(token.Text, "}"):
|
||||||
|
foundBlockDirective = true
|
||||||
// {blocks.foo.bar} will be extracted to key `foo.bar`
|
// {blocks.foo.bar} will be extracted to key `foo.bar`
|
||||||
blockKey := strings.TrimPrefix(strings.TrimSuffix(token.Text, "}"), "{blocks.")
|
blockKey := strings.TrimPrefix(strings.TrimSuffix(token.Text, "}"), "{blocks.")
|
||||||
val, ok := blockMapping[blockKey]
|
val, ok := blockMapping[blockKey]
|
||||||
if ok {
|
if ok {
|
||||||
tokensToAdd = val
|
tokensToAdd = val
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
skip = true
|
|
||||||
}
|
}
|
||||||
if !skip {
|
|
||||||
if len(tokensToAdd) == 0 {
|
if foundBlockDirective {
|
||||||
// if there is no content in the snippet block, don't do any replacement
|
|
||||||
// this allows snippets which contained {block}/{block.*} before this change to continue functioning as normal
|
|
||||||
tokensCopy = append(tokensCopy, token)
|
|
||||||
} else {
|
|
||||||
tokensCopy = append(tokensCopy, tokensToAdd...)
|
tokensCopy = append(tokensCopy, tokensToAdd...)
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -884,6 +885,51 @@ func TestRejectsGlobalMatcher(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRejectAnonymousImportBlock(t *testing.T) {
|
||||||
|
p := testParser(`
|
||||||
|
(site) {
|
||||||
|
http://{args[0]} https://{args[0]} {
|
||||||
|
{block}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import site test.domain {
|
||||||
|
{
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
_, err := p.parseAll()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected an error, but got nil")
|
||||||
|
}
|
||||||
|
expected := "anonymous blocks are not supported"
|
||||||
|
if !strings.HasPrefix(err.Error(), "anonymous blocks are not supported") {
|
||||||
|
t.Errorf("Expected error to start with '%s' but got '%v'", expected, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAcceptSiteImportWithBraces(t *testing.T) {
|
||||||
|
p := testParser(`
|
||||||
|
(site) {
|
||||||
|
http://{args[0]} https://{args[0]} {
|
||||||
|
{block}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import site test.domain {
|
||||||
|
reverse_proxy http://192.168.1.1:8080 {
|
||||||
|
header_up Host {host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
_, err := p.parseAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected error to be nil but got '%v'", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testParser(input string) parser {
|
func testParser(input string) parser {
|
||||||
return parser{Dispenser: NewTestDispenser(input)}
|
return parser{Dispenser: NewTestDispenser(input)}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -99,7 +100,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
||||||
// ca <acme_ca_endpoint>
|
// ca <acme_ca_endpoint>
|
||||||
// ca_root <pem_file>
|
// ca_root <pem_file>
|
||||||
// key_type [ed25519|p256|p384|rsa2048|rsa4096]
|
// key_type [ed25519|p256|p384|rsa2048|rsa4096]
|
||||||
// dns <provider_name> [...]
|
// dns [<provider_name> [...]] (required, though, if DNS is not configured as global option)
|
||||||
// propagation_delay <duration>
|
// propagation_delay <duration>
|
||||||
// propagation_timeout <duration>
|
// propagation_timeout <duration>
|
||||||
// resolvers <dns_servers...>
|
// resolvers <dns_servers...>
|
||||||
|
@ -129,6 +130,9 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
var reusePrivateKeys bool
|
var reusePrivateKeys bool
|
||||||
var forceAutomate bool
|
var forceAutomate bool
|
||||||
|
|
||||||
|
// Track which DNS challenge options are set
|
||||||
|
var dnsOptionsSet []string
|
||||||
|
|
||||||
firstLine := h.RemainingArgs()
|
firstLine := h.RemainingArgs()
|
||||||
switch len(firstLine) {
|
switch len(firstLine) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -312,10 +316,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
certManagers = append(certManagers, certManager)
|
certManagers = append(certManagers, certManager)
|
||||||
|
|
||||||
case "dns":
|
case "dns":
|
||||||
if !h.NextArg() {
|
|
||||||
return nil, h.ArgErr()
|
|
||||||
}
|
|
||||||
provName := h.Val()
|
|
||||||
if acmeIssuer == nil {
|
if acmeIssuer == nil {
|
||||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||||
}
|
}
|
||||||
|
@ -325,12 +325,19 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
if acmeIssuer.Challenges.DNS == nil {
|
if acmeIssuer.Challenges.DNS == nil {
|
||||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||||
}
|
}
|
||||||
|
// DNS provider configuration optional, since it may be configured globally via the TLS app with global options
|
||||||
|
if h.NextArg() {
|
||||||
|
provName := h.Val()
|
||||||
modID := "dns.providers." + provName
|
modID := "dns.providers." + provName
|
||||||
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
|
unm, err := caddyfile.UnmarshalModule(h.Dispenser, modID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings)
|
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, h.warnings)
|
||||||
|
} else if h.Option("dns") == nil {
|
||||||
|
// if DNS is omitted locally, it needs to be configured globally
|
||||||
|
return nil, h.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
case "resolvers":
|
case "resolvers":
|
||||||
args := h.RemainingArgs()
|
args := h.RemainingArgs()
|
||||||
|
@ -346,6 +353,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
if acmeIssuer.Challenges.DNS == nil {
|
if acmeIssuer.Challenges.DNS == nil {
|
||||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||||
}
|
}
|
||||||
|
dnsOptionsSet = append(dnsOptionsSet, "resolvers")
|
||||||
acmeIssuer.Challenges.DNS.Resolvers = args
|
acmeIssuer.Challenges.DNS.Resolvers = args
|
||||||
|
|
||||||
case "propagation_delay":
|
case "propagation_delay":
|
||||||
|
@ -367,6 +375,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
if acmeIssuer.Challenges.DNS == nil {
|
if acmeIssuer.Challenges.DNS == nil {
|
||||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||||
}
|
}
|
||||||
|
dnsOptionsSet = append(dnsOptionsSet, "propagation_delay")
|
||||||
acmeIssuer.Challenges.DNS.PropagationDelay = caddy.Duration(delay)
|
acmeIssuer.Challenges.DNS.PropagationDelay = caddy.Duration(delay)
|
||||||
|
|
||||||
case "propagation_timeout":
|
case "propagation_timeout":
|
||||||
|
@ -394,6 +403,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
if acmeIssuer.Challenges.DNS == nil {
|
if acmeIssuer.Challenges.DNS == nil {
|
||||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||||
}
|
}
|
||||||
|
dnsOptionsSet = append(dnsOptionsSet, "propagation_timeout")
|
||||||
acmeIssuer.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout)
|
acmeIssuer.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout)
|
||||||
|
|
||||||
case "dns_ttl":
|
case "dns_ttl":
|
||||||
|
@ -415,6 +425,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
if acmeIssuer.Challenges.DNS == nil {
|
if acmeIssuer.Challenges.DNS == nil {
|
||||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||||
}
|
}
|
||||||
|
dnsOptionsSet = append(dnsOptionsSet, "dns_ttl")
|
||||||
acmeIssuer.Challenges.DNS.TTL = caddy.Duration(ttl)
|
acmeIssuer.Challenges.DNS.TTL = caddy.Duration(ttl)
|
||||||
|
|
||||||
case "dns_challenge_override_domain":
|
case "dns_challenge_override_domain":
|
||||||
|
@ -431,6 +442,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
if acmeIssuer.Challenges.DNS == nil {
|
if acmeIssuer.Challenges.DNS == nil {
|
||||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||||
}
|
}
|
||||||
|
dnsOptionsSet = append(dnsOptionsSet, "dns_challenge_override_domain")
|
||||||
acmeIssuer.Challenges.DNS.OverrideDomain = arg[0]
|
acmeIssuer.Challenges.DNS.OverrideDomain = arg[0]
|
||||||
|
|
||||||
case "ca_root":
|
case "ca_root":
|
||||||
|
@ -466,6 +478,18 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate DNS challenge config: any DNS challenge option except "dns" requires a DNS provider
|
||||||
|
if acmeIssuer != nil && acmeIssuer.Challenges != nil && acmeIssuer.Challenges.DNS != nil {
|
||||||
|
dnsCfg := acmeIssuer.Challenges.DNS
|
||||||
|
providerSet := dnsCfg.ProviderRaw != nil || h.Option("dns") != nil || h.Option("acme_dns") != nil
|
||||||
|
if len(dnsOptionsSet) > 0 && !providerSet {
|
||||||
|
return nil, h.Errf(
|
||||||
|
"setting DNS challenge options [%s] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option)",
|
||||||
|
strings.Join(dnsOptionsSet, ", "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// a naked tls directive is not allowed
|
// a naked tls directive is not allowed
|
||||||
if len(firstLine) == 0 && !hasBlock {
|
if len(firstLine) == 0 && !hasBlock {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
|
@ -840,13 +864,18 @@ 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}),
|
||||||
}
|
}
|
||||||
for i := range subroute.Routes {
|
subroute.Routes[0].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
|
||||||
subroute.Routes[i].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return []ConfigValue{
|
return []ConfigValue{
|
||||||
{
|
{
|
||||||
|
@ -1157,6 +1186,11 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -173,10 +174,12 @@ func RegisterDirectiveOrder(dir string, position Positional, standardDir string)
|
||||||
if d != standardDir {
|
if d != standardDir {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if position == Before {
|
switch position {
|
||||||
|
case Before:
|
||||||
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
|
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
|
||||||
} else if position == After {
|
case After:
|
||||||
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
|
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
|
||||||
|
case First, Last:
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -365,9 +368,7 @@ 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))
|
||||||
for key, val := range h.matcherDefs {
|
maps.Copy(matcherDefs, 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++ {
|
||||||
|
@ -483,12 +484,29 @@ 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 strings.TrimSuffix(iPM[0], "*") == strings.TrimSuffix(jPM[0], "*") {
|
if iPathTrimmed == jPathTrimmed {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[*caddyhttp.Metrics](metrics, &caddyhttp.Metrics{})
|
metrics = cmp.Or(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[[]string, string](defaultLog.Exclude)
|
defaultLog.Exclude = slices.Compact(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,12 +633,6 @@ 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,16 +700,6 @@ 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
|
||||||
|
|
||||||
|
@ -801,7 +785,13 @@ 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
|
||||||
|
@ -841,18 +831,6 @@ 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 &&
|
||||||
|
@ -1083,11 +1061,40 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
|
||||||
|
|
||||||
// if they're exactly equal in every way, just keep one of them
|
// if 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 = append(cps[:j], cps[j+1:]...)
|
cps = slices.Delete(cps, j, 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) {
|
||||||
|
@ -1121,6 +1128,12 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
|
||||||
return nil, fmt.Errorf("two policies with same match criteria have conflicting default SNI: %s vs. %s",
|
return nil, fmt.Errorf("two policies with same match criteria have conflicting default SNI: %s vs. %s",
|
||||||
cps[i].DefaultSNI, cps[j].DefaultSNI)
|
cps[i].DefaultSNI, cps[j].DefaultSNI)
|
||||||
}
|
}
|
||||||
|
if cps[i].FallbackSNI != "" &&
|
||||||
|
cps[j].FallbackSNI != "" &&
|
||||||
|
cps[i].FallbackSNI != cps[j].FallbackSNI {
|
||||||
|
return nil, fmt.Errorf("two policies with same match criteria have conflicting fallback SNI: %s vs. %s",
|
||||||
|
cps[i].FallbackSNI, cps[j].FallbackSNI)
|
||||||
|
}
|
||||||
if cps[i].ProtocolMin != "" &&
|
if cps[i].ProtocolMin != "" &&
|
||||||
cps[j].ProtocolMin != "" &&
|
cps[j].ProtocolMin != "" &&
|
||||||
cps[i].ProtocolMin != cps[j].ProtocolMin {
|
cps[i].ProtocolMin != cps[j].ProtocolMin {
|
||||||
|
@ -1161,6 +1174,9 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
|
||||||
if cps[i].DefaultSNI == "" && cps[j].DefaultSNI != "" {
|
if cps[i].DefaultSNI == "" && cps[j].DefaultSNI != "" {
|
||||||
cps[i].DefaultSNI = cps[j].DefaultSNI
|
cps[i].DefaultSNI = cps[j].DefaultSNI
|
||||||
}
|
}
|
||||||
|
if cps[i].FallbackSNI == "" && cps[j].FallbackSNI != "" {
|
||||||
|
cps[i].FallbackSNI = cps[j].FallbackSNI
|
||||||
|
}
|
||||||
if cps[i].ProtocolMin == "" && cps[j].ProtocolMin != "" {
|
if cps[i].ProtocolMin == "" && cps[j].ProtocolMin != "" {
|
||||||
cps[i].ProtocolMin = cps[j].ProtocolMin
|
cps[i].ProtocolMin = cps[j].ProtocolMin
|
||||||
}
|
}
|
||||||
|
@ -1180,12 +1196,13 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cps = append(cps[:j], cps[j+1:]...)
|
cps = slices.Delete(cps, j, j+1)
|
||||||
i--
|
i--
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cps, nil
|
return cps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/libdns/libdns"
|
||||||
"github.com/mholt/acmez/v3/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
@ -45,7 +46,7 @@ func init() {
|
||||||
RegisterGlobalOption("ocsp_interval", parseOptDuration)
|
RegisterGlobalOption("ocsp_interval", parseOptDuration)
|
||||||
RegisterGlobalOption("acme_ca", parseOptSingleString)
|
RegisterGlobalOption("acme_ca", parseOptSingleString)
|
||||||
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
|
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
|
||||||
RegisterGlobalOption("acme_dns", parseOptACMEDNS)
|
RegisterGlobalOption("acme_dns", parseOptDNS)
|
||||||
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
|
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
|
||||||
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
|
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
|
||||||
RegisterGlobalOption("skip_install_trust", parseOptTrue)
|
RegisterGlobalOption("skip_install_trust", parseOptTrue)
|
||||||
|
@ -62,6 +63,8 @@ func init() {
|
||||||
RegisterGlobalOption("log", parseLogOptions)
|
RegisterGlobalOption("log", parseLogOptions)
|
||||||
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
||||||
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
||||||
|
RegisterGlobalOption("dns", parseOptDNS)
|
||||||
|
RegisterGlobalOption("ech", parseOptECH)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
||||||
|
@ -238,25 +241,6 @@ func parseOptDuration(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
return caddy.Duration(dur), nil
|
return caddy.Duration(dur), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptACMEDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
||||||
if !d.Next() { // consume option name
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
if !d.Next() { // get DNS module name
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
modID := "dns.providers." + d.Val()
|
|
||||||
unm, err := caddyfile.UnmarshalModule(d, modID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
prov, ok := unm.(certmagic.DNSProvider)
|
|
||||||
if !ok {
|
|
||||||
return nil, d.Errf("module %s (%T) is not a certmagic.DNSProvider", modID, unm)
|
|
||||||
}
|
|
||||||
return prov, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptACMEEAB(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
eab := new(acme.EAB)
|
eab := new(acme.EAB)
|
||||||
d.Next() // consume option name
|
d.Next() // consume option name
|
||||||
|
@ -474,8 +458,6 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
case "disable_certs":
|
case "disable_certs":
|
||||||
case "ignore_loaded_certs":
|
case "ignore_loaded_certs":
|
||||||
case "prefer_wildcard":
|
case "prefer_wildcard":
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'")
|
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'")
|
||||||
}
|
}
|
||||||
|
@ -570,3 +552,74 @@ func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
d.Next()
|
d.Next()
|
||||||
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
|
return caddytls.ParseCaddyfilePreferredChainsOptions(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
|
d.Next() // consume option name
|
||||||
|
optName := d.Val()
|
||||||
|
|
||||||
|
// get DNS module name
|
||||||
|
if !d.Next() {
|
||||||
|
// this is allowed if this is the "acme_dns" option since it may refer to the globally-configured "dns" option's value
|
||||||
|
if optName == "acme_dns" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
modID := "dns.providers." + d.Val()
|
||||||
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch unm.(type) {
|
||||||
|
case libdns.RecordGetter,
|
||||||
|
libdns.RecordSetter,
|
||||||
|
libdns.RecordAppender,
|
||||||
|
libdns.RecordDeleter:
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("module %s (%T) is not a libdns provider", modID, unm)
|
||||||
|
}
|
||||||
|
return unm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOptECH(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
|
d.Next() // consume option name
|
||||||
|
|
||||||
|
ech := new(caddytls.ECH)
|
||||||
|
|
||||||
|
publicNames := d.RemainingArgs()
|
||||||
|
for _, publicName := range publicNames {
|
||||||
|
ech.Configs = append(ech.Configs, caddytls.ECHConfiguration{
|
||||||
|
PublicName: publicName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(ech.Configs) == 0 {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
|
switch d.Val() {
|
||||||
|
case "dns":
|
||||||
|
if !d.Next() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
providerName := d.Val()
|
||||||
|
modID := "dns.providers." + providerName
|
||||||
|
unm, err := caddyfile.UnmarshalModule(d, modID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ech.Publication = append(ech.Publication, &caddytls.ECHPublication{
|
||||||
|
Configs: publicNames,
|
||||||
|
PublishersRaw: caddy.ModuleMap{
|
||||||
|
"dns": caddyconfig.JSON(caddytls.ECHDNSPublisher{
|
||||||
|
ProviderRaw: caddyconfig.JSONModuleObject(unm, "name", providerName, nil),
|
||||||
|
}, nil),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("ech: unrecognized subdirective '%s'", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ech, nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
|
@ -178,6 +180,15 @@ func (st ServerType) buildPKIApp(
|
||||||
if _, ok := options["skip_install_trust"]; ok {
|
if _, ok := options["skip_install_trust"]; ok {
|
||||||
skipInstallTrust = true
|
skipInstallTrust = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if auto_https is off - in that case we should not create
|
||||||
|
// any PKI infrastructure even with skip_install_trust directive
|
||||||
|
autoHTTPS := []string{}
|
||||||
|
if ah, ok := options["auto_https"].([]string); ok {
|
||||||
|
autoHTTPS = ah
|
||||||
|
}
|
||||||
|
autoHTTPSOff := slices.Contains(autoHTTPS, "off")
|
||||||
|
|
||||||
falseBool := false
|
falseBool := false
|
||||||
|
|
||||||
// Load the PKI app configured via global options
|
// Load the PKI app configured via global options
|
||||||
|
@ -218,7 +229,8 @@ func (st ServerType) buildPKIApp(
|
||||||
// if there was no CAs defined in any of the servers,
|
// if there was no CAs defined in any of the servers,
|
||||||
// and we were requested to not install trust, then
|
// and we were requested to not install trust, then
|
||||||
// add one for the default/local CA to do so
|
// add one for the default/local CA to do so
|
||||||
if len(pkiApp.CAs) == 0 && skipInstallTrust {
|
// only if auto_https is not completely disabled
|
||||||
|
if len(pkiApp.CAs) == 0 && skipInstallTrust && !autoHTTPSOff {
|
||||||
ca := new(caddypki.CA)
|
ca := new(caddypki.CA)
|
||||||
ca.ID = caddypki.DefaultCAID
|
ca.ID = caddypki.DefaultCAID
|
||||||
ca.InstallTrust = &falseBool
|
ca.InstallTrust = &falseBool
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
|
||||||
|
@ -42,12 +43,15 @@ type serverOptions struct {
|
||||||
WriteTimeout caddy.Duration
|
WriteTimeout caddy.Duration
|
||||||
IdleTimeout caddy.Duration
|
IdleTimeout caddy.Duration
|
||||||
KeepAliveInterval caddy.Duration
|
KeepAliveInterval caddy.Duration
|
||||||
|
KeepAliveIdle caddy.Duration
|
||||||
|
KeepAliveCount int
|
||||||
MaxHeaderBytes int
|
MaxHeaderBytes int
|
||||||
EnableFullDuplex bool
|
EnableFullDuplex bool
|
||||||
Protocols []string
|
Protocols []string
|
||||||
StrictSNIHost *bool
|
StrictSNIHost *bool
|
||||||
TrustedProxiesRaw json.RawMessage
|
TrustedProxiesRaw json.RawMessage
|
||||||
TrustedProxiesStrict int
|
TrustedProxiesStrict int
|
||||||
|
TrustedProxiesUnix bool
|
||||||
ClientIPHeaders []string
|
ClientIPHeaders []string
|
||||||
ShouldLogCredentials bool
|
ShouldLogCredentials bool
|
||||||
Metrics *caddyhttp.Metrics
|
Metrics *caddyhttp.Metrics
|
||||||
|
@ -142,6 +146,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||||
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
|
return nil, d.Errf("unrecognized timeouts option '%s'", d.Val())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "keepalive_interval":
|
case "keepalive_interval":
|
||||||
if !d.NextArg() {
|
if !d.NextArg() {
|
||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
|
@ -152,6 +157,26 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||||
}
|
}
|
||||||
serverOpts.KeepAliveInterval = caddy.Duration(dur)
|
serverOpts.KeepAliveInterval = caddy.Duration(dur)
|
||||||
|
|
||||||
|
case "keepalive_idle":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
dur, err := caddy.ParseDuration(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return nil, d.Errf("parsing keepalive idle duration: %v", err)
|
||||||
|
}
|
||||||
|
serverOpts.KeepAliveIdle = caddy.Duration(dur)
|
||||||
|
|
||||||
|
case "keepalive_count":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
cnt, err := strconv.ParseInt(d.Val(), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, d.Errf("parsing keepalive count int: %v", err)
|
||||||
|
}
|
||||||
|
serverOpts.KeepAliveCount = int(cnt)
|
||||||
|
|
||||||
case "max_header_size":
|
case "max_header_size":
|
||||||
var sizeStr string
|
var sizeStr string
|
||||||
if !d.AllArgs(&sizeStr) {
|
if !d.AllArgs(&sizeStr) {
|
||||||
|
@ -227,6 +252,12 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||||
}
|
}
|
||||||
serverOpts.TrustedProxiesStrict = 1
|
serverOpts.TrustedProxiesStrict = 1
|
||||||
|
|
||||||
|
case "trusted_proxies_unix":
|
||||||
|
if d.NextArg() {
|
||||||
|
return nil, d.ArgErr()
|
||||||
|
}
|
||||||
|
serverOpts.TrustedProxiesUnix = true
|
||||||
|
|
||||||
case "client_ip_headers":
|
case "client_ip_headers":
|
||||||
headers := d.RemainingArgs()
|
headers := d.RemainingArgs()
|
||||||
for _, header := range headers {
|
for _, header := range headers {
|
||||||
|
@ -246,6 +277,8 @@ 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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +340,8 @@ func applyServerOptions(
|
||||||
server.WriteTimeout = opts.WriteTimeout
|
server.WriteTimeout = opts.WriteTimeout
|
||||||
server.IdleTimeout = opts.IdleTimeout
|
server.IdleTimeout = opts.IdleTimeout
|
||||||
server.KeepAliveInterval = opts.KeepAliveInterval
|
server.KeepAliveInterval = opts.KeepAliveInterval
|
||||||
|
server.KeepAliveIdle = opts.KeepAliveIdle
|
||||||
|
server.KeepAliveCount = opts.KeepAliveCount
|
||||||
server.MaxHeaderBytes = opts.MaxHeaderBytes
|
server.MaxHeaderBytes = opts.MaxHeaderBytes
|
||||||
server.EnableFullDuplex = opts.EnableFullDuplex
|
server.EnableFullDuplex = opts.EnableFullDuplex
|
||||||
server.Protocols = opts.Protocols
|
server.Protocols = opts.Protocols
|
||||||
|
@ -314,6 +349,7 @@ func applyServerOptions(
|
||||||
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
|
server.TrustedProxiesRaw = opts.TrustedProxiesRaw
|
||||||
server.ClientIPHeaders = opts.ClientIPHeaders
|
server.ClientIPHeaders = opts.ClientIPHeaders
|
||||||
server.TrustedProxiesStrict = opts.TrustedProxiesStrict
|
server.TrustedProxiesStrict = opts.TrustedProxiesStrict
|
||||||
|
server.TrustedProxiesUnix = opts.TrustedProxiesUnix
|
||||||
server.Metrics = opts.Metrics
|
server.Metrics = opts.Metrics
|
||||||
if opts.ShouldLogCredentials {
|
if opts.ShouldLogCredentials {
|
||||||
if server.Logs == nil {
|
if server.Logs == nil {
|
||||||
|
|
|
@ -64,10 +64,13 @@ func placeholderShorthands() []string {
|
||||||
"{orig_?query}", "{http.request.orig_uri.prefixed_query}",
|
"{orig_?query}", "{http.request.orig_uri.prefixed_query}",
|
||||||
"{method}", "{http.request.method}",
|
"{method}", "{http.request.method}",
|
||||||
"{uri}", "{http.request.uri}",
|
"{uri}", "{http.request.uri}",
|
||||||
|
"{%uri}", "{http.request.uri_escaped}",
|
||||||
"{path}", "{http.request.uri.path}",
|
"{path}", "{http.request.uri.path}",
|
||||||
|
"{%path}", "{http.request.uri.path_escaped}",
|
||||||
"{dir}", "{http.request.uri.path.dir}",
|
"{dir}", "{http.request.uri.path.dir}",
|
||||||
"{file}", "{http.request.uri.path.file}",
|
"{file}", "{http.request.uri.path.file}",
|
||||||
"{query}", "{http.request.uri.query}",
|
"{query}", "{http.request.uri.query}",
|
||||||
|
"{%query}", "{http.request.uri.query_escaped}",
|
||||||
"{?query}", "{http.request.uri.prefixed_query}",
|
"{?query}", "{http.request.uri.prefixed_query}",
|
||||||
"{remote}", "{http.request.remote}",
|
"{remote}", "{http.request.remote}",
|
||||||
"{remote_host}", "{http.request.remote.host}",
|
"{remote_host}", "{http.request.remote.host}",
|
||||||
|
|
|
@ -92,11 +92,9 @@ func (st ServerType) buildTLSApp(
|
||||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
|
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect all hosts that have a wildcard in them, and arent HTTP
|
var wildcardHosts []string // collect all hosts that have a wildcard in them, and aren't HTTP
|
||||||
wildcardHosts := []string{}
|
forcedAutomatedNames := make(map[string]struct{}) // explicitly configured to be automated, even if covered by a wildcard
|
||||||
// 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 {
|
||||||
|
@ -153,7 +151,7 @@ func (st ServerType) buildTLSApp(
|
||||||
ap.OnDemand = true
|
ap.OnDemand = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// collect hosts that are forced to be automated
|
// collect hosts that are forced to have certs automated for their specific name
|
||||||
if _, ok := sblock.pile["tls.force_automate"]; ok {
|
if _, ok := sblock.pile["tls.force_automate"]; ok {
|
||||||
for _, host := range sblockHosts {
|
for _, host := range sblockHosts {
|
||||||
forcedAutomatedNames[host] = struct{}{}
|
forcedAutomatedNames[host] = struct{}{}
|
||||||
|
@ -340,7 +338,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 := 0; i < clVal.Len(); i++ {
|
for i := range clVal.Len() {
|
||||||
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)
|
||||||
|
@ -359,6 +357,32 @@ func (st ServerType) buildTLSApp(
|
||||||
tlsApp.Automation.OnDemand = onDemand
|
tlsApp.Automation.OnDemand = onDemand
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set up "global" (to the TLS app) DNS provider config
|
||||||
|
if globalDNS, ok := options["dns"]; ok && globalDNS != nil {
|
||||||
|
tlsApp.DNSRaw = caddyconfig.JSONModuleObject(globalDNS, "name", globalDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up ECH from Caddyfile options
|
||||||
|
if ech, ok := options["ech"].(*caddytls.ECH); ok {
|
||||||
|
tlsApp.EncryptedClientHello = ech
|
||||||
|
|
||||||
|
// outer server names will need certificates, so make sure they're included
|
||||||
|
// in an automation policy for them that applies any global options
|
||||||
|
ap, err := newBaseAutomationPolicy(options, warnings, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, warnings, err
|
||||||
|
}
|
||||||
|
for _, cfg := range ech.Configs {
|
||||||
|
if cfg.PublicName != "" {
|
||||||
|
ap.SubjectsRaw = append(ap.SubjectsRaw, cfg.PublicName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tlsApp.Automation == nil {
|
||||||
|
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||||
|
}
|
||||||
|
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
|
||||||
|
}
|
||||||
|
|
||||||
// if the storage clean interval is a boolean, then it's "off" to disable cleaning
|
// if the storage clean interval is a boolean, then it's "off" to disable cleaning
|
||||||
if sc, ok := options["storage_check"].(string); ok && sc == "off" {
|
if sc, ok := options["storage_check"].(string); ok && sc == "off" {
|
||||||
tlsApp.DisableStorageCheck = true
|
tlsApp.DisableStorageCheck = true
|
||||||
|
@ -440,12 +464,12 @@ func (st ServerType) buildTLSApp(
|
||||||
globalEmail := options["email"]
|
globalEmail := options["email"]
|
||||||
globalACMECA := options["acme_ca"]
|
globalACMECA := options["acme_ca"]
|
||||||
globalACMECARoot := options["acme_ca_root"]
|
globalACMECARoot := options["acme_ca_root"]
|
||||||
globalACMEDNS := options["acme_dns"]
|
_, globalACMEDNS := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
|
||||||
globalACMEEAB := options["acme_eab"]
|
globalACMEEAB := options["acme_eab"]
|
||||||
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 || globalACMEEAB != nil || globalPreferredChains != nil
|
||||||
if hasGlobalACMEDefaults {
|
if hasGlobalACMEDefaults {
|
||||||
for i := 0; i < len(tlsApp.Automation.Policies); i++ {
|
for i := range tlsApp.Automation.Policies {
|
||||||
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
|
||||||
|
@ -525,11 +549,12 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||||
globalEmail := options["email"]
|
globalEmail := options["email"]
|
||||||
globalACMECA := options["acme_ca"]
|
globalACMECA := options["acme_ca"]
|
||||||
globalACMECARoot := options["acme_ca_root"]
|
globalACMECARoot := options["acme_ca_root"]
|
||||||
globalACMEDNS := options["acme_dns"]
|
globalACMEDNS, globalACMEDNSok := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
|
||||||
globalACMEEAB := options["acme_eab"]
|
globalACMEEAB := options["acme_eab"]
|
||||||
globalPreferredChains := options["preferred_chains"]
|
globalPreferredChains := options["preferred_chains"]
|
||||||
globalCertLifetime := options["cert_lifetime"]
|
globalCertLifetime := options["cert_lifetime"]
|
||||||
globalHTTPPort, globalHTTPSPort := options["http_port"], options["https_port"]
|
globalHTTPPort, globalHTTPSPort := options["http_port"], options["https_port"]
|
||||||
|
globalDefaultBind := options["default_bind"]
|
||||||
|
|
||||||
if globalEmail != nil && acmeIssuer.Email == "" {
|
if globalEmail != nil && acmeIssuer.Email == "" {
|
||||||
acmeIssuer.Email = globalEmail.(string)
|
acmeIssuer.Email = globalEmail.(string)
|
||||||
|
@ -540,11 +565,21 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||||
if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
|
if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
|
||||||
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
|
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
|
||||||
}
|
}
|
||||||
if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
|
if globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil || acmeIssuer.Challenges.DNS.ProviderRaw == nil) {
|
||||||
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
|
globalDNS := options["dns"]
|
||||||
DNS: &caddytls.DNSChallengeConfig{
|
if globalDNS == nil && globalACMEDNS == nil {
|
||||||
ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil),
|
return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
|
||||||
},
|
}
|
||||||
|
if acmeIssuer.Challenges == nil {
|
||||||
|
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||||
|
}
|
||||||
|
if acmeIssuer.Challenges.DNS == nil {
|
||||||
|
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||||
|
}
|
||||||
|
// If global `dns` is set, do NOT set provider in issuer, just set empty dns config
|
||||||
|
if globalDNS == nil && acmeIssuer.Challenges.DNS.ProviderRaw == nil {
|
||||||
|
// Set a global DNS provider if `acme_dns` is set and `dns` is NOT set
|
||||||
|
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
|
if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
|
||||||
|
@ -553,7 +588,8 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||||
if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
|
if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
|
||||||
acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
|
acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
|
||||||
}
|
}
|
||||||
if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
|
// only configure alt HTTP and TLS-ALPN ports if the DNS challenge is not enabled (wouldn't hurt, but isn't necessary since the DNS challenge is exclusive of others)
|
||||||
|
if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
|
||||||
if acmeIssuer.Challenges == nil {
|
if acmeIssuer.Challenges == nil {
|
||||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||||
}
|
}
|
||||||
|
@ -562,7 +598,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||||
}
|
}
|
||||||
acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int)
|
acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int)
|
||||||
}
|
}
|
||||||
if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) {
|
if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) {
|
||||||
if acmeIssuer.Challenges == nil {
|
if acmeIssuer.Challenges == nil {
|
||||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||||
}
|
}
|
||||||
|
@ -571,6 +607,20 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||||
}
|
}
|
||||||
acmeIssuer.Challenges.TLSALPN.AlternatePort = globalHTTPSPort.(int)
|
acmeIssuer.Challenges.TLSALPN.AlternatePort = globalHTTPSPort.(int)
|
||||||
}
|
}
|
||||||
|
// If BindHost is still unset, fall back to the first default_bind address if set
|
||||||
|
// This avoids binding the automation policy to the wildcard socket, which is unexpected behavior when a more selective socket is specified via default_bind
|
||||||
|
// In BSD it is valid to bind to the wildcard socket even though a more selective socket is already open (still unexpected behavior by the caller though)
|
||||||
|
// In Linux the same call will error with EADDRINUSE whenever the listener for the automation policy is opened
|
||||||
|
if acmeIssuer.Challenges == nil || (acmeIssuer.Challenges.DNS == nil && acmeIssuer.Challenges.BindHost == "") {
|
||||||
|
if defBinds, ok := globalDefaultBind.([]ConfigValue); ok && len(defBinds) > 0 {
|
||||||
|
if abp, ok := defBinds[0].Value.(addressesWithProtocols); ok && len(abp.addresses) > 0 {
|
||||||
|
if acmeIssuer.Challenges == nil {
|
||||||
|
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||||
|
}
|
||||||
|
acmeIssuer.Challenges.BindHost = abp.addresses[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if globalCertLifetime != nil && acmeIssuer.CertificateLifetime == 0 {
|
if globalCertLifetime != nil && acmeIssuer.CertificateLifetime == 0 {
|
||||||
acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration)
|
acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration)
|
||||||
}
|
}
|
||||||
|
@ -591,12 +641,18 @@ func newBaseAutomationPolicy(
|
||||||
_, hasLocalCerts := options["local_certs"]
|
_, hasLocalCerts := options["local_certs"]
|
||||||
keyType, hasKeyType := options["key_type"]
|
keyType, hasKeyType := options["key_type"]
|
||||||
ocspStapling, hasOCSPStapling := options["ocsp_stapling"]
|
ocspStapling, hasOCSPStapling := options["ocsp_stapling"]
|
||||||
|
|
||||||
hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling
|
hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling
|
||||||
|
|
||||||
|
globalACMECA := options["acme_ca"]
|
||||||
|
globalACMECARoot := options["acme_ca_root"]
|
||||||
|
_, globalACMEDNS := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
|
||||||
|
globalACMEEAB := options["acme_eab"]
|
||||||
|
globalPreferredChains := options["preferred_chains"]
|
||||||
|
hasGlobalACMEDefaults := globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS || globalACMEEAB != nil || globalPreferredChains != nil
|
||||||
|
|
||||||
// if there are no global options related to automation policies
|
// if there are no global options related to automation policies
|
||||||
// set, then we can just return right away
|
// set, then we can just return right away
|
||||||
if !hasGlobalAutomationOpts {
|
if !hasGlobalAutomationOpts && !hasGlobalACMEDefaults {
|
||||||
if always {
|
if always {
|
||||||
return new(caddytls.AutomationPolicy), nil
|
return new(caddytls.AutomationPolicy), nil
|
||||||
}
|
}
|
||||||
|
@ -618,6 +674,14 @@ func newBaseAutomationPolicy(
|
||||||
ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
|
ap.Issuers = []certmagic.Issuer{new(caddytls.InternalIssuer)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasGlobalACMEDefaults {
|
||||||
|
for i := range ap.Issuers {
|
||||||
|
if err := fillInGlobalACMEDefaults(ap.Issuers[i], options); err != nil {
|
||||||
|
return nil, fmt.Errorf("filling in global issuer defaults for issuer %d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if hasOCSPStapling {
|
if hasOCSPStapling {
|
||||||
ocspConfig := ocspStapling.(certmagic.OCSPConfig)
|
ocspConfig := ocspStapling.(certmagic.OCSPConfig)
|
||||||
ap.DisableOCSPStapling = ocspConfig.DisableStapling
|
ap.DisableOCSPStapling = ocspConfig.DisableStapling
|
||||||
|
|
|
@ -121,6 +121,13 @@ func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this request changed the config, clear the last
|
||||||
|
// config info we have stored, if it is different from
|
||||||
|
// the original source.
|
||||||
|
caddy.ClearLastConfigIfDifferent(
|
||||||
|
r.Header.Get("Caddy-Config-Source-File"),
|
||||||
|
r.Header.Get("Caddy-Config-Source-Adapter"))
|
||||||
|
|
||||||
caddy.Log().Named("admin.api").Info("load complete")
|
caddy.Log().Named("admin.api").Info("load complete")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -31,8 +31,8 @@ import (
|
||||||
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Defaults store any configuration required to make the tests run
|
// Config store any configuration required to make the tests run
|
||||||
type Defaults struct {
|
type Config struct {
|
||||||
// Port we expect caddy to listening on
|
// Port we expect caddy to listening on
|
||||||
AdminPort int
|
AdminPort int
|
||||||
// Certificates we expect to be loaded before attempting to run the tests
|
// Certificates we expect to be loaded before attempting to run the tests
|
||||||
|
@ -44,7 +44,7 @@ type Defaults struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default testing values
|
// Default testing values
|
||||||
var Default = Defaults{
|
var Default = Config{
|
||||||
AdminPort: 2999, // different from what a real server also running on a developer's machine might be
|
AdminPort: 2999, // different from what a real server also running on a developer's machine might be
|
||||||
Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
||||||
TestRequestTimeout: 5 * time.Second,
|
TestRequestTimeout: 5 * time.Second,
|
||||||
|
@ -61,6 +61,7 @@ type Tester struct {
|
||||||
Client *http.Client
|
Client *http.Client
|
||||||
configLoaded bool
|
configLoaded bool
|
||||||
t testing.TB
|
t testing.TB
|
||||||
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTester will create a new testing client with an attached cookie jar
|
// NewTester will create a new testing client with an attached cookie jar
|
||||||
|
@ -78,9 +79,29 @@ func NewTester(t testing.TB) *Tester {
|
||||||
},
|
},
|
||||||
configLoaded: false,
|
configLoaded: false,
|
||||||
t: t,
|
t: t,
|
||||||
|
config: Default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithDefaultOverrides this will override the default test configuration with the provided values.
|
||||||
|
func (tc *Tester) WithDefaultOverrides(overrides Config) *Tester {
|
||||||
|
if overrides.AdminPort != 0 {
|
||||||
|
tc.config.AdminPort = overrides.AdminPort
|
||||||
|
}
|
||||||
|
if len(overrides.Certificates) > 0 {
|
||||||
|
tc.config.Certificates = overrides.Certificates
|
||||||
|
}
|
||||||
|
if overrides.TestRequestTimeout != 0 {
|
||||||
|
tc.config.TestRequestTimeout = overrides.TestRequestTimeout
|
||||||
|
tc.Client.Timeout = overrides.TestRequestTimeout
|
||||||
|
}
|
||||||
|
if overrides.LoadRequestTimeout != 0 {
|
||||||
|
tc.config.LoadRequestTimeout = overrides.LoadRequestTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return tc
|
||||||
|
}
|
||||||
|
|
||||||
type configLoadError struct {
|
type configLoadError struct {
|
||||||
Response string
|
Response string
|
||||||
}
|
}
|
||||||
|
@ -113,7 +134,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := validateTestPrerequisites(tc.t)
|
err := validateTestPrerequisites(tc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
||||||
return nil
|
return nil
|
||||||
|
@ -121,7 +142,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
||||||
|
|
||||||
tc.t.Cleanup(func() {
|
tc.t.Cleanup(func() {
|
||||||
if tc.t.Failed() && tc.configLoaded {
|
if tc.t.Failed() && tc.configLoaded {
|
||||||
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Log("unable to read the current config")
|
tc.t.Log("unable to read the current config")
|
||||||
return
|
return
|
||||||
|
@ -151,10 +172,10 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
||||||
tc.t.Logf("After: %s", rawConfig)
|
tc.t.Logf("After: %s", rawConfig)
|
||||||
}
|
}
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: Default.LoadRequestTimeout,
|
Timeout: tc.config.LoadRequestTimeout,
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig))
|
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", tc.config.AdminPort), strings.NewReader(rawConfig))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("failed to create request. %s", err)
|
tc.t.Errorf("failed to create request. %s", err)
|
||||||
return err
|
return err
|
||||||
|
@ -205,11 +226,11 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: Default.LoadRequestTimeout,
|
Timeout: tc.config.LoadRequestTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchConfig := func(client *http.Client) any {
|
fetchConfig := func(client *http.Client) any {
|
||||||
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -237,30 +258,30 @@ func (tc *Tester) ensureConfigRunning(rawConfig string, configType string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
const initConfig = `{
|
const initConfig = `{
|
||||||
admin localhost:2999
|
admin localhost:%d
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// validateTestPrerequisites ensures the certificates are available in the
|
// validateTestPrerequisites ensures the certificates are available in the
|
||||||
// designated path and Caddy sub-process is running.
|
// designated path and Caddy sub-process is running.
|
||||||
func validateTestPrerequisites(t testing.TB) error {
|
func validateTestPrerequisites(tc *Tester) error {
|
||||||
// check certificates are found
|
// check certificates are found
|
||||||
for _, certName := range Default.Certificates {
|
for _, certName := range tc.config.Certificates {
|
||||||
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
|
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
|
||||||
return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
|
return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCaddyAdminRunning() != nil {
|
if isCaddyAdminRunning(tc) != nil {
|
||||||
// setup the init config file, and set the cleanup afterwards
|
// setup the init config file, and set the cleanup afterwards
|
||||||
f, err := os.CreateTemp("", "")
|
f, err := os.CreateTemp("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.Cleanup(func() {
|
tc.t.Cleanup(func() {
|
||||||
os.Remove(f.Name())
|
os.Remove(f.Name())
|
||||||
})
|
})
|
||||||
if _, err := f.WriteString(initConfig); err != nil {
|
if _, err := fmt.Fprintf(f, initConfig, tc.config.AdminPort); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,23 +292,23 @@ func validateTestPrerequisites(t testing.TB) error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// wait for caddy to start serving the initial config
|
// wait for caddy to start serving the initial config
|
||||||
for retries := 10; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
|
for retries := 10; retries > 0 && isCaddyAdminRunning(tc) != nil; retries-- {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// one more time to return the error
|
// one more time to return the error
|
||||||
return isCaddyAdminRunning()
|
return isCaddyAdminRunning(tc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCaddyAdminRunning() error {
|
func isCaddyAdminRunning(tc *Tester) error {
|
||||||
// assert that caddy is running
|
// assert that caddy is running
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: Default.LoadRequestTimeout,
|
Timeout: tc.config.LoadRequestTimeout,
|
||||||
}
|
}
|
||||||
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", tc.config.AdminPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", Default.AdminPort)
|
return fmt.Errorf("caddy integration test caddy server not running. Expected to be listening on localhost:%d", tc.config.AdminPort)
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
|
|
|
@ -12,13 +12,14 @@ 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
|
||||||
|
|
|
@ -9,11 +9,12 @@ 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) {
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
{
|
||||||
|
acme_dns mock foo
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
respond "Hello World"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Hello World",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"argument": "foo",
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
dns mock
|
||||||
|
acme_dns
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
acme_dns
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
respond "Hello World"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
acme_dns specified without DNS provider config, but no provider specified with 'dns' global option
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
pki {
|
||||||
|
ca custom-ca {
|
||||||
|
name "Custom CA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acme.example.com {
|
||||||
|
acme_server {
|
||||||
|
ca custom-ca
|
||||||
|
allow {
|
||||||
|
domains host-1.internal.example.com host-2.internal.example.com
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"acme.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"ca": "custom-ca",
|
||||||
|
"handler": "acme_server",
|
||||||
|
"policy": {
|
||||||
|
"allow": {
|
||||||
|
"domains": [
|
||||||
|
"host-1.internal.example.com",
|
||||||
|
"host-2.internal.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pki": {
|
||||||
|
"certificate_authorities": {
|
||||||
|
"custom-ca": {
|
||||||
|
"name": "Custom CA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
pki {
|
||||||
|
ca custom-ca {
|
||||||
|
name "Custom CA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acme.example.com {
|
||||||
|
acme_server {
|
||||||
|
ca custom-ca
|
||||||
|
allow {
|
||||||
|
domains host-1.internal.example.com host-2.internal.example.com
|
||||||
|
}
|
||||||
|
deny {
|
||||||
|
domains dc.internal.example.com
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"acme.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"ca": "custom-ca",
|
||||||
|
"handler": "acme_server",
|
||||||
|
"policy": {
|
||||||
|
"allow": {
|
||||||
|
"domains": [
|
||||||
|
"host-1.internal.example.com",
|
||||||
|
"host-2.internal.example.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"deny": {
|
||||||
|
"domains": [
|
||||||
|
"dc.internal.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pki": {
|
||||||
|
"certificate_authorities": {
|
||||||
|
"custom-ca": {
|
||||||
|
"name": "Custom CA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
pki {
|
||||||
|
ca custom-ca {
|
||||||
|
name "Custom CA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acme.example.com {
|
||||||
|
acme_server {
|
||||||
|
ca custom-ca
|
||||||
|
deny {
|
||||||
|
domains dc.internal.example.com
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"acme.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"ca": "custom-ca",
|
||||||
|
"handler": "acme_server",
|
||||||
|
"policy": {
|
||||||
|
"deny": {
|
||||||
|
"domains": [
|
||||||
|
"dc.internal.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pki": {
|
||||||
|
"certificate_authorities": {
|
||||||
|
"custom-ca": {
|
||||||
|
"name": "Custom CA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
example.com
|
||||||
|
handle {
|
||||||
|
respond "one"
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com
|
||||||
|
handle {
|
||||||
|
respond "two"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
Caddyfile:6: unrecognized directive: example.com
|
||||||
|
Did you mean to define a second site? If so, you must use curly braces around each site to separate their configurations.
|
|
@ -0,0 +1,9 @@
|
||||||
|
:8080 {
|
||||||
|
respond "one"
|
||||||
|
}
|
||||||
|
|
||||||
|
:8080 {
|
||||||
|
respond "two"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
ambiguous site definition: :8080
|
|
@ -1,109 +0,0 @@
|
||||||
{
|
|
||||||
auto_https prefer_wildcard
|
|
||||||
}
|
|
||||||
|
|
||||||
*.example.com {
|
|
||||||
tls {
|
|
||||||
dns mock
|
|
||||||
}
|
|
||||||
respond "fallback"
|
|
||||||
}
|
|
||||||
|
|
||||||
foo.example.com {
|
|
||||||
respond "foo"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"foo.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "foo",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"*.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "fallback",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"automatic_https": {
|
|
||||||
"skip_certificates": [
|
|
||||||
"foo.example.com"
|
|
||||||
],
|
|
||||||
"prefer_wildcard": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tls": {
|
|
||||||
"automation": {
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"subjects": [
|
|
||||||
"*.example.com"
|
|
||||||
],
|
|
||||||
"issuers": [
|
|
||||||
{
|
|
||||||
"challenges": {
|
|
||||||
"dns": {
|
|
||||||
"provider": {
|
|
||||||
"name": "mock"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"module": "acme"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
{
|
|
||||||
auto_https prefer_wildcard
|
|
||||||
}
|
|
||||||
|
|
||||||
# Covers two domains
|
|
||||||
*.one.example.com {
|
|
||||||
tls {
|
|
||||||
dns mock
|
|
||||||
}
|
|
||||||
respond "one fallback"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Is covered, should not get its own AP
|
|
||||||
foo.one.example.com {
|
|
||||||
respond "foo one"
|
|
||||||
}
|
|
||||||
|
|
||||||
# This one has its own tls config so it doesn't get covered (escape hatch)
|
|
||||||
bar.one.example.com {
|
|
||||||
respond "bar one"
|
|
||||||
tls bar@bar.com
|
|
||||||
}
|
|
||||||
|
|
||||||
# Covers nothing but AP gets consolidated with the first
|
|
||||||
*.two.example.com {
|
|
||||||
tls {
|
|
||||||
dns mock
|
|
||||||
}
|
|
||||||
respond "two fallback"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Is HTTP so it should not cover
|
|
||||||
http://*.three.example.com {
|
|
||||||
respond "three fallback"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Has no wildcard coverage so it gets an AP
|
|
||||||
foo.three.example.com {
|
|
||||||
respond "foo three"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"foo.three.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "foo three",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"foo.one.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "foo one",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"bar.one.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "bar one",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"*.one.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "one fallback",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"*.two.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "two fallback",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"automatic_https": {
|
|
||||||
"skip_certificates": [
|
|
||||||
"foo.one.example.com",
|
|
||||||
"bar.one.example.com"
|
|
||||||
],
|
|
||||||
"prefer_wildcard": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"srv1": {
|
|
||||||
"listen": [
|
|
||||||
":80"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"*.three.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "three fallback",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"automatic_https": {
|
|
||||||
"prefer_wildcard": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tls": {
|
|
||||||
"automation": {
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"subjects": [
|
|
||||||
"foo.three.example.com"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"subjects": [
|
|
||||||
"bar.one.example.com"
|
|
||||||
],
|
|
||||||
"issuers": [
|
|
||||||
{
|
|
||||||
"email": "bar@bar.com",
|
|
||||||
"module": "acme"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ca": "https://acme.zerossl.com/v2/DV90",
|
|
||||||
"email": "bar@bar.com",
|
|
||||||
"module": "acme"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"subjects": [
|
|
||||||
"*.one.example.com",
|
|
||||||
"*.two.example.com"
|
|
||||||
],
|
|
||||||
"issuers": [
|
|
||||||
{
|
|
||||||
"challenges": {
|
|
||||||
"dns": {
|
|
||||||
"provider": {
|
|
||||||
"name": "mock"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"module": "acme"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
handle
|
||||||
|
|
||||||
|
respond "should not work"
|
||||||
|
----------
|
||||||
|
Caddyfile:1: parsed 'handle' as a site address, but it is a known directive; directives must appear in a site block
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
servers {
|
||||||
|
srv0 {
|
||||||
|
listen :8080
|
||||||
|
}
|
||||||
|
srv1 {
|
||||||
|
listen :8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
parsing caddyfile tokens for 'servers': unrecognized servers option 'srv0', at Caddyfile:3
|
|
@ -101,6 +101,11 @@ example.com {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
|
@ -126,6 +131,10 @@ example.com {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"terminal": true
|
"terminal": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,11 @@ bar.localhost {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -168,6 +173,10 @@ bar.localhost {
|
||||||
"body": "404 or 410 error",
|
"body": "404 or 410 error",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
@ -175,12 +184,21 @@ bar.localhost {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"body": "Error In range [500 .. 599]",
|
"body": "Error In range [500 .. 599]",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
@ -202,6 +220,11 @@ bar.localhost {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -211,6 +234,10 @@ bar.localhost {
|
||||||
"body": "404 or 410 error from second site",
|
"body": "404 or 410 error from second site",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
@ -218,12 +245,21 @@ bar.localhost {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"body": "Error In range [500 .. 599] from second site",
|
"body": "Error In range [500 .. 599] from second site",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -90,6 +90,11 @@ localhost:3010 {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -99,6 +104,10 @@ localhost:3010 {
|
||||||
"body": "Error in the [400 .. 499] range",
|
"body": "Error in the [400 .. 499] range",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -110,6 +110,11 @@ localhost:2099 {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -119,6 +124,10 @@ localhost:2099 {
|
||||||
"body": "Error in the [400 .. 499] range",
|
"body": "Error in the [400 .. 499] range",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
@ -126,12 +135,21 @@ localhost:2099 {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"body": "Error code is equal to 500 or in the [300..399] range",
|
"body": "Error code is equal to 500 or in the [300..399] range",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -90,6 +90,11 @@ localhost:3010 {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -99,6 +104,10 @@ localhost:3010 {
|
||||||
"body": "404 or 410 error",
|
"body": "404 or 410 error",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -110,6 +110,11 @@ localhost:2099 {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handle": [
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -119,6 +124,10 @@ localhost:2099 {
|
||||||
"body": "Error in the [400 .. 499] range",
|
"body": "Error in the [400 .. 499] range",
|
||||||
"handler": "static_response"
|
"handler": "static_response"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
{
|
{
|
||||||
|
@ -126,6 +135,11 @@ localhost:2099 {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
|
@ -136,6 +150,10 @@ localhost:2099 {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"terminal": true
|
"terminal": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,260 @@
|
||||||
|
{
|
||||||
|
http_port 2099
|
||||||
|
}
|
||||||
|
localhost:2099 {
|
||||||
|
root * /var/www/
|
||||||
|
file_server
|
||||||
|
|
||||||
|
handle_errors 404 {
|
||||||
|
handle /en/* {
|
||||||
|
respond "not found" 404
|
||||||
|
}
|
||||||
|
handle /es/* {
|
||||||
|
respond "no encontrado"
|
||||||
|
}
|
||||||
|
handle {
|
||||||
|
respond "default not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle_errors {
|
||||||
|
handle /en/* {
|
||||||
|
respond "English error"
|
||||||
|
}
|
||||||
|
handle /es/* {
|
||||||
|
respond "Spanish error"
|
||||||
|
}
|
||||||
|
handle {
|
||||||
|
respond "Default error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"http_port": 2099,
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":2099"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "vars",
|
||||||
|
"root": "/var/www/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handler": "file_server",
|
||||||
|
"hide": [
|
||||||
|
"./Caddyfile"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": {
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "not found",
|
||||||
|
"handler": "static_response",
|
||||||
|
"status_code": 404
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"/en/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "no encontrado",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"/es/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "default not found",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"expression": "{http.error.status_code} in [404]"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"group": "group8",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "English error",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"/en/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group8",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Spanish error",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"/es/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group8",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Default error",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -31,9 +31,6 @@ example.com
|
||||||
"automation": {
|
"automation": {
|
||||||
"policies": [
|
"policies": [
|
||||||
{
|
{
|
||||||
"subjects": [
|
|
||||||
"example.com"
|
|
||||||
],
|
|
||||||
"issuers": [
|
"issuers": [
|
||||||
{
|
{
|
||||||
"module": "acme",
|
"module": "acme",
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
trusted_proxies static private_ranges
|
trusted_proxies static private_ranges
|
||||||
client_ip_headers Custom-Real-Client-IP X-Forwarded-For
|
client_ip_headers Custom-Real-Client-IP X-Forwarded-For
|
||||||
client_ip_headers A-Third-One
|
client_ip_headers A-Third-One
|
||||||
|
keepalive_interval 20s
|
||||||
|
keepalive_idle 20s
|
||||||
|
keepalive_count 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +48,9 @@ foo.com {
|
||||||
"read_header_timeout": 30000000000,
|
"read_header_timeout": 30000000000,
|
||||||
"write_timeout": 30000000000,
|
"write_timeout": 30000000000,
|
||||||
"idle_timeout": 30000000000,
|
"idle_timeout": 30000000000,
|
||||||
|
"keepalive_interval": 20000000000,
|
||||||
|
"keepalive_idle": 20000000000,
|
||||||
|
"keepalive_count": 10,
|
||||||
"max_header_bytes": 100000000,
|
"max_header_bytes": 100000000,
|
||||||
"enable_full_duplex": true,
|
"enable_full_duplex": true,
|
||||||
"routes": [
|
"routes": [
|
||||||
|
|
|
@ -12,10 +12,14 @@
|
||||||
@images path /images/*
|
@images path /images/*
|
||||||
header @images {
|
header @images {
|
||||||
Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
|
Cache-Control "public, max-age=3600, stale-while-revalidate=86400"
|
||||||
|
match {
|
||||||
|
status 200
|
||||||
|
}
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
+Link "Foo"
|
+Link "Foo"
|
||||||
+Link "Bar"
|
+Link "Bar"
|
||||||
|
match status 200
|
||||||
}
|
}
|
||||||
header >Set Defer
|
header >Set Defer
|
||||||
header >Replace Deferred Replacement
|
header >Replace Deferred Replacement
|
||||||
|
@ -42,6 +46,11 @@
|
||||||
{
|
{
|
||||||
"handler": "headers",
|
"handler": "headers",
|
||||||
"response": {
|
"response": {
|
||||||
|
"require": {
|
||||||
|
"status_code": [
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
"set": {
|
"set": {
|
||||||
"Cache-Control": [
|
"Cache-Control": [
|
||||||
"public, max-age=3600, stale-while-revalidate=86400"
|
"public, max-age=3600, stale-while-revalidate=86400"
|
||||||
|
@ -136,6 +145,11 @@
|
||||||
"Foo",
|
"Foo",
|
||||||
"Bar"
|
"Bar"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"status_code": [
|
||||||
|
200
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
:80 {
|
||||||
|
header Test-Static ":443" "STATIC-WORKS"
|
||||||
|
header Test-Dynamic ":{http.request.local.port}" "DYNAMIC-WORKS"
|
||||||
|
header Test-Complex "port-{http.request.local.port}-end" "COMPLEX-{http.request.method}"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"response": {
|
||||||
|
"replace": {
|
||||||
|
"Test-Static": [
|
||||||
|
{
|
||||||
|
"replace": "STATIC-WORKS",
|
||||||
|
"search_regexp": ":443"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"response": {
|
||||||
|
"replace": {
|
||||||
|
"Test-Dynamic": [
|
||||||
|
{
|
||||||
|
"replace": "DYNAMIC-WORKS",
|
||||||
|
"search_regexp": ":{http.request.local.port}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"response": {
|
||||||
|
"replace": {
|
||||||
|
"Test-Complex": [
|
||||||
|
{
|
||||||
|
"replace": "COMPLEX-{http.request.method}",
|
||||||
|
"search_regexp": "port-{http.request.local.port}-end"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
:80
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond <<END
|
||||||
|
line1
|
||||||
|
line2
|
||||||
|
END
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": " line1\n line2",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
:80
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond <<EOF
|
||||||
|
Hello
|
||||||
|
# missing EOF marker
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
mismatched leading whitespace in heredoc <<EOF on line #5 [ Hello], expected whitespace [# missing ] to match the closing marker
|
|
@ -0,0 +1,9 @@
|
||||||
|
:80
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond <<END!
|
||||||
|
Hello
|
||||||
|
END!
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
heredoc marker on line #4 must contain only alpha-numeric characters, dashes and underscores; got 'END!'
|
|
@ -0,0 +1,10 @@
|
||||||
|
:80
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond <<END
|
||||||
|
line1
|
||||||
|
line2
|
||||||
|
END
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
mismatched leading whitespace in heredoc <<END on line #5 [ line1], expected whitespace [ ] to match the closing marker
|
|
@ -0,0 +1,9 @@
|
||||||
|
:80
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond <<
|
||||||
|
Hello
|
||||||
|
END
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
parsing caddyfile tokens for 'handle': unrecognized directive: Hello - are you sure your Caddyfile structure (nesting and braces) is correct?, at Caddyfile:7
|
|
@ -0,0 +1,9 @@
|
||||||
|
:80
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond <<<END
|
||||||
|
Hello
|
||||||
|
END
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
too many '<' for heredoc on line #4; only use two, for example <<END
|
|
@ -0,0 +1,13 @@
|
||||||
|
(site) {
|
||||||
|
http://{args[0]} https://{args[0]} {
|
||||||
|
{block}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
import site test.domain {
|
||||||
|
{
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
anonymous blocks are not supported
|
|
@ -0,0 +1,57 @@
|
||||||
|
(snippet) {
|
||||||
|
header {
|
||||||
|
reverse_proxy localhost:3000
|
||||||
|
{block}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
import snippet
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"response": {
|
||||||
|
"set": {
|
||||||
|
"Reverse_proxy": [
|
||||||
|
"localhost:3000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
(snippet) {
|
||||||
|
header {
|
||||||
|
reverse_proxy localhost:3000
|
||||||
|
{blocks.content_type}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
import snippet
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "headers",
|
||||||
|
"response": {
|
||||||
|
"set": {
|
||||||
|
"Reverse_proxy": [
|
||||||
|
"localhost:3000"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
(site) {
|
||||||
|
https://{args[0]} {
|
||||||
|
{block}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import site test.domain {
|
||||||
|
reverse_proxy http://192.168.1.1:8080 {
|
||||||
|
header_up Host {host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"test.domain"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"headers": {
|
||||||
|
"request": {
|
||||||
|
"set": {
|
||||||
|
"Host": [
|
||||||
|
"{http.request.host}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "192.168.1.1:8080"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
(import1) {
|
||||||
|
import import2
|
||||||
|
}
|
||||||
|
|
||||||
|
(import2) {
|
||||||
|
import import1
|
||||||
|
}
|
||||||
|
|
||||||
|
import import1
|
||||||
|
|
||||||
|
----------
|
||||||
|
a cycle of imports exists between Caddyfile:import2 and Caddyfile:import1
|
|
@ -0,0 +1,5 @@
|
||||||
|
example.com {
|
||||||
|
invoke foo
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
cannot invoke named route 'foo', which was not defined
|
|
@ -0,0 +1,95 @@
|
||||||
|
:80
|
||||||
|
|
||||||
|
log {
|
||||||
|
output stdout
|
||||||
|
format filter {
|
||||||
|
wrap console
|
||||||
|
|
||||||
|
# Multiple regexp filters for the same field - this should work now!
|
||||||
|
request>headers>Authorization regexp "Bearer\s+([A-Za-z0-9_-]+)" "Bearer [REDACTED]"
|
||||||
|
request>headers>Authorization regexp "Basic\s+([A-Za-z0-9+/=]+)" "Basic [REDACTED]"
|
||||||
|
request>headers>Authorization regexp "token=([^&\s]+)" "token=[REDACTED]"
|
||||||
|
|
||||||
|
# Single regexp filter - this should continue to work as before
|
||||||
|
request>headers>Cookie regexp "sessionid=[^;]+" "sessionid=[REDACTED]"
|
||||||
|
|
||||||
|
# Mixed filters (non-regexp) - these should work normally
|
||||||
|
request>headers>Server delete
|
||||||
|
request>remote_ip ip_mask {
|
||||||
|
ipv4 24
|
||||||
|
ipv6 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"logs": {
|
||||||
|
"default": {
|
||||||
|
"exclude": [
|
||||||
|
"http.log.access.log0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"log0": {
|
||||||
|
"writer": {
|
||||||
|
"output": "stdout"
|
||||||
|
},
|
||||||
|
"encoder": {
|
||||||
|
"fields": {
|
||||||
|
"request\u003eheaders\u003eAuthorization": {
|
||||||
|
"filter": "multi_regexp",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"regexp": "Bearer\\s+([A-Za-z0-9_-]+)",
|
||||||
|
"value": "Bearer [REDACTED]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regexp": "Basic\\s+([A-Za-z0-9+/=]+)",
|
||||||
|
"value": "Basic [REDACTED]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regexp": "token=([^\u0026\\s]+)",
|
||||||
|
"value": "token=[REDACTED]"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"request\u003eheaders\u003eCookie": {
|
||||||
|
"filter": "regexp",
|
||||||
|
"regexp": "sessionid=[^;]+",
|
||||||
|
"value": "sessionid=[REDACTED]"
|
||||||
|
},
|
||||||
|
"request\u003eheaders\u003eServer": {
|
||||||
|
"filter": "delete"
|
||||||
|
},
|
||||||
|
"request\u003eremote_ip": {
|
||||||
|
"filter": "ip_mask",
|
||||||
|
"ipv4_cidr": 24,
|
||||||
|
"ipv6_cidr": 32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"format": "filter",
|
||||||
|
"wrap": {
|
||||||
|
"format": "console"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"http.log.access.log0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"logs": {
|
||||||
|
"default_logger_name": "log0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
@foo {
|
||||||
|
path /foo
|
||||||
|
}
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond "should not work"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
request matchers may not be defined globally, they must be in a site block; found @foo, at Caddyfile:1
|
|
@ -0,0 +1,41 @@
|
||||||
|
:8884
|
||||||
|
reverse_proxy 127.0.0.1:65535 {
|
||||||
|
transport http {
|
||||||
|
forward_proxy_url http://localhost:8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8884"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"transport": {
|
||||||
|
"network_proxy": {
|
||||||
|
"from": "url",
|
||||||
|
"url": "http://localhost:8080"
|
||||||
|
},
|
||||||
|
"protocol": "http"
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "127.0.0.1:65535"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
:8884
|
||||||
|
reverse_proxy 127.0.0.1:65535 {
|
||||||
|
transport http {
|
||||||
|
network_proxy none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8884"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"transport": {
|
||||||
|
"network_proxy": {
|
||||||
|
"from": "none"
|
||||||
|
},
|
||||||
|
"protocol": "http"
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "127.0.0.1:65535"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
:8884
|
||||||
|
reverse_proxy 127.0.0.1:65535 {
|
||||||
|
transport http {
|
||||||
|
network_proxy url http://localhost:8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8884"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"transport": {
|
||||||
|
"network_proxy": {
|
||||||
|
"from": "url",
|
||||||
|
"url": "http://localhost:8080"
|
||||||
|
},
|
||||||
|
"protocol": "http"
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "127.0.0.1:65535"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
servers {
|
||||||
|
trusted_proxies_unix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
reverse_proxy https://local:8080
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"transport": {
|
||||||
|
"protocol": "http",
|
||||||
|
"tls": {}
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "local:8080"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trusted_proxies_unix": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
:70000
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond "should not work"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
port 70000 is out of range
|
|
@ -0,0 +1,7 @@
|
||||||
|
:-1
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond "should not work"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
port -1 is out of range
|
|
@ -0,0 +1,7 @@
|
||||||
|
foo://example.com
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond "hello"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
unsupported URL scheme foo://
|
|
@ -0,0 +1,7 @@
|
||||||
|
wss://example.com:70000
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond "should not work"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
port 70000 is out of range
|
|
@ -0,0 +1,7 @@
|
||||||
|
wss://example.com
|
||||||
|
|
||||||
|
handle {
|
||||||
|
respond "hello"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
the scheme wss:// is only supported in browsers; use https:// instead
|
|
@ -131,13 +131,7 @@ shadowed.example.com {
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"sni": [
|
"sni": [
|
||||||
"automated1.example.com"
|
"automated1.example.com",
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"sni": [
|
|
||||||
"automated2.example.com"
|
"automated2.example.com"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
localhost
|
||||||
|
|
||||||
|
respond "hello from localhost"
|
||||||
|
tls {
|
||||||
|
client_auth {
|
||||||
|
mode request
|
||||||
|
trust_pool inline {
|
||||||
|
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||||
|
}
|
||||||
|
verifier leaf {
|
||||||
|
file ../caddy.ca.cer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "hello from localhost",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"client_authentication": {
|
||||||
|
"ca": {
|
||||||
|
"provider": "inline",
|
||||||
|
"trusted_ca_certs": [
|
||||||
|
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verifiers": [
|
||||||
|
{
|
||||||
|
"leaf_certs_loaders": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"../caddy.ca.cer"
|
||||||
|
],
|
||||||
|
"loader": "file"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"verifier": "leaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "request"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
localhost
|
||||||
|
|
||||||
|
respond "hello from localhost"
|
||||||
|
tls {
|
||||||
|
client_auth {
|
||||||
|
mode request
|
||||||
|
trust_pool inline {
|
||||||
|
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||||
|
}
|
||||||
|
verifier leaf file ../caddy.ca.cer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "hello from localhost",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"client_authentication": {
|
||||||
|
"ca": {
|
||||||
|
"provider": "inline",
|
||||||
|
"trusted_ca_certs": [
|
||||||
|
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verifiers": [
|
||||||
|
{
|
||||||
|
"leaf_certs_loaders": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"../caddy.ca.cer"
|
||||||
|
],
|
||||||
|
"loader": "file"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"verifier": "leaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "request"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
localhost
|
||||||
|
|
||||||
|
respond "hello from localhost"
|
||||||
|
tls {
|
||||||
|
client_auth {
|
||||||
|
mode request
|
||||||
|
trust_pool inline {
|
||||||
|
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||||
|
}
|
||||||
|
verifier leaf {
|
||||||
|
file ../caddy.ca.cer
|
||||||
|
file ../caddy.ca.cer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "hello from localhost",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"client_authentication": {
|
||||||
|
"ca": {
|
||||||
|
"provider": "inline",
|
||||||
|
"trusted_ca_certs": [
|
||||||
|
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verifiers": [
|
||||||
|
{
|
||||||
|
"leaf_certs_loaders": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"../caddy.ca.cer"
|
||||||
|
],
|
||||||
|
"loader": "file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"../caddy.ca.cer"
|
||||||
|
],
|
||||||
|
"loader": "file"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"verifier": "leaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "request"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
localhost
|
||||||
|
|
||||||
|
respond "hello from localhost"
|
||||||
|
tls {
|
||||||
|
client_auth {
|
||||||
|
mode request
|
||||||
|
trust_pool inline {
|
||||||
|
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||||
|
}
|
||||||
|
verifier leaf {
|
||||||
|
folder ../
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "hello from localhost",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"client_authentication": {
|
||||||
|
"ca": {
|
||||||
|
"provider": "inline",
|
||||||
|
"trusted_ca_certs": [
|
||||||
|
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verifiers": [
|
||||||
|
{
|
||||||
|
"leaf_certs_loaders": [
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
"../"
|
||||||
|
],
|
||||||
|
"loader": "folder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"verifier": "leaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "request"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
localhost
|
||||||
|
|
||||||
|
respond "hello from localhost"
|
||||||
|
tls {
|
||||||
|
client_auth {
|
||||||
|
mode request
|
||||||
|
trust_pool inline {
|
||||||
|
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||||
|
}
|
||||||
|
verifier leaf folder ../
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "hello from localhost",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"client_authentication": {
|
||||||
|
"ca": {
|
||||||
|
"provider": "inline",
|
||||||
|
"trusted_ca_certs": [
|
||||||
|
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verifiers": [
|
||||||
|
{
|
||||||
|
"leaf_certs_loaders": [
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
"../"
|
||||||
|
],
|
||||||
|
"loader": "folder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"verifier": "leaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "request"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
localhost
|
||||||
|
|
||||||
|
respond "hello from localhost"
|
||||||
|
tls {
|
||||||
|
client_auth {
|
||||||
|
mode request
|
||||||
|
trust_pool inline {
|
||||||
|
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||||
|
}
|
||||||
|
verifier leaf {
|
||||||
|
folder ../
|
||||||
|
folder ../
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "hello from localhost",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tls_connection_policies": [
|
||||||
|
{
|
||||||
|
"match": {
|
||||||
|
"sni": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"client_authentication": {
|
||||||
|
"ca": {
|
||||||
|
"provider": "inline",
|
||||||
|
"trusted_ca_certs": [
|
||||||
|
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"verifiers": [
|
||||||
|
{
|
||||||
|
"leaf_certs_loaders": [
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
"../"
|
||||||
|
],
|
||||||
|
"loader": "folder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
"../"
|
||||||
|
],
|
||||||
|
"loader": "folder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"verifier": "leaf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "request"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
localhost
|
||||||
|
|
||||||
|
tls {
|
||||||
|
propagation_delay 10s
|
||||||
|
dns_ttl 5m
|
||||||
|
}
|
||||||
|
|
||||||
|
----------
|
||||||
|
parsing caddyfile tokens for 'tls': setting DNS challenge options [propagation_delay, dns_ttl] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option), at Caddyfile:6
|
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
acme_dns mock foo
|
||||||
|
}
|
||||||
|
|
||||||
|
localhost {
|
||||||
|
tls {
|
||||||
|
dns mock bar
|
||||||
|
resolvers 8.8.8.8 8.8.4.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"argument": "bar",
|
||||||
|
"name": "mock"
|
||||||
|
},
|
||||||
|
"resolvers": [
|
||||||
|
"8.8.8.8",
|
||||||
|
"8.8.4.4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"argument": "foo",
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
dns mock foo
|
||||||
|
}
|
||||||
|
|
||||||
|
localhost {
|
||||||
|
tls {
|
||||||
|
dns mock bar
|
||||||
|
resolvers 8.8.8.8 8.8.4.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"argument": "bar",
|
||||||
|
"name": "mock"
|
||||||
|
},
|
||||||
|
"resolvers": [
|
||||||
|
"8.8.8.8",
|
||||||
|
"8.8.4.4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"argument": "foo",
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
:443 {
|
||||||
|
tls {
|
||||||
|
propagation_timeout 30s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
parsing caddyfile tokens for 'tls': setting DNS challenge options [propagation_timeout] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option), at Caddyfile:4
|
|
@ -0,0 +1,7 @@
|
||||||
|
:443 {
|
||||||
|
tls {
|
||||||
|
propagation_delay 30s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
parsing caddyfile tokens for 'tls': setting DNS challenge options [propagation_delay] requires a DNS provider (set with the 'dns' subdirective or 'acme_dns' global option), at Caddyfile:4
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
acme_dns mock
|
||||||
|
}
|
||||||
|
|
||||||
|
localhost {
|
||||||
|
tls {
|
||||||
|
resolvers 8.8.8.8 8.8.4.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
},
|
||||||
|
"resolvers": [
|
||||||
|
"8.8.8.8",
|
||||||
|
"8.8.4.4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ localhost
|
||||||
|
|
||||||
respond "hello from localhost"
|
respond "hello from localhost"
|
||||||
tls {
|
tls {
|
||||||
|
dns mock
|
||||||
dns_ttl 5m10s
|
dns_ttl 5m10s
|
||||||
}
|
}
|
||||||
----------
|
----------
|
||||||
|
@ -54,6 +55,9 @@ tls {
|
||||||
{
|
{
|
||||||
"challenges": {
|
"challenges": {
|
||||||
"dns": {
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
},
|
||||||
"ttl": 310000000000
|
"ttl": 310000000000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue