mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-26 08:42:31 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08aee56c57 | |||
| aea4013a56 |
@@ -1,31 +0,0 @@
|
|||||||
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"'
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
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
|
|
||||||
+3
-5
@@ -18,7 +18,7 @@ A security report must demonstrate a security bug in the source code from this r
|
|||||||
|
|
||||||
Some security problems are the result of interplay between different components of the Web, rather than a vulnerability in the web server itself. Please only report vulnerabilities in the web server itself, as we cannot coerce the rest of the Web to be fixed (for example, we do not consider IP spoofing, BGP hijacks, or missing/misconfigured HTTP headers a vulnerability in the Caddy web server).
|
Some security problems are the result of interplay between different components of the Web, rather than a vulnerability in the web server itself. Please only report vulnerabilities in the web server itself, as we cannot coerce the rest of the Web to be fixed (for example, we do not consider IP spoofing, BGP hijacks, or missing/misconfigured HTTP headers a vulnerability in the Caddy web server).
|
||||||
|
|
||||||
Vulnerabilities caused by misconfigurations are out of scope. Yes, it is entirely possible to craft and use a configuration that is unsafe, just like with every other web server; we recommend against doing that. Similarly, external misconfigurations are out of scope. For example, an open or forwarded port from a public network to a Caddy instance intended to serve only internal clients is not a vulnerability in Caddy.
|
Vulnerabilities caused by misconfigurations are out of scope. Yes, it is entirely possible to craft and use a configuration that is unsafe, just like with every other web server; we recommend against doing that.
|
||||||
|
|
||||||
We do not accept reports if the steps imply or require a compromised system or third-party software, as we cannot control those. We expect that users secure their own systems and keep all their software patched. For example, if untrusted users are able to upload/write/host arbitrary files in the web root directory, it is NOT a security bug in Caddy if those files get served to clients; however, it _would_ be a valid report if a bug in Caddy's source code unintentionally gave unauthorized users the ability to upload unsafe files or delete files without relying on an unpatched system or piece of software.
|
We do not accept reports if the steps imply or require a compromised system or third-party software, as we cannot control those. We expect that users secure their own systems and keep all their software patched. For example, if untrusted users are able to upload/write/host arbitrary files in the web root directory, it is NOT a security bug in Caddy if those files get served to clients; however, it _would_ be a valid report if a bug in Caddy's source code unintentionally gave unauthorized users the ability to upload unsafe files or delete files without relying on an unpatched system or piece of software.
|
||||||
|
|
||||||
@@ -33,8 +33,6 @@ We get a lot of difficult reports that turn out to be invalid. Clear, obvious re
|
|||||||
|
|
||||||
First please ensure your report falls within the accepted scope of security bugs (above).
|
First please ensure your report falls within the accepted scope of security bugs (above).
|
||||||
|
|
||||||
**YOU MUST DISCLOSE THE USE OF LLMs ("AI") INVOLVED IN ANY WAY.** Whether you are using AI for discovery, as part of writing the report or its replies, and/or testing or validating proofs and changes, we require you to mention the extent of it. **FAILURE TO INCLUDE A DISCLOSURE MAY LEAD TO IMMEDIATE DISMISSAL OF YOUR REPORT AND POTENTIAL BLOCKLISTING.**
|
|
||||||
|
|
||||||
We'll need enough information to verify the bug and make a patch. To speed things up, please include:
|
We'll need enough information to verify the bug and make a patch. To speed things up, please include:
|
||||||
|
|
||||||
- Most minimal possible config (without redactions!)
|
- Most minimal possible config (without redactions!)
|
||||||
@@ -50,9 +48,9 @@ We consider publicly-registered domain names to be public information. This nece
|
|||||||
|
|
||||||
It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding.
|
It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, depending on available resources. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. (We get a lot of invalid reports.) Thank you for understanding.
|
||||||
|
|
||||||
When you are ready, please submit a [new private vulnerability report](https://github.com/caddyserver/caddy/security/advisories/new).
|
When you are ready, please email Matt Holt (the author) directly: matt at dyanim dot com.
|
||||||
|
|
||||||
Please don't encrypt the message. It only makes the process more complicated.
|
Please don't encrypt the email body. It only makes the process more complicated.
|
||||||
|
|
||||||
Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you.
|
Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you.
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,5 @@ 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"
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 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._
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
name: Release Proposal Approval Tracker
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_review:
|
|
||||||
types: [submitted, dismissed]
|
|
||||||
pull_request:
|
|
||||||
types: [labeled, unlabeled, synchronize, closed]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check-approvals:
|
|
||||||
name: Track Maintainer Approvals
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Only run on PRs with release-proposal label
|
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'release-proposal') && github.event.pull_request.state == 'open'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check approvals and update PR
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
env:
|
|
||||||
MAINTAINER_LOGINS: ${{ secrets.MAINTAINER_LOGINS }}
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const pr = context.payload.pull_request;
|
|
||||||
|
|
||||||
// Extract version from PR title (e.g., "Release Proposal: v1.2.3")
|
|
||||||
const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);
|
|
||||||
const commitMatch = pr.body.match(/\*\*Target Commit:\*\*\s*`([a-f0-9]+)`/);
|
|
||||||
|
|
||||||
if (!versionMatch || !commitMatch) {
|
|
||||||
console.log('Could not extract version from title or commit from body');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const version = versionMatch[1];
|
|
||||||
const targetCommit = commitMatch[1];
|
|
||||||
|
|
||||||
console.log(`Version: ${version}, Target Commit: ${targetCommit}`);
|
|
||||||
|
|
||||||
// Get all reviews
|
|
||||||
const reviews = await github.rest.pulls.listReviews({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
pull_number: pr.number
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get list of maintainers
|
|
||||||
const maintainerLoginsRaw = process.env.MAINTAINER_LOGINS || '';
|
|
||||||
const maintainerLogins = maintainerLoginsRaw
|
|
||||||
.split(/[,;]/)
|
|
||||||
.map(login => login.trim())
|
|
||||||
.filter(login => login.length > 0);
|
|
||||||
|
|
||||||
console.log(`Maintainer logins: ${maintainerLogins.join(', ')}`);
|
|
||||||
|
|
||||||
// Get the latest review from each user
|
|
||||||
const latestReviewsByUser = {};
|
|
||||||
reviews.data.forEach(review => {
|
|
||||||
const username = review.user.login;
|
|
||||||
if (!latestReviewsByUser[username] || new Date(review.submitted_at) > new Date(latestReviewsByUser[username].submitted_at)) {
|
|
||||||
latestReviewsByUser[username] = review;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Count approvals from maintainers
|
|
||||||
const maintainerApprovals = Object.entries(latestReviewsByUser)
|
|
||||||
.filter(([username, review]) =>
|
|
||||||
maintainerLogins.includes(username) &&
|
|
||||||
review.state === 'APPROVED'
|
|
||||||
)
|
|
||||||
.map(([username, review]) => username);
|
|
||||||
|
|
||||||
const approvalCount = maintainerApprovals.length;
|
|
||||||
console.log(`Found ${approvalCount} maintainer approvals from: ${maintainerApprovals.join(', ')}`);
|
|
||||||
|
|
||||||
// Get current labels
|
|
||||||
const currentLabels = pr.labels.map(label => label.name);
|
|
||||||
const hasApprovedLabel = currentLabels.includes('approved');
|
|
||||||
const hasAwaitingApprovalLabel = currentLabels.includes('awaiting-approval');
|
|
||||||
|
|
||||||
if (approvalCount >= 2 && !hasApprovedLabel) {
|
|
||||||
console.log('✅ Quorum reached! Updating PR...');
|
|
||||||
|
|
||||||
// Remove awaiting-approval label if present
|
|
||||||
if (hasAwaitingApprovalLabel) {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
name: 'awaiting-approval'
|
|
||||||
}).catch(e => console.log('Label not found:', e.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add approved label
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
labels: ['approved']
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add comment with tagging instructions
|
|
||||||
const approversList = maintainerApprovals.map(u => `@${u}`).join(', ');
|
|
||||||
const commentBody = [
|
|
||||||
'## ✅ Approval Quorum Reached',
|
|
||||||
'',
|
|
||||||
`This release proposal has been approved by ${approvalCount} maintainers: ${approversList}`,
|
|
||||||
'',
|
|
||||||
'### Tagging Instructions',
|
|
||||||
'',
|
|
||||||
'A maintainer should now create and push the signed tag:',
|
|
||||||
'',
|
|
||||||
'```bash',
|
|
||||||
`git checkout ${targetCommit}`,
|
|
||||||
`git tag -s ${version} -m "Release ${version}"`,
|
|
||||||
`git push origin ${version}`,
|
|
||||||
`git checkout -`,
|
|
||||||
'```',
|
|
||||||
'',
|
|
||||||
'The release workflow will automatically start when the tag is pushed.'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
body: commentBody
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Posted tagging instructions');
|
|
||||||
} else if (approvalCount < 2 && hasApprovedLabel) {
|
|
||||||
console.log('⚠️ Approval count dropped below quorum, removing approved label');
|
|
||||||
|
|
||||||
// Remove approved label
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
name: 'approved'
|
|
||||||
}).catch(e => console.log('Label not found:', e.message));
|
|
||||||
|
|
||||||
// Add awaiting-approval label
|
|
||||||
if (!hasAwaitingApprovalLabel) {
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
labels: ['awaiting-approval']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(`⏳ Waiting for more approvals (${approvalCount}/2 required)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
handle-pr-closed:
|
|
||||||
name: Handle PR Closed Without Tag
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: |
|
|
||||||
contains(github.event.pull_request.labels.*.name, 'release-proposal') &&
|
|
||||||
github.event.action == 'closed' && !contains(github.event.pull_request.labels.*.name, 'released')
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Add cancelled label and comment
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const pr = context.payload.pull_request;
|
|
||||||
|
|
||||||
// Check if the release-in-progress label is present
|
|
||||||
const hasReleaseInProgress = pr.labels.some(label => label.name === 'release-in-progress');
|
|
||||||
|
|
||||||
if (hasReleaseInProgress) {
|
|
||||||
// PR was closed while release was in progress - this is unusual
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
body: '⚠️ **Warning:** This PR was closed while a release was in progress. This may indicate an error. Please verify the release status.'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// PR was closed before tag was created - this is normal cancellation
|
|
||||||
const versionMatch = pr.title.match(/Release Proposal:\s*(v[\d.]+(?:-[\w.]+)?)/);
|
|
||||||
const version = versionMatch ? versionMatch[1] : 'unknown';
|
|
||||||
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
body: `## 🚫 Release Proposal Cancelled\n\nThis release proposal for ${version} was closed without creating the tag.\n\nIf you want to proceed with this release later, you can create a new release proposal.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add cancelled label
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
labels: ['cancelled']
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove other workflow labels if present
|
|
||||||
const labelsToRemove = ['awaiting-approval', 'approved', 'release-in-progress'];
|
|
||||||
for (const label of labelsToRemove) {
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.number,
|
|
||||||
name: label
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Label ${label} not found or already removed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Added cancelled label and cleaned up workflow labels');
|
|
||||||
|
|
||||||
+16
-45
@@ -13,13 +13,9 @@ on:
|
|||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
|
|
||||||
# https://github.com/actions/setup-go/issues/491
|
# https://github.com/actions/setup-go/issues/491
|
||||||
GOTOOLCHAIN: local
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -31,13 +27,13 @@ jobs:
|
|||||||
- mac
|
- mac
|
||||||
- windows
|
- windows
|
||||||
go:
|
go:
|
||||||
- '1.26'
|
- '1.24'
|
||||||
|
|
||||||
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.26'
|
- go: '1.24'
|
||||||
GO_SEMVER: '~1.26.0'
|
GO_SEMVER: '~1.24.1'
|
||||||
|
|
||||||
# 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)
|
||||||
@@ -59,21 +55,13 @@ jobs:
|
|||||||
SUCCESS: 'True'
|
SUCCESS: 'True'
|
||||||
|
|
||||||
runs-on: ${{ matrix.OS_LABEL }}
|
runs-on: ${{ matrix.OS_LABEL }}
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
actions: write # to allow uploading artifacts and cache
|
|
||||||
steps:
|
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.GO_SEMVER }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@@ -111,7 +99,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
run: |
|
run: |
|
||||||
go build -trimpath -ldflags="-w -s" -v
|
go build -tags nobadger,nomysql,nopgx -trimpath -ldflags="-w -s" -v
|
||||||
|
|
||||||
- name: Smoke test Caddy
|
- name: Smoke test Caddy
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
@@ -120,7 +108,7 @@ jobs:
|
|||||||
./caddy stop
|
./caddy stop
|
||||||
|
|
||||||
- name: Publish Build Artifact
|
- name: Publish Build Artifact
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
|
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
|
||||||
path: ${{ matrix.CADDY_BIN_PATH }}
|
path: ${{ matrix.CADDY_BIN_PATH }}
|
||||||
@@ -134,7 +122,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 -v -coverprofile="cover-profile.out" -short -race ./...
|
go test -tags nobadger,nomysql,nopgx -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
|
||||||
@@ -154,21 +142,12 @@ jobs:
|
|||||||
|
|
||||||
s390x-test:
|
s390x-test:
|
||||||
name: test (s390x on IBM Z)
|
name: test (s390x on IBM Z)
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
|
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
|
||||||
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
allowed-endpoints: ci-s390x.caddyserver.com:22
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@v4
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
set +e
|
set +e
|
||||||
@@ -191,7 +170,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 -v ./...
|
CGO_ENABLED=0 go test -p 1 -tags nobadger,nomysql,nopgx -v ./...
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
if ((exit_code == 0)); then
|
if ((exit_code == 0)); then
|
||||||
break
|
break
|
||||||
@@ -215,33 +194,25 @@ jobs:
|
|||||||
|
|
||||||
goreleaser-check:
|
goreleaser-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
|
if: github.event.pull_request.head.repo.full_name == 'caddyserver/caddy' && github.actor != 'dependabot[bot]'
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
- uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: check
|
args: check
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: "~1.26"
|
go-version: "~1.24"
|
||||||
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@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
- uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: build --single-target --snapshot
|
args: build --single-target --snapshot
|
||||||
|
|||||||
@@ -11,14 +11,9 @@ on:
|
|||||||
- 2.*
|
- 2.*
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GOFLAGS: '-tags=nobadger,nomysql,nopgx'
|
|
||||||
CGO_ENABLED: '0'
|
|
||||||
# https://github.com/actions/setup-go/issues/491
|
# https://github.com/actions/setup-go/issues/491
|
||||||
GOTOOLCHAIN: local
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -36,30 +31,22 @@ jobs:
|
|||||||
- 'darwin'
|
- 'darwin'
|
||||||
- 'netbsd'
|
- 'netbsd'
|
||||||
go:
|
go:
|
||||||
- '1.26'
|
- '1.24'
|
||||||
|
|
||||||
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.26'
|
- go: '1.24'
|
||||||
GO_SEMVER: '~1.26.0'
|
GO_SEMVER: '~1.24.1'
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.GO_SEMVER }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
@@ -76,9 +63,11 @@ 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: go build -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
run: |
|
||||||
|
GOOS=$GOOS GOARCH=$GOARCH go build -tags=nobadger,nomysql,nopgx -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
||||||
|
|||||||
@@ -44,19 +44,14 @@ jobs:
|
|||||||
runs-on: ${{ matrix.OS_LABEL }}
|
runs-on: ${{ matrix.OS_LABEL }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
- uses: actions/checkout@v4
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
go-version: '~1.24'
|
||||||
|
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
||||||
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
|
||||||
with:
|
|
||||||
go-version: '~1.26'
|
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|
||||||
@@ -67,39 +62,10 @@ jobs:
|
|||||||
# only-new-issues: true
|
# only-new-issues: true
|
||||||
|
|
||||||
govulncheck:
|
govulncheck:
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
|
|
||||||
- name: govulncheck
|
- name: govulncheck
|
||||||
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
|
uses: golang/govulncheck-action@v1
|
||||||
with:
|
with:
|
||||||
go-version-input: '~1.26.0'
|
go-version-input: '~1.24.1'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
dependency-review:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@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 }}
|
|
||||||
|
|||||||
@@ -1,249 +0,0 @@
|
|||||||
name: Release Proposal
|
|
||||||
|
|
||||||
# This workflow creates a release proposal as a PR that requires approval from maintainers
|
|
||||||
# Triggered manually by maintainers when ready to prepare a release
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: 'Version to release (e.g., v2.8.0)'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
commit_hash:
|
|
||||||
description: 'Commit hash to release from'
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
create-proposal:
|
|
||||||
name: Create Release Proposal
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
||||||
with:
|
|
||||||
egress-policy: audit
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Trim and validate inputs
|
|
||||||
id: inputs
|
|
||||||
run: |
|
|
||||||
# Trim whitespace from inputs
|
|
||||||
VERSION=$(echo "${{ inputs.version }}" | xargs)
|
|
||||||
COMMIT_HASH=$(echo "${{ inputs.commit_hash }}" | xargs)
|
|
||||||
|
|
||||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
||||||
echo "commit_hash=$COMMIT_HASH" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Validate version format
|
|
||||||
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
|
|
||||||
echo "Error: Version must follow semver format (e.g., v2.8.0 or v2.8.0-beta.1)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Validate commit hash format
|
|
||||||
if [[ ! "$COMMIT_HASH" =~ ^[a-f0-9]{7,40}$ ]]; then
|
|
||||||
echo "Error: Commit hash must be a valid SHA (7-40 characters)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if commit exists
|
|
||||||
if ! git cat-file -e "$COMMIT_HASH"; then
|
|
||||||
echo "Error: Commit $COMMIT_HASH does not exist"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check if tag already exists
|
|
||||||
run: |
|
|
||||||
if git rev-parse "${{ steps.inputs.outputs.version }}" >/dev/null 2>&1; then
|
|
||||||
echo "Error: Tag ${{ steps.inputs.outputs.version }} already exists"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check for existing proposal PR
|
|
||||||
id: check_existing
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const version = '${{ steps.inputs.outputs.version }}';
|
|
||||||
|
|
||||||
// Search for existing open PRs with release-proposal label that match this version
|
|
||||||
const openPRs = await github.rest.pulls.list({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
state: 'open',
|
|
||||||
sort: 'updated',
|
|
||||||
direction: 'desc'
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingOpenPR = openPRs.data.find(pr =>
|
|
||||||
pr.title.includes(version) &&
|
|
||||||
pr.labels.some(label => label.name === 'release-proposal')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingOpenPR) {
|
|
||||||
const hasReleased = existingOpenPR.labels.some(label => label.name === 'released');
|
|
||||||
const hasReleaseInProgress = existingOpenPR.labels.some(label => label.name === 'release-in-progress');
|
|
||||||
|
|
||||||
if (hasReleased || hasReleaseInProgress) {
|
|
||||||
core.setFailed(`A release for ${version} is already in progress or completed: ${existingOpenPR.html_url}`);
|
|
||||||
} else {
|
|
||||||
core.setFailed(`An open release proposal already exists for ${version}: ${existingOpenPR.html_url}\n\nPlease use the existing PR or close it first.`);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for closed PRs with this version that were cancelled
|
|
||||||
const closedPRs = await github.rest.pulls.list({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
state: 'closed',
|
|
||||||
sort: 'updated',
|
|
||||||
direction: 'desc'
|
|
||||||
});
|
|
||||||
|
|
||||||
const cancelledPR = closedPRs.data.find(pr =>
|
|
||||||
pr.title.includes(version) &&
|
|
||||||
pr.labels.some(label => label.name === 'release-proposal') &&
|
|
||||||
pr.labels.some(label => label.name === 'cancelled')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cancelledPR) {
|
|
||||||
console.log(`Found previously cancelled proposal for ${version}: ${cancelledPR.html_url}`);
|
|
||||||
console.log('Creating new proposal to replace cancelled one...');
|
|
||||||
} else {
|
|
||||||
console.log(`No existing proposal found for ${version}, proceeding...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Generate changelog and create branch
|
|
||||||
id: setup
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.inputs.outputs.version }}"
|
|
||||||
COMMIT_HASH="${{ steps.inputs.outputs.commit_hash }}"
|
|
||||||
|
|
||||||
# Create a new branch for the release proposal
|
|
||||||
BRANCH_NAME="release_proposal-$VERSION"
|
|
||||||
git checkout -b "$BRANCH_NAME"
|
|
||||||
|
|
||||||
# Calculate how many commits behind HEAD
|
|
||||||
COMMITS_BEHIND=$(git rev-list --count ${COMMIT_HASH}..HEAD)
|
|
||||||
|
|
||||||
if [ "$COMMITS_BEHIND" -eq 0 ]; then
|
|
||||||
BEHIND_INFO="This is the latest commit (HEAD)"
|
|
||||||
else
|
|
||||||
BEHIND_INFO="This commit is **${COMMITS_BEHIND} commits behind HEAD**"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "commits_behind=$COMMITS_BEHIND" >> $GITHUB_OUTPUT
|
|
||||||
echo "behind_info=$BEHIND_INFO" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Get the last tag
|
|
||||||
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
if [ -z "$LAST_TAG" ]; then
|
|
||||||
echo "No previous tag found, generating full changelog"
|
|
||||||
COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse "$COMMIT_HASH")
|
|
||||||
else
|
|
||||||
echo "Generating changelog since $LAST_TAG"
|
|
||||||
COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse "${LAST_TAG}..$COMMIT_HASH")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Store changelog for PR body
|
|
||||||
CLEANSED_COMMITS=$(echo "$COMMITS" | sed 's/`/\\`/g')
|
|
||||||
echo "changelog<<EOF" >> $GITHUB_OUTPUT
|
|
||||||
echo "$CLEANSED_COMMITS" >> $GITHUB_OUTPUT
|
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Create empty commit for the PR
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git commit --allow-empty -m "Release proposal for $VERSION"
|
|
||||||
|
|
||||||
# Push the branch
|
|
||||||
git push origin "$BRANCH_NAME"
|
|
||||||
|
|
||||||
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Create release proposal PR
|
|
||||||
id: create_pr
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const changelog = `${{ steps.setup.outputs.changelog }}`;
|
|
||||||
|
|
||||||
const pr = await github.rest.pulls.create({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
title: `Release Proposal: ${{ steps.inputs.outputs.version }}`,
|
|
||||||
head: '${{ steps.setup.outputs.branch_name }}',
|
|
||||||
base: 'master',
|
|
||||||
body: `## Release Proposal: ${{ steps.inputs.outputs.version }}
|
|
||||||
|
|
||||||
**Target Commit:** \`${{ steps.inputs.outputs.commit_hash }}\`
|
|
||||||
**Requested by:** @${{ github.actor }}
|
|
||||||
**Commit Status:** ${{ steps.setup.outputs.behind_info }}
|
|
||||||
|
|
||||||
This PR proposes creating release tag \`${{ steps.inputs.outputs.version }}\` at commit \`${{ steps.inputs.outputs.commit_hash }}\`.
|
|
||||||
|
|
||||||
### Approval Process
|
|
||||||
|
|
||||||
This PR requires **approval from 2+ maintainers** before the tag can be created.
|
|
||||||
|
|
||||||
### What happens next?
|
|
||||||
|
|
||||||
1. Maintainers review this proposal
|
|
||||||
2. When 2+ maintainer approvals are received, an automated workflow will post tagging instructions
|
|
||||||
3. A maintainer manually creates and pushes the signed tag
|
|
||||||
4. The release workflow is triggered automatically by the tag push
|
|
||||||
5. Upon release completion, this PR is closed and the branch is deleted
|
|
||||||
|
|
||||||
### Changes Since Last Release
|
|
||||||
|
|
||||||
${changelog}
|
|
||||||
|
|
||||||
### Release Checklist
|
|
||||||
|
|
||||||
- [ ] All tests pass
|
|
||||||
- [ ] Security review completed
|
|
||||||
- [ ] Documentation updated
|
|
||||||
- [ ] Breaking changes documented
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Note:** Tag creation is manual and requires a signed tag from a maintainer.`,
|
|
||||||
draft: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add labels
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pr.data.number,
|
|
||||||
labels: ['release-proposal', 'awaiting-approval']
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Created PR: ${pr.data.html_url}`);
|
|
||||||
|
|
||||||
return { number: pr.data.number, url: pr.data.html_url };
|
|
||||||
result-encoding: json
|
|
||||||
|
|
||||||
- name: Post summary
|
|
||||||
run: |
|
|
||||||
echo "## Release Proposal PR Created! 🚀" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Version: **${{ steps.inputs.outputs.version }}**" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Commit: **${{ steps.inputs.outputs.commit_hash }}**" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Status: ${{ steps.setup.outputs.behind_info }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "PR: ${{ fromJson(steps.create_pr.outputs.result).url }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
+19
-402
@@ -9,338 +9,21 @@ env:
|
|||||||
# https://github.com/actions/setup-go/issues/491
|
# https://github.com/actions/setup-go/issues/491
|
||||||
GOTOOLCHAIN: local
|
GOTOOLCHAIN: local
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify-tag:
|
|
||||||
name: Verify Tag Signature and Approvals
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
outputs:
|
|
||||||
verification_passed: ${{ steps.verify.outputs.passed }}
|
|
||||||
tag_version: ${{ steps.info.outputs.version }}
|
|
||||||
proposal_issue_number: ${{ steps.find_proposal.outputs.result && fromJson(steps.find_proposal.outputs.result).number || '' }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
# Force fetch upstream tags -- because 65 minutes
|
|
||||||
# tl;dr: actions/checkout@v3 runs this line:
|
|
||||||
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
|
||||||
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
|
||||||
# git fetch --prune --unshallow
|
|
||||||
# which doesn't overwrite that tag because that would be destructive.
|
|
||||||
# Credit to @francislavoie for the investigation.
|
|
||||||
# https://github.com/actions/checkout/issues/290#issuecomment-680260080
|
|
||||||
- name: Force fetch upstream tags
|
|
||||||
run: git fetch --tags --force
|
|
||||||
|
|
||||||
- name: Get tag info
|
|
||||||
id: info
|
|
||||||
run: |
|
|
||||||
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
|
||||||
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
|
|
||||||
- name: Print Go version and environment
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
printf "Using go at: $(which go)\n"
|
|
||||||
printf "Go version: $(go version)\n"
|
|
||||||
printf "\n\nGo environment:\n\n"
|
|
||||||
go env
|
|
||||||
printf "\n\nSystem environment:\n\n"
|
|
||||||
env
|
|
||||||
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
|
||||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Add "pip install" CLI tools to PATH
|
|
||||||
echo ~/.local/bin >> $GITHUB_PATH
|
|
||||||
|
|
||||||
# Parse semver
|
|
||||||
TAG=${GITHUB_REF/refs\/tags\//}
|
|
||||||
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
|
|
||||||
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
|
|
||||||
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
|
|
||||||
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
|
|
||||||
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
|
|
||||||
echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT
|
|
||||||
echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT
|
|
||||||
echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT
|
|
||||||
echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Validate commits and tag signatures
|
|
||||||
id: verify
|
|
||||||
env:
|
|
||||||
signing_keys: ${{ secrets.SIGNING_KEYS }}
|
|
||||||
run: |
|
|
||||||
# Read the string into an array, splitting by IFS
|
|
||||||
IFS=";" read -ra keys_collection <<< "$signing_keys"
|
|
||||||
|
|
||||||
# ref: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#example-usage-of-the-runner-context
|
|
||||||
touch "${{ runner.temp }}/allowed_signers"
|
|
||||||
|
|
||||||
# Iterate and print the split elements
|
|
||||||
for item in "${keys_collection[@]}"; do
|
|
||||||
|
|
||||||
# trim leading whitespaces
|
|
||||||
item="${item##*( )}"
|
|
||||||
|
|
||||||
# trim trailing whitespaces
|
|
||||||
item="${item%%*( )}"
|
|
||||||
|
|
||||||
IFS=" " read -ra key_components <<< "$item"
|
|
||||||
# git wants it in format: email address, type, public key
|
|
||||||
# ssh has it in format: type, public key, email address
|
|
||||||
echo "${key_components[2]} namespaces=\"git\" ${key_components[0]} ${key_components[1]}" >> "${{ runner.temp }}/allowed_signers"
|
|
||||||
done
|
|
||||||
|
|
||||||
git config set --global gpg.ssh.allowedSignersFile "${{ runner.temp }}/allowed_signers"
|
|
||||||
|
|
||||||
echo "Verifying the tag: ${{ steps.vars.outputs.version_tag }}"
|
|
||||||
|
|
||||||
# Verify the tag is signed
|
|
||||||
if ! git verify-tag -v "${{ steps.vars.outputs.version_tag }}" 2>&1; then
|
|
||||||
echo "❌ Tag verification failed!"
|
|
||||||
echo "passed=false" >> $GITHUB_OUTPUT
|
|
||||||
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# Run it again to capture the output
|
|
||||||
git verify-tag -v "${{ steps.vars.outputs.version_tag }}" 2>&1 | tee /tmp/verify-output.txt;
|
|
||||||
|
|
||||||
# SSH verification output typically includes the key fingerprint
|
|
||||||
# Use GNU grep with Perl regex for cleaner extraction (Linux environment)
|
|
||||||
KEY_SHA256=$(grep -oP "SHA256:[\"']?\K[A-Za-z0-9+/=]+(?=[\"']?)" /tmp/verify-output.txt | head -1 || echo "")
|
|
||||||
|
|
||||||
if [ -z "$KEY_SHA256" ]; then
|
|
||||||
# Try alternative pattern with "key" prefix
|
|
||||||
KEY_SHA256=$(grep -oP "key SHA256:[\"']?\K[A-Za-z0-9+/=]+(?=[\"']?)" /tmp/verify-output.txt | head -1 || echo "")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$KEY_SHA256" ]; then
|
|
||||||
# Fallback: extract any base64-like string (40+ chars)
|
|
||||||
KEY_SHA256=$(grep -oP '[A-Za-z0-9+/]{40,}=?' /tmp/verify-output.txt | head -1 || echo "")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$KEY_SHA256" ]; then
|
|
||||||
echo "Somehow could not extract SSH key fingerprint from git verify-tag output"
|
|
||||||
echo "Cancelling flow and deleting tag"
|
|
||||||
echo "passed=false" >> $GITHUB_OUTPUT
|
|
||||||
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Tag verification succeeded!"
|
|
||||||
echo "passed=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "key_id=$KEY_SHA256" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Find related release proposal
|
|
||||||
id: find_proposal
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const version = '${{ steps.vars.outputs.version_tag }}';
|
|
||||||
|
|
||||||
// Search for PRs with release-proposal label that match this version
|
|
||||||
const prs = await github.rest.pulls.list({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
state: 'open', // Changed to 'all' to find both open and closed PRs
|
|
||||||
sort: 'updated',
|
|
||||||
direction: 'desc'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Find the most recent PR for this version
|
|
||||||
const proposal = prs.data.find(pr =>
|
|
||||||
pr.title.includes(version) &&
|
|
||||||
pr.labels.some(label => label.name === 'release-proposal')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!proposal) {
|
|
||||||
console.log(`⚠️ No release proposal PR found for ${version}`);
|
|
||||||
console.log('This might be a hotfix or emergency release');
|
|
||||||
return { number: null, approved: true, approvals: 0, proposedCommit: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Found proposal PR #${proposal.number} for version ${version}`);
|
|
||||||
|
|
||||||
// Extract commit hash from PR body
|
|
||||||
const commitMatch = proposal.body.match(/\*\*Target Commit:\*\*\s*`([a-f0-9]+)`/);
|
|
||||||
const proposedCommit = commitMatch ? commitMatch[1] : null;
|
|
||||||
|
|
||||||
if (proposedCommit) {
|
|
||||||
console.log(`Proposal was for commit: ${proposedCommit}`);
|
|
||||||
} else {
|
|
||||||
console.log('⚠️ No target commit hash found in PR body');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get PR reviews to extract approvers
|
|
||||||
let approvers = 'Validated by automation';
|
|
||||||
let approvalCount = 2; // Minimum required
|
|
||||||
|
|
||||||
try {
|
|
||||||
const reviews = await github.rest.pulls.listReviews({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
pull_number: proposal.number
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get latest review per user and filter for approvals
|
|
||||||
const latestReviewsByUser = {};
|
|
||||||
reviews.data.forEach(review => {
|
|
||||||
const username = review.user.login;
|
|
||||||
if (!latestReviewsByUser[username] || new Date(review.submitted_at) > new Date(latestReviewsByUser[username].submitted_at)) {
|
|
||||||
latestReviewsByUser[username] = review;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const approvalReviews = Object.values(latestReviewsByUser).filter(review =>
|
|
||||||
review.state === 'APPROVED'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (approvalReviews.length > 0) {
|
|
||||||
approvers = approvalReviews.map(r => '@' + r.user.login).join(', ');
|
|
||||||
approvalCount = approvalReviews.length;
|
|
||||||
console.log(`Found ${approvalCount} approvals from: ${approvers}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Could not fetch reviews: ${error.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
number: proposal.number,
|
|
||||||
approved: true,
|
|
||||||
approvals: approvalCount,
|
|
||||||
approvers: approvers,
|
|
||||||
proposedCommit: proposedCommit
|
|
||||||
};
|
|
||||||
result-encoding: json
|
|
||||||
|
|
||||||
- name: Verify proposal commit
|
|
||||||
run: |
|
|
||||||
APPROVALS='${{ steps.find_proposal.outputs.result }}'
|
|
||||||
|
|
||||||
# Parse JSON
|
|
||||||
PROPOSED_COMMIT=$(echo "$APPROVALS" | jq -r '.proposedCommit')
|
|
||||||
CURRENT_COMMIT="${{ steps.info.outputs.sha }}"
|
|
||||||
|
|
||||||
echo "Proposed commit: $PROPOSED_COMMIT"
|
|
||||||
echo "Current commit: $CURRENT_COMMIT"
|
|
||||||
|
|
||||||
# Check if commits match (if proposal had a target commit)
|
|
||||||
if [ "$PROPOSED_COMMIT" != "null" ] && [ -n "$PROPOSED_COMMIT" ]; then
|
|
||||||
# Normalize both commits to full SHA for comparison
|
|
||||||
PROPOSED_FULL=$(git rev-parse "$PROPOSED_COMMIT" 2>/dev/null || echo "")
|
|
||||||
CURRENT_FULL=$(git rev-parse "$CURRENT_COMMIT" 2>/dev/null || echo "")
|
|
||||||
|
|
||||||
if [ -z "$PROPOSED_FULL" ]; then
|
|
||||||
echo "⚠️ Could not resolve proposed commit: $PROPOSED_COMMIT"
|
|
||||||
elif [ "$PROPOSED_FULL" != "$CURRENT_FULL" ]; then
|
|
||||||
echo "❌ Commit mismatch!"
|
|
||||||
echo "The tag points to commit $CURRENT_FULL but the proposal was for $PROPOSED_FULL"
|
|
||||||
echo "This indicates an error in tag creation."
|
|
||||||
# Delete the tag remotely
|
|
||||||
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
|
|
||||||
echo "Tag ${{steps.vars.outputs.version_tag}} has been deleted"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "✅ Commit hash matches proposal"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "⚠️ No target commit found in proposal (might be legacy release)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Tag verification completed"
|
|
||||||
|
|
||||||
- name: Update release proposal PR
|
|
||||||
if: fromJson(steps.find_proposal.outputs.result).number != null
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const result = ${{ steps.find_proposal.outputs.result }};
|
|
||||||
|
|
||||||
if (result.number) {
|
|
||||||
// Add in-progress label
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: result.number,
|
|
||||||
labels: ['release-in-progress']
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove approved label if present
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: result.number,
|
|
||||||
name: 'approved'
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Approved label not found:', e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const commentBody = [
|
|
||||||
'## 🚀 Release Workflow Started',
|
|
||||||
'',
|
|
||||||
'- **Tag:** ${{ steps.info.outputs.version }}',
|
|
||||||
'- **Signed by key:** ${{ steps.verify.outputs.key_id }}',
|
|
||||||
'- **Commit:** ${{ steps.info.outputs.sha }}',
|
|
||||||
'- **Approved by:** ' + result.approvers,
|
|
||||||
'',
|
|
||||||
'Release workflow is now running. This PR will be updated when the release is published.'
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: result.number,
|
|
||||||
body: commentBody
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
run: |
|
|
||||||
APPROVALS='${{ steps.find_proposal.outputs.result }}'
|
|
||||||
PROPOSED_COMMIT=$(echo "$APPROVALS" | jq -r '.proposedCommit // "N/A"')
|
|
||||||
APPROVERS=$(echo "$APPROVALS" | jq -r '.approvers // "N/A"')
|
|
||||||
|
|
||||||
echo "## Tag Verification Summary 🔐" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- **Tag:** ${{ steps.info.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- **Commit:** ${{ steps.info.outputs.sha }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- **Proposed Commit:** $PROPOSED_COMMIT" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- **Signature:** ✅ Verified" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- **Signed by:** ${{ steps.verify.outputs.key_id }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- **Approvals:** ✅ Sufficient" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- **Approved by:** $APPROVERS" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Proceeding with release build..." >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
needs: verify-tag
|
|
||||||
if: ${{ needs.verify-tag.outputs.verification_passed == 'true' }}
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
go:
|
go:
|
||||||
- '1.26'
|
- '1.24'
|
||||||
|
|
||||||
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.26'
|
- go: '1.24'
|
||||||
GO_SEMVER: '~1.26.0'
|
GO_SEMVER: '~1.24.1'
|
||||||
|
|
||||||
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
|
||||||
@@ -350,28 +33,21 @@ jobs:
|
|||||||
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents
|
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents
|
||||||
# "Releases" is part of `contents`, so it needs the `write`
|
# "Releases" is part of `contents`, so it needs the `write`
|
||||||
contents: write
|
contents: write
|
||||||
issues: write
|
|
||||||
pull-requests: 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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.GO_SEMVER }}
|
go-version: ${{ matrix.GO_SEMVER }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
# Force fetch upstream tags -- because 65 minutes
|
# Force fetch upstream tags -- because 65 minutes
|
||||||
# tl;dr: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 runs this line:
|
# tl;dr: actions/checkout@v4 runs this line:
|
||||||
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
||||||
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
||||||
# git fetch --prune --unshallow
|
# git fetch --prune --unshallow
|
||||||
@@ -414,12 +90,22 @@ jobs:
|
|||||||
- name: Install Cloudsmith CLI
|
- name: Install Cloudsmith CLI
|
||||||
run: pip install --upgrade cloudsmith-cli
|
run: pip install --upgrade cloudsmith-cli
|
||||||
|
|
||||||
|
- name: Validate commits and tag signatures
|
||||||
|
run: |
|
||||||
|
|
||||||
|
# Import Matt Holt's key
|
||||||
|
curl 'https://github.com/mholt.gpg' | gpg --import
|
||||||
|
|
||||||
|
echo "Verifying the tag: ${{ steps.vars.outputs.version_tag }}"
|
||||||
|
# tags are only accepted if signed by Matt's key
|
||||||
|
git verify-tag "${{ steps.vars.outputs.version_tag }}" || exit 1
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # main
|
uses: sigstore/cosign-installer@main
|
||||||
- name: Cosign version
|
- name: Cosign version
|
||||||
run: cosign version
|
run: cosign version
|
||||||
- name: Install Syft
|
- name: Install Syft
|
||||||
uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # main
|
uses: anchore/sbom-action/download-syft@main
|
||||||
- name: Syft version
|
- name: Syft version
|
||||||
run: syft version
|
run: syft version
|
||||||
- name: Install xcaddy
|
- name: Install xcaddy
|
||||||
@@ -428,7 +114,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@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean --timeout 60m
|
args: release --clean --timeout 60m
|
||||||
@@ -494,72 +180,3 @@ jobs:
|
|||||||
echo "Pushing $filename to 'testing'"
|
echo "Pushing $filename to 'testing'"
|
||||||
cloudsmith push deb caddy/testing/any-distro/any-version $filename
|
cloudsmith push deb caddy/testing/any-distro/any-version $filename
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Update release proposal PR
|
|
||||||
if: needs.verify-tag.outputs.proposal_issue_number != ''
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const prNumber = parseInt('${{ needs.verify-tag.outputs.proposal_issue_number }}');
|
|
||||||
|
|
||||||
if (prNumber) {
|
|
||||||
// Get PR details to find the branch
|
|
||||||
const pr = await github.rest.pulls.get({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
pull_number: prNumber
|
|
||||||
});
|
|
||||||
|
|
||||||
const branchName = pr.data.head.ref;
|
|
||||||
|
|
||||||
// Remove in-progress label
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: prNumber,
|
|
||||||
name: 'release-in-progress'
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Label not found:', e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add released label
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: prNumber,
|
|
||||||
labels: ['released']
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add final comment
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: prNumber,
|
|
||||||
body: '## ✅ Release Published\n\nThe release has been successfully published and is now available.'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close the PR if it's still open
|
|
||||||
if (pr.data.state === 'open') {
|
|
||||||
await github.rest.pulls.update({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
pull_number: prNumber,
|
|
||||||
state: 'closed'
|
|
||||||
});
|
|
||||||
console.log(`Closed PR #${prNumber}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the branch
|
|
||||||
try {
|
|
||||||
await github.rest.git.deleteRef({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
ref: `heads/${branchName}`
|
|
||||||
});
|
|
||||||
console.log(`Deleted branch: ${branchName}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Could not delete branch ${branchName}: ${e.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release Published
|
name: Release Published
|
||||||
@@ -16,20 +13,12 @@ jobs:
|
|||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: read
|
|
||||||
actions: write
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
# See https://github.com/peter-evans/repository-dispatch
|
# See https://github.com/peter-evans/repository-dispatch
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@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@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0
|
uses: peter-evans/repository-dispatch@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||||
repository: caddyserver/dist
|
repository: caddyserver/dist
|
||||||
@@ -37,7 +26,7 @@ jobs:
|
|||||||
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
|
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
|
||||||
|
|
||||||
- name: Trigger event on caddyserver/caddy-docker
|
- name: Trigger event on caddyserver/caddy-docker
|
||||||
uses: peter-evans/repository-dispatch@5fc4efd1a4797ddb68ffd0714a238564e4cc0e6f # v4.0.0
|
uses: peter-evans/repository-dispatch@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||||
repository: caddyserver/caddy-docker
|
repository: caddyserver/caddy-docker
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
# This workflow uses actions that are not certified by GitHub. They are provided
|
|
||||||
# by a third-party and are governed by separate terms of service, privacy
|
|
||||||
# policy, and support documentation.
|
|
||||||
|
|
||||||
name: OpenSSF Scorecard supply-chain security
|
|
||||||
on:
|
|
||||||
# For Branch-Protection check. Only the default branch is supported. See
|
|
||||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
|
||||||
branch_protection_rule:
|
|
||||||
# To guarantee Maintained check is occasionally updated. See
|
|
||||||
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
|
||||||
schedule:
|
|
||||||
- cron: '20 2 * * 5'
|
|
||||||
push:
|
|
||||||
branches: [ "master", "2.*" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master", "2.*" ]
|
|
||||||
|
|
||||||
|
|
||||||
# Declare default permissions as read only.
|
|
||||||
permissions: read-all
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analysis:
|
|
||||||
name: Scorecard analysis
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
|
|
||||||
if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
|
|
||||||
permissions:
|
|
||||||
# Needed to upload the results to code-scanning dashboard.
|
|
||||||
security-events: write
|
|
||||||
# Needed to publish results and get a badge (see publish_results below).
|
|
||||||
id-token: write
|
|
||||||
# Uncomment the permissions below if installing in a private repository.
|
|
||||||
# contents: read
|
|
||||||
# actions: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Harden the runner (Audit all outbound calls)
|
|
||||||
uses: step-security/harden-runner@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
|
|
||||||
+151
-91
@@ -1,19 +1,27 @@
|
|||||||
version: "2"
|
linters-settings:
|
||||||
run:
|
errcheck:
|
||||||
issues-exit-code: 1
|
exclude-functions:
|
||||||
tests: false
|
- fmt.*
|
||||||
build-tags:
|
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
|
||||||
- nobadger
|
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
|
||||||
- nomysql
|
gci:
|
||||||
- nopgx
|
sections:
|
||||||
output:
|
- standard # Standard section: captures all standard packages.
|
||||||
formats:
|
- default # Default section: contains all imports that could not be matched to another section type.
|
||||||
text:
|
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
|
||||||
path: stdout
|
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
|
||||||
print-linter-name: true
|
# Skip generated files.
|
||||||
print-issued-lines: true
|
# Default: true
|
||||||
|
skip-generated: true
|
||||||
|
# Enable custom order of sections.
|
||||||
|
# If `true`, make the section order the same as the order of `sections`.
|
||||||
|
# Default: false
|
||||||
|
custom-order: true
|
||||||
|
exhaustive:
|
||||||
|
ignore-enum-types: reflect.Kind|svc.Cmd
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
default: none
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- asasalint
|
- asasalint
|
||||||
- asciicheck
|
- asciicheck
|
||||||
@@ -27,96 +35,148 @@ linters:
|
|||||||
- errcheck
|
- errcheck
|
||||||
- errname
|
- errname
|
||||||
- exhaustive
|
- exhaustive
|
||||||
|
- gci
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gofumpt
|
||||||
- gosec
|
- gosec
|
||||||
|
- gosimple
|
||||||
- govet
|
- govet
|
||||||
- importas
|
|
||||||
- ineffassign
|
- ineffassign
|
||||||
|
- importas
|
||||||
- misspell
|
- misspell
|
||||||
- prealloc
|
- prealloc
|
||||||
- promlinter
|
- promlinter
|
||||||
- sloglint
|
- sloglint
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
|
- tenv
|
||||||
- testableexamples
|
- testableexamples
|
||||||
- testifylint
|
- testifylint
|
||||||
- tparallel
|
- tparallel
|
||||||
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
- unused
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- whitespace
|
||||||
- zerologlint
|
- zerologlint
|
||||||
settings:
|
# these are implicitly disabled:
|
||||||
staticcheck:
|
# - containedctx
|
||||||
checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1006", "-QF1008"] # default, and exclude 1 more undesired check
|
# - contextcheck
|
||||||
errcheck:
|
# - cyclop
|
||||||
exclude-functions:
|
# - depguard
|
||||||
- fmt.*
|
# - errchkjson
|
||||||
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
|
# - errorlint
|
||||||
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
|
# - exhaustruct
|
||||||
exhaustive:
|
# - execinquery
|
||||||
ignore-enum-types: reflect.Kind|svc.Cmd
|
# - exhaustruct
|
||||||
exclusions:
|
# - forbidigo
|
||||||
generated: lax
|
# - forcetypeassert
|
||||||
presets:
|
# - funlen
|
||||||
- comments
|
# - ginkgolinter
|
||||||
- common-false-positives
|
# - gocheckcompilerdirectives
|
||||||
- legacy
|
# - gochecknoglobals
|
||||||
- std-error-handling
|
# - gochecknoinits
|
||||||
rules:
|
# - gochecksumtype
|
||||||
- linters:
|
# - gocognit
|
||||||
- gosec
|
# - goconst
|
||||||
text: G115 # TODO: Either we should fix the issues or nuke the linter if it's bad
|
# - gocritic
|
||||||
- linters:
|
# - gocyclo
|
||||||
- gosec
|
# - godot
|
||||||
text: G107 # we aren't calling unknown URL
|
# - godox
|
||||||
- linters:
|
# - goerr113
|
||||||
- gosec
|
# - goheader
|
||||||
text: G203 # as a web server that's expected to handle any template, this is totally in the hands of the user.
|
# - gomnd
|
||||||
- linters:
|
# - gomoddirectives
|
||||||
- gosec
|
# - gomodguard
|
||||||
text: G204 # we're shelling out to known commands, not relying on user-defined input.
|
# - goprintffuncname
|
||||||
- linters:
|
# - gosmopolitan
|
||||||
- gosec
|
# - grouper
|
||||||
# the choice of weakrand is deliberate, hence the named import "weakrand"
|
# - inamedparam
|
||||||
path: modules/caddyhttp/reverseproxy/selectionpolicies.go
|
# - interfacebloat
|
||||||
text: G404
|
# - ireturn
|
||||||
- linters:
|
# - lll
|
||||||
- gosec
|
# - loggercheck
|
||||||
path: modules/caddyhttp/reverseproxy/streaming.go
|
# - maintidx
|
||||||
text: G404
|
# - makezero
|
||||||
- linters:
|
# - mirror
|
||||||
- dupl
|
# - musttag
|
||||||
path: modules/logging/filters.go
|
# - nakedret
|
||||||
- linters:
|
# - nestif
|
||||||
- dupl
|
# - nilerr
|
||||||
path: modules/caddyhttp/matchers.go
|
# - nilnil
|
||||||
- linters:
|
# - nlreturn
|
||||||
- dupl
|
# - noctx
|
||||||
path: modules/caddyhttp/vars.go
|
# - nolintlint
|
||||||
- linters:
|
# - nonamedreturns
|
||||||
- errcheck
|
# - nosprintfhostport
|
||||||
path: _test\.go
|
# - paralleltest
|
||||||
paths:
|
# - perfsprint
|
||||||
- third_party$
|
# - predeclared
|
||||||
- builtin$
|
# - protogetter
|
||||||
- examples$
|
# - reassign
|
||||||
formatters:
|
# - revive
|
||||||
enable:
|
# - rowserrcheck
|
||||||
- gci
|
# - stylecheck
|
||||||
- gofmt
|
# - tagalign
|
||||||
- gofumpt
|
# - tagliatelle
|
||||||
- goimports
|
# - testpackage
|
||||||
settings:
|
# - thelper
|
||||||
gci:
|
# - unparam
|
||||||
sections:
|
# - usestdlibvars
|
||||||
- standard # Standard section: captures all standard packages.
|
# - varnamelen
|
||||||
- default # Default section: contains all imports that could not be matched to another section type.
|
# - wrapcheck
|
||||||
- prefix(github.com/caddyserver/caddy/v2/cmd) # ensure that this is always at the top and always has a line break.
|
# - wsl
|
||||||
- prefix(github.com/caddyserver/caddy) # Custom section: groups all imports with the specified Prefix.
|
|
||||||
custom-order: true
|
run:
|
||||||
exclusions:
|
# default concurrency is a available CPU number.
|
||||||
generated: lax
|
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
|
||||||
paths:
|
timeout: 5m
|
||||||
- third_party$
|
issues-exit-code: 1
|
||||||
- builtin$
|
tests: false
|
||||||
- examples$
|
|
||||||
|
# output configuration options
|
||||||
|
output:
|
||||||
|
formats:
|
||||||
|
- format: 'colored-line-number'
|
||||||
|
print-issued-lines: true
|
||||||
|
print-linter-name: true
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- text: 'G115' # TODO: Either we should fix the issues or nuke the linter if it's bad
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
# we aren't calling unknown URL
|
||||||
|
- text: 'G107' # G107: Url provided to HTTP request as taint input
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
# as a web server that's expected to handle any template, this is totally in the hands of the user.
|
||||||
|
- text: 'G203' # G203: Use of unescaped data in HTML templates
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
# we're shelling out to known commands, not relying on user-defined input.
|
||||||
|
- text: 'G204' # G204: Audit use of command execution
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
# the choice of weakrand is deliberate, hence the named import "weakrand"
|
||||||
|
- path: modules/caddyhttp/reverseproxy/selectionpolicies.go
|
||||||
|
text: 'G404' # G404: Insecure random number source (rand)
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
- path: modules/caddyhttp/reverseproxy/streaming.go
|
||||||
|
text: 'G404' # G404: Insecure random number source (rand)
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
- path: modules/logging/filters.go
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- path: modules/caddyhttp/matchers.go
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- path: modules/caddyhttp/vars.go
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
|||||||
+1
-3
@@ -13,7 +13,7 @@ before:
|
|||||||
- cp cmd/caddy/main.go caddy-build/main.go
|
- cp cmd/caddy/main.go caddy-build/main.go
|
||||||
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
||||||
# prepare syso files for windows embedding
|
# prepare syso files for windows embedding
|
||||||
- /bin/sh -c 'for a in amd64 arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a xcaddy build {{.Env.TAG}}; done'
|
- /bin/sh -c 'for a in amd64 arm arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a xcaddy build {{.Env.TAG}}; done'
|
||||||
- /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build'
|
- /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build'
|
||||||
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
|
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
|
||||||
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
|
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
|
||||||
@@ -67,8 +67,6 @@ builds:
|
|||||||
goarch: s390x
|
goarch: s390x
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: riscv64
|
goarch: riscv64
|
||||||
- goos: windows
|
|
||||||
goarch: arm
|
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
goarch: ppc64le
|
goarch: ppc64le
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
repos:
|
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
|
||||||
rev: v8.16.3
|
|
||||||
hooks:
|
|
||||||
- id: gitleaks
|
|
||||||
- repo: https://github.com/golangci/golangci-lint
|
|
||||||
rev: v1.52.2
|
|
||||||
hooks:
|
|
||||||
- id: golangci-lint-config-verify
|
|
||||||
- id: golangci-lint
|
|
||||||
- id: golangci-lint-fmt
|
|
||||||
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
|
||||||
rev: 3.0.0
|
|
||||||
hooks:
|
|
||||||
- id: shellcheck
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v4.4.0
|
|
||||||
hooks:
|
|
||||||
- id: end-of-file-fixer
|
|
||||||
- id: trailing-whitespace
|
|
||||||
@@ -12,52 +12,23 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<h3 align="center">Every site on HTTPS</h3>
|
<h3 align="center">Every site on HTTPS</h3>
|
||||||
<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">
|
||||||
|
<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://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
|
||||||
|
<br>
|
||||||
|
<a href="https://x.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/twitter/follow/caddyserver" alt="@caddyserver on Twitter"></a>
|
||||||
|
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
|
||||||
|
<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://cloudsmith.io/~caddy/repos/"><img src="https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith" alt="Cloudsmith"></a>
|
||||||
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/caddyserver/caddy/releases">Releases</a> ·
|
<a href="https://github.com/caddyserver/caddy/releases">Releases</a> ·
|
||||||
<a href="https://caddyserver.com/docs/">Documentation</a> ·
|
<a href="https://caddyserver.com/docs/">Documentation</a> ·
|
||||||
<a href="https://caddy.community">Get Help</a>
|
<a href="https://caddy.community">Get Help</a>
|
||||||
</p>
|
</p>
|
||||||
<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://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://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>
|
|
||||||
<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://cloudsmith.io/~caddy/repos/"><img src="https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith" alt="Cloudsmith"></a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<b>Powered by</b>
|
|
||||||
<br>
|
|
||||||
<a href="https://github.com/caddyserver/certmagic">
|
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/55066419/206946718-740b6371-3df3-4d72-a822-47e4c48af999.png">
|
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png">
|
|
||||||
<img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250">
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Warp sponsorship requests this section -->
|
|
||||||
<div align="center" markdown="1">
|
|
||||||
<hr>
|
|
||||||
<sup>Special thanks to:</sup>
|
|
||||||
<br>
|
|
||||||
<a href="https://go.warp.dev/caddy">
|
|
||||||
<img alt="Warp sponsorship" width="400" src="https://github.com/user-attachments/assets/c8efffde-18c7-4af4-83ed-b1aba2dda394">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
### [Warp, built for coding with multiple AI agents](https://go.warp.dev/caddy)
|
|
||||||
[Available for MacOS, Linux, & Windows](https://go.warp.dev/caddy)<br>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
### Menu
|
### Menu
|
||||||
|
|
||||||
@@ -72,6 +43,18 @@
|
|||||||
- [Getting help](#getting-help)
|
- [Getting help](#getting-help)
|
||||||
- [About](#about)
|
- [About](#about)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<b>Powered by</b>
|
||||||
|
<br>
|
||||||
|
<a href="https://github.com/caddyserver/certmagic">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/55066419/206946718-740b6371-3df3-4d72-a822-47e4c48af999.png">
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png">
|
||||||
|
<img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250">
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
## [Features](https://caddyserver.com/features)
|
## [Features](https://caddyserver.com/features)
|
||||||
|
|
||||||
@@ -105,7 +88,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
|
|||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- [Go 1.25.0 or newer](https://golang.org/dl/)
|
- [Go 1.24.0 or newer](https://golang.org/dl/)
|
||||||
|
|
||||||
### For development
|
### For development
|
||||||
|
|
||||||
@@ -133,18 +116,11 @@ username ALL=(ALL:ALL) NOPASSWD: /usr/sbin/setcap
|
|||||||
|
|
||||||
replacing `username` with your actual username. Please be careful and only do this if you know what you are doing! We are only qualified to document how to use Caddy, not Go tooling or your computer, and we are providing these instructions for convenience only; please learn how to use your own computer at your own risk and make any needful adjustments.
|
replacing `username` with your actual username. Please be careful and only do this if you know what you are doing! We are only qualified to document how to use Caddy, not Go tooling or your computer, and we are providing these instructions for convenience only; please learn how to use your own computer at your own risk and make any needful adjustments.
|
||||||
|
|
||||||
Then you can run the tests in all modules or a specific one:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go test ./...
|
|
||||||
$ go test ./modules/caddyhttp/tracing/
|
|
||||||
```
|
|
||||||
|
|
||||||
### With version information and/or plugins
|
### With version information and/or plugins
|
||||||
|
|
||||||
Using [our builder tool, `xcaddy`](https://github.com/caddyserver/xcaddy)...
|
Using [our builder tool, `xcaddy`](https://github.com/caddyserver/xcaddy)...
|
||||||
|
|
||||||
```bash
|
```
|
||||||
$ xcaddy build
|
$ xcaddy build
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -220,6 +196,6 @@ Matthew Holt began developing Caddy in 2014 while studying computer science at B
|
|||||||
- _Project on X: [@caddyserver](https://x.com/caddyserver)_
|
- _Project on X: [@caddyserver](https://x.com/caddyserver)_
|
||||||
- _Author on X: [@mholt6](https://x.com/mholt6)_
|
- _Author on X: [@mholt6](https://x.com/mholt6)_
|
||||||
|
|
||||||
Caddy is a project of [ZeroSSL](https://zerossl.com), an HID Global company.
|
Caddy is a project of [ZeroSSL](https://zerossl.com), a Stack Holdings company.
|
||||||
|
|
||||||
Debian package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com). Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that enables your organization to create, store and share packages in any format, to any place, with total confidence.
|
Debian package repository hosting is graciously provided by [Cloudsmith](https://cloudsmith.com). Cloudsmith is the only fully hosted, cloud-native, universal package management solution, that enables your organization to create, store and share packages in any format, to any place, with total confidence.
|
||||||
|
|||||||
@@ -47,12 +47,6 @@ import (
|
|||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// testCertMagicStorageOverride is a package-level test hook. Tests may set
|
|
||||||
// this variable to provide a temporary certmagic.Storage so that cert
|
|
||||||
// management in tests does not hit the real default storage on disk.
|
|
||||||
// This must NOT be set in production code.
|
|
||||||
var testCertMagicStorageOverride certmagic.Storage
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// The hard-coded default `DefaultAdminListen` can be overridden
|
// The hard-coded default `DefaultAdminListen` can be overridden
|
||||||
// by setting the `CADDY_ADMIN` environment variable.
|
// by setting the `CADDY_ADMIN` environment variable.
|
||||||
@@ -639,19 +633,8 @@ func (ident *IdentityConfig) certmagicConfig(logger *zap.Logger, makeCache bool)
|
|||||||
// certmagic config, although it'll be mostly useless for remote management
|
// certmagic config, although it'll be mostly useless for remote management
|
||||||
ident = new(IdentityConfig)
|
ident = new(IdentityConfig)
|
||||||
}
|
}
|
||||||
// Choose storage: prefer the package-level test override when present,
|
|
||||||
// otherwise use the configured DefaultStorage. Tests may set an override
|
|
||||||
// to divert storage into a temporary location. Otherwise, in production
|
|
||||||
// we use the DefaultStorage since we don't want to act as part of a
|
|
||||||
// cluster; this storage is for the server's local identity only.
|
|
||||||
var storage certmagic.Storage
|
|
||||||
if testCertMagicStorageOverride != nil {
|
|
||||||
storage = testCertMagicStorageOverride
|
|
||||||
} else {
|
|
||||||
storage = DefaultStorage
|
|
||||||
}
|
|
||||||
template := certmagic.Config{
|
template := certmagic.Config{
|
||||||
Storage: storage,
|
Storage: DefaultStorage, // do not act as part of a cluster (this is for the server's local identity)
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Issuers: ident.issuers,
|
Issuers: ident.issuers,
|
||||||
}
|
}
|
||||||
@@ -824,38 +807,13 @@ func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// common mitigations in browser contexts
|
|
||||||
if strings.Contains(r.Header.Get("Upgrade"), "websocket") {
|
if strings.Contains(r.Header.Get("Upgrade"), "websocket") {
|
||||||
// I've never been able demonstrate a vulnerability myself, but apparently
|
// I've never been able demonstrate a vulnerability myself, but apparently
|
||||||
// WebSocket connections originating from browsers aren't subject to CORS
|
// WebSocket connections originating from browsers aren't subject to CORS
|
||||||
// restrictions, so we'll just be on the safe side
|
// restrictions, so we'll just be on the safe side
|
||||||
h.handleError(w, r, APIError{
|
h.handleError(w, r, fmt.Errorf("websocket connections aren't allowed"))
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: errors.New("websocket connections aren't allowed"),
|
|
||||||
Message: "WebSocket connections aren't allowed.",
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.Contains(r.Header.Get("Sec-Fetch-Mode"), "no-cors") {
|
|
||||||
// turns out web pages can just disable the same-origin policy (!???!?)
|
|
||||||
// but at least browsers let us know that's the case, holy heck
|
|
||||||
h.handleError(w, r, APIError{
|
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: errors.New("client attempted to make request by disabling same-origin policy using no-cors mode"),
|
|
||||||
Message: "Disabling same-origin restrictions is not allowed.",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if r.Header.Get("Origin") == "null" {
|
|
||||||
// bug in Firefox in certain cross-origin situations (yikes?)
|
|
||||||
// (not strictly a security vuln on its own, but it's red flaggy,
|
|
||||||
// since it seems to manifest in cross-origin contexts)
|
|
||||||
h.handleError(w, r, APIError{
|
|
||||||
HTTPStatus: http.StatusBadRequest,
|
|
||||||
Err: errors.New("invalid origin 'null'"),
|
|
||||||
Message: "Buggy browser is sending null Origin header.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.enforceHost {
|
if h.enforceHost {
|
||||||
// DNS rebinding mitigation
|
// DNS rebinding mitigation
|
||||||
@@ -866,9 +824,7 @@ func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, hasOriginHeader := r.Header["Origin"]
|
if h.enforceOrigin {
|
||||||
_, hasSecHeader := r.Header["Sec-Fetch-Mode"]
|
|
||||||
if h.enforceOrigin || hasOriginHeader || hasSecHeader {
|
|
||||||
// cross-site mitigation
|
// cross-site mitigation
|
||||||
origin, err := h.checkOrigin(r)
|
origin, err := h.checkOrigin(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -990,7 +946,7 @@ func (h adminHandler) originAllowed(origin *url.URL) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// etagHasher returns the hasher we used on the config to both
|
// etagHasher returns a 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() }
|
||||||
|
|
||||||
@@ -1073,13 +1029,6 @@ 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,
|
||||||
@@ -1154,10 +1103,7 @@ func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error
|
|||||||
if len(body) > 0 {
|
if len(body) > 0 {
|
||||||
err = json.Unmarshal(body, &val)
|
err = json.Unmarshal(body, &val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if jsonErr, ok := err.(*json.SyntaxError); ok {
|
return fmt.Errorf("decoding request body: %v", err)
|
||||||
return fmt.Errorf("decoding request body: %w, at offset %d", jsonErr, jsonErr.Offset)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("decoding request body: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-48
@@ -19,14 +19,11 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@@ -151,9 +148,11 @@ 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.Go(func() {
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
_ = Load(testCfg, true)
|
_ = Load(testCfg, true)
|
||||||
})
|
wg.Done()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
@@ -207,7 +206,7 @@ func TestETags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkLoad(b *testing.B) {
|
func BenchmarkLoad(b *testing.B) {
|
||||||
for b.Loop() {
|
for i := 0; i < b.N; i++ {
|
||||||
Load(testCfg, true)
|
Load(testCfg, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,12 +276,13 @@ func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the admin handler directly (no listener active)
|
err := replaceLocalAdminServer(cfg, Context{})
|
||||||
addr, err := ParseNetworkAddress("localhost:2019")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to parse address: %v", err)
|
t.Fatalf("setting up admin server: %v", err)
|
||||||
}
|
}
|
||||||
handler := cfg.Admin.newAdminHandler(addr, false, Context{})
|
defer func() {
|
||||||
|
stopAdminServer(localAdminServer)
|
||||||
|
}()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -315,7 +315,7 @@ func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
|
|||||||
req := httptest.NewRequest(test.method, fmt.Sprintf("http://localhost:2019%s", test.path), nil)
|
req := httptest.NewRequest(test.method, fmt.Sprintf("http://localhost:2019%s", test.path), nil)
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
handler.ServeHTTP(rr, req)
|
localAdminServer.Handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
if rr.Code != test.expectedStatus {
|
if rr.Code != test.expectedStatus {
|
||||||
t.Errorf("expected status %d but got %d", test.expectedStatus, rr.Code)
|
t.Errorf("expected status %d but got %d", test.expectedStatus, rr.Code)
|
||||||
@@ -335,7 +335,9 @@ func TestAdminHandlerBuiltinRouteErrors(t *testing.T) {
|
|||||||
|
|
||||||
func testGetMetricValue(labels map[string]string) float64 {
|
func testGetMetricValue(labels map[string]string) float64 {
|
||||||
promLabels := prometheus.Labels{}
|
promLabels := prometheus.Labels{}
|
||||||
maps.Copy(promLabels, labels)
|
for k, v := range labels {
|
||||||
|
promLabels[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels)
|
metric, err := adminMetrics.requestErrors.GetMetricWith(promLabels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -375,7 +377,9 @@ func (m *mockModule) CaddyModule() ModuleInfo {
|
|||||||
|
|
||||||
func TestNewAdminHandlerRouterRegistration(t *testing.T) {
|
func TestNewAdminHandlerRouterRegistration(t *testing.T) {
|
||||||
originalModules := make(map[string]ModuleInfo)
|
originalModules := make(map[string]ModuleInfo)
|
||||||
maps.Copy(originalModules, modules)
|
for k, v := range modules {
|
||||||
|
originalModules[k] = v
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
modules = originalModules
|
modules = originalModules
|
||||||
}()
|
}()
|
||||||
@@ -475,7 +479,9 @@ func TestAdminRouterProvisioning(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
originalModules := make(map[string]ModuleInfo)
|
originalModules := make(map[string]ModuleInfo)
|
||||||
maps.Copy(originalModules, modules)
|
for k, v := range modules {
|
||||||
|
originalModules[k] = v
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
modules = originalModules
|
modules = originalModules
|
||||||
}()
|
}()
|
||||||
@@ -768,7 +774,9 @@ func (m *mockIssuerModule) CaddyModule() ModuleInfo {
|
|||||||
|
|
||||||
func TestManageIdentity(t *testing.T) {
|
func TestManageIdentity(t *testing.T) {
|
||||||
originalModules := make(map[string]ModuleInfo)
|
originalModules := make(map[string]ModuleInfo)
|
||||||
maps.Copy(originalModules, modules)
|
for k, v := range modules {
|
||||||
|
originalModules[k] = v
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
modules = originalModules
|
modules = originalModules
|
||||||
}()
|
}()
|
||||||
@@ -800,24 +808,8 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
|
|||||||
...
|
...
|
||||||
-----END PRIVATE KEY-----`)
|
-----END PRIVATE KEY-----`)
|
||||||
|
|
||||||
tmpDir, err := os.MkdirTemp("", "TestManageIdentity-")
|
testStorage := certmagic.FileStorage{Path: t.TempDir()}
|
||||||
if err != nil {
|
err := testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testStorage := certmagic.FileStorage{Path: tmpDir}
|
|
||||||
// Clean up the temp dir after the test finishes. Ensure any background
|
|
||||||
// certificate maintenance is stopped first to avoid RemoveAll races.
|
|
||||||
t.Cleanup(func() {
|
|
||||||
if identityCertCache != nil {
|
|
||||||
identityCertCache.Stop()
|
|
||||||
identityCertCache = nil
|
|
||||||
}
|
|
||||||
// Give goroutines a moment to exit and release file handles.
|
|
||||||
time.Sleep(50 * time.Millisecond)
|
|
||||||
_ = os.RemoveAll(tmpDir)
|
|
||||||
})
|
|
||||||
|
|
||||||
err = testStorage.Store(context.Background(), "localhost/localhost.crt", certPEM)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -879,7 +871,7 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
storage: &testStorage,
|
storage: &certmagic.FileStorage{Path: "testdata"},
|
||||||
},
|
},
|
||||||
checkState: func(t *testing.T, cfg *Config) {
|
checkState: func(t *testing.T, cfg *Config) {
|
||||||
if len(cfg.Admin.Identity.issuers) != 1 {
|
if len(cfg.Admin.Identity.issuers) != 1 {
|
||||||
@@ -917,13 +909,6 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
|
|||||||
identityCertCache.Stop()
|
identityCertCache.Stop()
|
||||||
identityCertCache = nil
|
identityCertCache = nil
|
||||||
}
|
}
|
||||||
// Ensure any cache started by manageIdentity is stopped at the end
|
|
||||||
defer func() {
|
|
||||||
if identityCertCache != nil {
|
|
||||||
identityCertCache.Stop()
|
|
||||||
identityCertCache = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ctx := Context{
|
ctx := Context{
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
@@ -931,13 +916,6 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
|
|||||||
moduleInstances: make(map[string][]Module),
|
moduleInstances: make(map[string][]Module),
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this test provided a FileStorage, set the package-level
|
|
||||||
// testCertMagicStorageOverride so certmagicConfig will use it.
|
|
||||||
if test.cfg != nil && test.cfg.storage != nil {
|
|
||||||
testCertMagicStorageOverride = test.cfg.storage
|
|
||||||
defer func() { testCertMagicStorageOverride = nil }()
|
|
||||||
}
|
|
||||||
|
|
||||||
err := manageIdentity(ctx, test.cfg)
|
err := manageIdentity(ctx, test.cfg)
|
||||||
|
|
||||||
if test.wantErr {
|
if test.wantErr {
|
||||||
|
|||||||
@@ -81,10 +81,7 @@ type Config struct {
|
|||||||
// associated value.
|
// associated value.
|
||||||
AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
|
AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
|
||||||
|
|
||||||
apps map[string]App
|
apps map[string]App
|
||||||
|
|
||||||
// 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
|
eventEmitter eventEmitter
|
||||||
|
|
||||||
@@ -147,8 +144,8 @@ func Load(cfgJSON []byte, forceReload bool) error {
|
|||||||
// the new value (if applicable; i.e. "DELETE" doesn't have an input).
|
// the new value (if applicable; i.e. "DELETE" doesn't have an input).
|
||||||
// If the resulting config is the same as the previous, no reload will
|
// If the resulting config is the same as the previous, no reload will
|
||||||
// occur unless forceReload is true. If the config is unchanged and not
|
// occur unless forceReload is true. If the config is unchanged and not
|
||||||
// forcefully reloaded, then errConfigUnchanged is returned. This function
|
// forcefully reloaded, then errConfigUnchanged This function is safe for
|
||||||
// is safe for concurrent use.
|
// concurrent use.
|
||||||
// The ifMatchHeader can optionally be given a string of the format:
|
// The ifMatchHeader can optionally be given a string of the format:
|
||||||
//
|
//
|
||||||
// "<path> <hash>"
|
// "<path> <hash>"
|
||||||
@@ -411,23 +408,11 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
return ctx, nil
|
return ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
// if newCfg fails to start completely, clean up the already provisioned modules
|
|
||||||
// partially copied from provisionContext
|
|
||||||
if err != nil {
|
|
||||||
globalMetrics.configSuccess.Set(0)
|
|
||||||
ctx.cfg.cancelFunc()
|
|
||||||
|
|
||||||
if currentCtx.cfg != nil {
|
|
||||||
certmagic.Default.Storage = currentCtx.cfg.storage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Provision any admin routers which may need to access
|
// Provision any admin routers which may need to access
|
||||||
// some of the other apps at runtime
|
// some of the other apps at runtime
|
||||||
err = ctx.cfg.Admin.provisionAdminRouters(ctx)
|
err = ctx.cfg.Admin.provisionAdminRouters(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
globalMetrics.configSuccess.Set(0)
|
||||||
return ctx, err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +438,7 @@ 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)
|
||||||
@@ -463,8 +449,7 @@ func run(newCfg *Config, start bool) (Context, error) {
|
|||||||
|
|
||||||
// now that the user's config is running, finish setting up anything else,
|
// now that the user's config is running, finish setting up anything else,
|
||||||
// such as remote admin endpoint, config loader, etc.
|
// such as remote admin endpoint, config loader, etc.
|
||||||
err = finishSettingUp(ctx, ctx.cfg)
|
return ctx, finishSettingUp(ctx, ctx.cfg)
|
||||||
return ctx, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// provisionContext creates a new context from the given configuration and provisions
|
// provisionContext creates a new context from the given configuration and provisions
|
||||||
@@ -525,7 +510,6 @@ func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error)
|
|||||||
|
|
||||||
// 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 {
|
||||||
@@ -975,11 +959,11 @@ func Version() (simple, full string) {
|
|||||||
if CustomVersion != "" {
|
if CustomVersion != "" {
|
||||||
full = CustomVersion
|
full = CustomVersion
|
||||||
simple = CustomVersion
|
simple = CustomVersion
|
||||||
return simple, full
|
return
|
||||||
}
|
}
|
||||||
full = "unknown"
|
full = "unknown"
|
||||||
simple = "unknown"
|
simple = "unknown"
|
||||||
return simple, full
|
return
|
||||||
}
|
}
|
||||||
// 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 {
|
||||||
@@ -1059,7 +1043,7 @@ func Version() (simple, full string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return simple, full
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event represents something that has happened or is happening.
|
// Event represents something that has happened or is happening.
|
||||||
@@ -1092,7 +1076,7 @@ type Event struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewEvent creates a new event, but does not emit the event. To emit an
|
// 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 instead.
|
// event, call Emit() on the current instance of the caddyevents app insteaad.
|
||||||
//
|
//
|
||||||
// EXPERIMENTAL: Subject to change.
|
// EXPERIMENTAL: Subject to change.
|
||||||
func NewEvent(ctx Context, name string, data map[string]any) (Event, error) {
|
func NewEvent(ctx Context, name string, data map[string]any) (Event, error) {
|
||||||
@@ -1120,15 +1104,9 @@ func (e Event) Origin() Module { return e.origin } // Returns the module t
|
|||||||
// CloudEvents spec.
|
// CloudEvents spec.
|
||||||
func (e Event) CloudEvent() CloudEvent {
|
func (e Event) CloudEvent() CloudEvent {
|
||||||
dataJSON, _ := json.Marshal(e.Data)
|
dataJSON, _ := json.Marshal(e.Data)
|
||||||
var source string
|
|
||||||
if e.Origin() == nil {
|
|
||||||
source = "caddy"
|
|
||||||
} else {
|
|
||||||
source = string(e.Origin().CaddyModule().ID)
|
|
||||||
}
|
|
||||||
return CloudEvent{
|
return CloudEvent{
|
||||||
ID: e.id.String(),
|
ID: e.id.String(),
|
||||||
Source: source,
|
Source: e.origin.CaddyModule().String(),
|
||||||
SpecVersion: "1.0",
|
SpecVersion: "1.0",
|
||||||
Type: e.name,
|
Type: e.name,
|
||||||
Time: e.ts,
|
Time: e.ts,
|
||||||
@@ -1197,91 +1175,6 @@ 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,7 +15,6 @@
|
|||||||
package caddy
|
package caddy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -73,21 +72,3 @@ func TestParseDuration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEvent_CloudEvent_NilOrigin(t *testing.T) {
|
|
||||||
ctx, _ := NewContext(Context{Context: context.Background()}) // module will be nil by default
|
|
||||||
event, err := NewEvent(ctx, "started", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("NewEvent() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should not panic
|
|
||||||
ce := event.CloudEvent()
|
|
||||||
|
|
||||||
if ce.Source != "caddy" {
|
|
||||||
t.Errorf("Expected CloudEvent Source to be 'caddy', got '%s'", ce.Source)
|
|
||||||
}
|
|
||||||
if ce.Type != "started" {
|
|
||||||
t.Errorf("Expected CloudEvent Type to be 'started', got '%s'", ce.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func (a Adapter) Adapt(body []byte, options map[string]any) ([]byte, []caddyconf
|
|||||||
// TODO: also perform this check on imported files
|
// TODO: also perform this check on imported files
|
||||||
func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
|
func FormattingDifference(filename string, body []byte) (caddyconfig.Warning, bool) {
|
||||||
// replace windows-style newlines to normalize comparison
|
// replace windows-style newlines to normalize comparison
|
||||||
normalizedBody := bytes.ReplaceAll(body, []byte("\r\n"), []byte("\n"))
|
normalizedBody := bytes.Replace(body, []byte("\r\n"), []byte("\n"), -1)
|
||||||
|
|
||||||
formatted := Format(normalizedBody)
|
formatted := Format(normalizedBody)
|
||||||
if bytes.Equal(formatted, normalizedBody) {
|
if bytes.Equal(formatted, normalizedBody) {
|
||||||
|
|||||||
@@ -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 of strings and returns them. Open curly brace tokens
|
// into a slice and returns them. Open curly brace tokens also indicate
|
||||||
// also indicate the end of arguments, and the curly brace is not
|
// the end of arguments, and the curly brace is not included in
|
||||||
// included in the return value nor is it loaded.
|
// 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 of strings and returns them.
|
// retaining quotes) into a slice and returns them. Open curly brace
|
||||||
// Open curly brace tokens also indicate the end of arguments,
|
// tokens also indicate the end of arguments, and the curly brace is
|
||||||
// and the curly brace is not included in the return value nor is it loaded.
|
// 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,18 +331,6 @@ 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,66 +274,6 @@ 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 {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,16 +52,17 @@ func Format(input []byte) []byte {
|
|||||||
|
|
||||||
newLines int // count of newlines consumed
|
newLines int // count of newlines consumed
|
||||||
|
|
||||||
comment bool // whether we're in a comment
|
comment bool // whether we're in a comment
|
||||||
quotes string // encountered quotes ('', '`', '"', '"`', '`"')
|
quoted bool // whether we're in a quoted segment
|
||||||
escaped bool // whether current char is escaped
|
escaped bool // whether current char is escaped
|
||||||
|
|
||||||
heredoc heredocState // whether we're in a heredoc
|
heredoc heredocState // whether we're in a heredoc
|
||||||
heredocEscaped bool // whether heredoc is escaped
|
heredocEscaped bool // whether heredoc is escaped
|
||||||
heredocMarker []rune
|
heredocMarker []rune
|
||||||
heredocClosingMarker []rune
|
heredocClosingMarker []rune
|
||||||
|
|
||||||
nesting int // indentation level
|
nesting int // indentation level
|
||||||
|
withinBackquote bool
|
||||||
)
|
)
|
||||||
|
|
||||||
write := func(ch rune) {
|
write := func(ch rune) {
|
||||||
@@ -89,8 +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 quotes == "" && (heredoc == heredocClosed && !heredocEscaped) &&
|
if !quoted && !(heredoc != heredocClosed || heredocEscaped) &&
|
||||||
space && last == '<' && ch == '<' {
|
space && last == '<' && ch == '<' {
|
||||||
write(ch)
|
write(ch)
|
||||||
heredoc = heredocOpening
|
heredoc = heredocOpening
|
||||||
@@ -176,47 +180,16 @@ func Format(input []byte) []byte {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ch == '`' {
|
if quoted {
|
||||||
switch quotes {
|
|
||||||
case "\"`":
|
|
||||||
quotes = "\""
|
|
||||||
case "`":
|
|
||||||
quotes = ""
|
|
||||||
case "\"":
|
|
||||||
quotes = "\"`"
|
|
||||||
default:
|
|
||||||
quotes = "`"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if quotes == "\"" {
|
|
||||||
if ch == '"' {
|
if ch == '"' {
|
||||||
quotes = ""
|
quoted = false
|
||||||
}
|
}
|
||||||
write(ch)
|
write(ch)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ch == '"' {
|
if space && ch == '"' {
|
||||||
switch quotes {
|
quoted = true
|
||||||
case "":
|
|
||||||
if space {
|
|
||||||
quotes = "\""
|
|
||||||
}
|
|
||||||
case "`\"":
|
|
||||||
quotes = "`"
|
|
||||||
case "\"`":
|
|
||||||
quotes = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(quotes, "`") {
|
|
||||||
if ch == '`' && space && !beginningOfLine {
|
|
||||||
write(' ')
|
|
||||||
}
|
|
||||||
write(ch)
|
|
||||||
space = false
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if unicode.IsSpace(ch) {
|
if unicode.IsSpace(ch) {
|
||||||
@@ -251,7 +224,7 @@ func Format(input []byte) []byte {
|
|||||||
openBrace = false
|
openBrace = false
|
||||||
if beginningOfLine {
|
if beginningOfLine {
|
||||||
indent()
|
indent()
|
||||||
} else if !openBraceSpace || !unicode.IsSpace(last) {
|
} else if !openBraceSpace {
|
||||||
write(' ')
|
write(' ')
|
||||||
}
|
}
|
||||||
write('{')
|
write('{')
|
||||||
@@ -268,11 +241,11 @@ func Format(input []byte) []byte {
|
|||||||
case ch == '{':
|
case ch == '{':
|
||||||
openBrace = true
|
openBrace = true
|
||||||
openBraceSpace = spacePrior && !beginningOfLine
|
openBraceSpace = spacePrior && !beginningOfLine
|
||||||
if openBraceSpace && newLines == 0 {
|
if openBraceSpace {
|
||||||
write(' ')
|
write(' ')
|
||||||
}
|
}
|
||||||
openBraceWritten = false
|
openBraceWritten = false
|
||||||
if quotes == "`" {
|
if withinBackquote {
|
||||||
write('{')
|
write('{')
|
||||||
openBraceWritten = true
|
openBraceWritten = true
|
||||||
continue
|
continue
|
||||||
@@ -280,7 +253,7 @@ func Format(input []byte) []byte {
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
case ch == '}' && (spacePrior || !openBrace):
|
case ch == '}' && (spacePrior || !openBrace):
|
||||||
if quotes == "`" {
|
if withinBackquote {
|
||||||
write('}')
|
write('}')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -444,37 +444,6 @@ block2 {
|
|||||||
input: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
|
input: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
|
||||||
expect: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
|
expect: "block {respond \"All braces should remain: {{now | date `2006`}}\"}",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
description: "Preserve quoted backticks and backticked quotes",
|
|
||||||
input: "block { respond \"`\" } block { respond `\"`}",
|
|
||||||
expect: "block {\n\trespond \"`\"\n}\n\nblock {\n\trespond `\"`\n}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "No trailing space on line before env variable",
|
|
||||||
input: `{
|
|
||||||
a
|
|
||||||
|
|
||||||
{$ENV_VAR}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
expect: `{
|
|
||||||
a
|
|
||||||
|
|
||||||
{$ENV_VAR}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "issue #7425: multiline backticked string indentation",
|
|
||||||
input: `https://localhost:8953 {
|
|
||||||
respond ` + "`" + `Here are some random numbers:
|
|
||||||
|
|
||||||
{{randNumeric 16}}
|
|
||||||
|
|
||||||
Hope this helps.` + "`" + `
|
|
||||||
}`,
|
|
||||||
expect: "https://localhost:8953 {\n\trespond `Here are some random numbers:\n\n{{randNumeric 16}}\n\nHope this helps.`\n}",
|
|
||||||
},
|
|
||||||
} {
|
} {
|
||||||
// the formatter should output a trailing newline,
|
// the formatter should output a trailing newline,
|
||||||
// even if the tests aren't written to expect that
|
// even if the tests aren't written to expect that
|
||||||
|
|||||||
@@ -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,8 +323,7 @@ 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 {
|
||||||
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, lineText, paddingToStrip)
|
||||||
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,23 +379,28 @@ 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() {
|
||||||
currentMappingKey := bd.Val()
|
// see if we can grab a key
|
||||||
|
var currentMappingKey string
|
||||||
if currentMappingKey == "{" {
|
if bd.Val() == "{" {
|
||||||
return p.Err("anonymous blocks are not supported")
|
return p.Err("anonymous blocks are not supported")
|
||||||
}
|
}
|
||||||
|
currentMappingKey = bd.Val()
|
||||||
// load up all arguments (if there even are any)
|
currentMappingTokens := []Token{}
|
||||||
currentMappingTokens := bd.RemainingArgsAsTokens()
|
// read all args until end of line / {
|
||||||
|
if bd.NextArg() {
|
||||||
// load up the entire block
|
|
||||||
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
|
|
||||||
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
||||||
|
for bd.NextArg() {
|
||||||
|
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
||||||
|
}
|
||||||
|
// TODO(elee1766): we don't enter another mapping here because it's annoying to extract the { and } properly.
|
||||||
|
// maybe someone can do that in the future
|
||||||
|
} else {
|
||||||
|
// attempt to enter a block and add tokens to the currentMappingTokens
|
||||||
|
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
|
||||||
|
currentMappingTokens = append(currentMappingTokens, bd.Token())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockMapping[currentMappingKey] = currentMappingTokens
|
blockMapping[currentMappingKey] = currentMappingTokens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,24 +538,29 @@ 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 foundBlockDirective {
|
if len(tokensToAdd) == 0 {
|
||||||
tokensCopy = append(tokensCopy, tokensToAdd...)
|
// 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...)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,7 +771,7 @@ type ServerBlock struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sb ServerBlock) GetKeysText() []string {
|
func (sb ServerBlock) GetKeysText() []string {
|
||||||
res := make([]string, 0, len(sb.Keys))
|
res := []string{}
|
||||||
for _, k := range sb.Keys {
|
for _, k := range sb.Keys {
|
||||||
res = append(res, k.Text)
|
res = append(res, k.Text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -885,51 +884,6 @@ 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)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,11 +81,7 @@ func JSONModuleObject(val any, fieldName, fieldVal string, warnings *[]Warning)
|
|||||||
err = json.Unmarshal(enc, &tmp)
|
err = json.Unmarshal(enc, &tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if warnings != nil {
|
if warnings != nil {
|
||||||
message := err.Error()
|
*warnings = append(*warnings, Warning{Message: err.Error()})
|
||||||
if jsonErr, ok := err.(*json.SyntaxError); ok {
|
|
||||||
message = fmt.Sprintf("%v, at offset %d", jsonErr.Error(), jsonErr.Offset)
|
|
||||||
}
|
|
||||||
*warnings = append(*warnings, Warning{Message: message})
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -91,7 +90,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
|||||||
// curves <curves...>
|
// curves <curves...>
|
||||||
// client_auth {
|
// client_auth {
|
||||||
// mode [request|require|verify_if_given|require_and_verify]
|
// mode [request|require|verify_if_given|require_and_verify]
|
||||||
// trust_pool <module_name> [...]
|
// trust_pool <module_name> [...]
|
||||||
// trusted_leaf_cert <base64_der>
|
// trusted_leaf_cert <base64_der>
|
||||||
// trusted_leaf_cert_file <filename>
|
// trusted_leaf_cert_file <filename>
|
||||||
// }
|
// }
|
||||||
@@ -113,7 +112,6 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
|||||||
// issuer <module_name> [...]
|
// issuer <module_name> [...]
|
||||||
// get_certificate <module_name> [...]
|
// get_certificate <module_name> [...]
|
||||||
// insecure_secrets_log <log_file>
|
// insecure_secrets_log <log_file>
|
||||||
// renewal_window_ratio <ratio>
|
|
||||||
// }
|
// }
|
||||||
func parseTLS(h Helper) ([]ConfigValue, error) {
|
func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||||
h.Next() // consume directive name
|
h.Next() // consume directive name
|
||||||
@@ -130,10 +128,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
var onDemand bool
|
var onDemand bool
|
||||||
var reusePrivateKeys bool
|
var reusePrivateKeys bool
|
||||||
var forceAutomate bool
|
var forceAutomate bool
|
||||||
var renewalWindowRatio float64
|
|
||||||
|
|
||||||
// Track which DNS challenge options are set
|
|
||||||
var dnsOptionsSet []string
|
|
||||||
|
|
||||||
firstLine := h.RemainingArgs()
|
firstLine := h.RemainingArgs()
|
||||||
switch len(firstLine) {
|
switch len(firstLine) {
|
||||||
@@ -355,7 +349,6 @@ 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":
|
||||||
@@ -377,7 +370,6 @@ 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":
|
||||||
@@ -405,7 +397,6 @@ 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":
|
||||||
@@ -427,7 +418,6 @@ 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":
|
||||||
@@ -444,7 +434,6 @@ 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":
|
||||||
@@ -475,37 +464,11 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
}
|
}
|
||||||
cp.InsecureSecretsLog = h.Val()
|
cp.InsecureSecretsLog = h.Val()
|
||||||
|
|
||||||
case "renewal_window_ratio":
|
|
||||||
arg := h.RemainingArgs()
|
|
||||||
if len(arg) != 1 {
|
|
||||||
return nil, h.ArgErr()
|
|
||||||
}
|
|
||||||
ratio, err := strconv.ParseFloat(arg[0], 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, h.Errf("parsing renewal_window_ratio: %v", err)
|
|
||||||
}
|
|
||||||
if ratio <= 0 || ratio >= 1 {
|
|
||||||
return nil, h.Errf("renewal_window_ratio must be between 0 and 1 (exclusive)")
|
|
||||||
}
|
|
||||||
renewalWindowRatio = ratio
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, h.Errf("unknown subdirective: %s", h.Val())
|
return nil, h.Errf("unknown subdirective: %s", h.Val())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
@@ -613,14 +576,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// renewal window ratio
|
|
||||||
if renewalWindowRatio > 0 {
|
|
||||||
configVals = append(configVals, ConfigValue{
|
|
||||||
Class: "tls.renewal_window_ratio",
|
|
||||||
Value: renewalWindowRatio,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// if enabled, the names in the site addresses will be
|
// if enabled, the names in the site addresses will be
|
||||||
// added to the automation policies
|
// added to the automation policies
|
||||||
if forceAutomate {
|
if forceAutomate {
|
||||||
@@ -888,18 +843,13 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
|
|||||||
return nil, h.Errf("segment was not parsed as a subroute")
|
return nil, h.Errf("segment was not parsed as a subroute")
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap the subroutes
|
|
||||||
wrappingRoute := caddyhttp.Route{
|
|
||||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)},
|
|
||||||
}
|
|
||||||
subroute = &caddyhttp.Subroute{
|
|
||||||
Routes: []caddyhttp.Route{wrappingRoute},
|
|
||||||
}
|
|
||||||
if expression != "" {
|
if expression != "" {
|
||||||
statusMatcher := caddy.ModuleMap{
|
statusMatcher := caddy.ModuleMap{
|
||||||
"expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}),
|
"expression": h.JSON(caddyhttp.MatchExpression{Expr: expression}),
|
||||||
}
|
}
|
||||||
subroute.Routes[0].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
|
for i := range subroute.Routes {
|
||||||
|
subroute.Routes[i].MatcherSetsRaw = []caddy.ModuleMap{statusMatcher}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return []ConfigValue{
|
return []ConfigValue{
|
||||||
{
|
{
|
||||||
@@ -954,7 +904,6 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
|||||||
// modifications to the parsing behavior.
|
// modifications to the parsing behavior.
|
||||||
parseAsGlobalOption := globalLogNames != nil
|
parseAsGlobalOption := globalLogNames != nil
|
||||||
|
|
||||||
// nolint:prealloc
|
|
||||||
var configValues []ConfigValue
|
var configValues []ConfigValue
|
||||||
|
|
||||||
// Logic below expects that a name is always present when a
|
// Logic below expects that a name is always present when a
|
||||||
@@ -1211,11 +1160,6 @@ func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
|||||||
if h.NextArg() {
|
if h.NextArg() {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.NextBlock(0) {
|
|
||||||
return nil, h.Err("log_skip directive does not accept blocks")
|
|
||||||
}
|
|
||||||
|
|
||||||
return caddyhttp.VarsMiddleware{"log_skip": true}, nil
|
return caddyhttp.VarsMiddleware{"log_skip": true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ package httpcaddyfile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"maps"
|
|
||||||
"net"
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -174,12 +173,10 @@ func RegisterDirectiveOrder(dir string, position Positional, standardDir string)
|
|||||||
if d != standardDir {
|
if d != standardDir {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch position {
|
if position == Before {
|
||||||
case Before:
|
|
||||||
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
|
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
|
||||||
case After:
|
} else if position == After {
|
||||||
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
|
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
|
||||||
case First, Last:
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -368,7 +365,9 @@ func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
|
|||||||
// copy existing matcher definitions so we can augment
|
// copy existing matcher definitions so we can augment
|
||||||
// new ones that are defined only in this scope
|
// new ones that are defined only in this scope
|
||||||
matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
|
matcherDefs := make(map[string]caddy.ModuleMap, len(h.matcherDefs))
|
||||||
maps.Copy(matcherDefs, h.matcherDefs)
|
for key, val := range h.matcherDefs {
|
||||||
|
matcherDefs[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
// find and extract any embedded matcher definitions in this scope
|
// find and extract any embedded matcher definitions in this scope
|
||||||
for i := 0; i < len(segments); i++ {
|
for i := 0; i < len(segments); i++ {
|
||||||
@@ -484,29 +483,12 @@ func sortRoutes(routes []ConfigValue) {
|
|||||||
// we can only confidently compare path lengths if both
|
// we can only confidently compare path lengths if both
|
||||||
// directives have a single path to match (issue #5037)
|
// directives have a single path to match (issue #5037)
|
||||||
if iPathLen > 0 && jPathLen > 0 {
|
if iPathLen > 0 && jPathLen > 0 {
|
||||||
// trim the trailing wildcard if there is one
|
|
||||||
iPathTrimmed := strings.TrimSuffix(iPM[0], "*")
|
|
||||||
jPathTrimmed := strings.TrimSuffix(jPM[0], "*")
|
|
||||||
|
|
||||||
// if both paths are the same except for a trailing wildcard,
|
// if both paths are the same except for a trailing wildcard,
|
||||||
// sort by the shorter path first (which is more specific)
|
// sort by the shorter path first (which is more specific)
|
||||||
if iPathTrimmed == jPathTrimmed {
|
if strings.TrimSuffix(iPM[0], "*") == strings.TrimSuffix(jPM[0], "*") {
|
||||||
return iPathLen < jPathLen
|
return iPathLen < jPathLen
|
||||||
}
|
}
|
||||||
|
|
||||||
// we use the trimmed length to compare the paths
|
|
||||||
// https://github.com/caddyserver/caddy/issues/7012#issuecomment-2870142195
|
|
||||||
// credit to https://github.com/Hellio404
|
|
||||||
// for sorts with many items, mixing matchers w/ and w/o wildcards will confuse the sort and result in incorrect orders
|
|
||||||
iPathLen = len(iPathTrimmed)
|
|
||||||
jPathLen = len(jPathTrimmed)
|
|
||||||
|
|
||||||
// if both paths have the same length, sort lexically
|
|
||||||
// https://github.com/caddyserver/caddy/pull/7015#issuecomment-2871993588
|
|
||||||
if iPathLen == jPathLen {
|
|
||||||
return iPathTrimmed < jPathTrimmed
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort most-specific (longest) path first
|
// sort most-specific (longest) path first
|
||||||
return iPathLen > jPathLen
|
return iPathLen > jPathLen
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -851,20 +851,6 @@ func (st *ServerType) serversFromPairings(
|
|||||||
srv.ListenerWrappersRaw = append(srv.ListenerWrappersRaw, jsonListenerWrapper)
|
srv.ListenerWrappersRaw = append(srv.ListenerWrappersRaw, jsonListenerWrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for any config values that provide packet conn wrappers on the server block
|
|
||||||
for _, listenerConfig := range sblock.pile["packet_conn_wrapper"] {
|
|
||||||
packetConnWrapper, ok := listenerConfig.Value.(caddy.PacketConnWrapper)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("config for a packet conn wrapper did not provide a value that implements caddy.PacketConnWrapper")
|
|
||||||
}
|
|
||||||
jsonPacketConnWrapper := caddyconfig.JSONModuleObject(
|
|
||||||
packetConnWrapper,
|
|
||||||
"wrapper",
|
|
||||||
packetConnWrapper.(caddy.Module).CaddyModule().ID.Name(),
|
|
||||||
warnings)
|
|
||||||
srv.PacketConnWrappersRaw = append(srv.PacketConnWrappersRaw, jsonPacketConnWrapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up each handler directive, making sure to honor directive order
|
// set up each handler directive, making sure to honor directive order
|
||||||
dirRoutes := sblock.pile["route"]
|
dirRoutes := sblock.pile["route"]
|
||||||
siteSubroute, err := buildSubroute(dirRoutes, groupCounter, true)
|
siteSubroute, err := buildSubroute(dirRoutes, groupCounter, true)
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ func init() {
|
|||||||
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
||||||
RegisterGlobalOption("dns", parseOptDNS)
|
RegisterGlobalOption("dns", parseOptDNS)
|
||||||
RegisterGlobalOption("ech", parseOptECH)
|
RegisterGlobalOption("ech", parseOptECH)
|
||||||
RegisterGlobalOption("renewal_window_ratio", parseOptRenewalWindowRatio)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
func parseOptTrue(d *caddyfile.Dispenser, _ any) (any, error) { return true, nil }
|
||||||
@@ -458,8 +457,11 @@ func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
case "disable_redirects":
|
case "disable_redirects":
|
||||||
case "disable_certs":
|
case "disable_certs":
|
||||||
case "ignore_loaded_certs":
|
case "ignore_loaded_certs":
|
||||||
|
case "prefer_wildcard":
|
||||||
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
|
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
@@ -472,8 +474,6 @@ func unmarshalCaddyfileMetricsOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
switch d.Val() {
|
switch d.Val() {
|
||||||
case "per_host":
|
case "per_host":
|
||||||
metrics.PerHost = true
|
metrics.PerHost = true
|
||||||
case "observe_catchall_hosts":
|
|
||||||
metrics.ObserveCatchallHosts = true
|
|
||||||
default:
|
default:
|
||||||
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
|
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
|
||||||
}
|
}
|
||||||
@@ -557,14 +557,8 @@ func parseOptPreferredChains(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
|
|
||||||
func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
d.Next() // consume option name
|
d.Next() // consume option name
|
||||||
optName := d.Val()
|
|
||||||
|
|
||||||
// get DNS module name
|
if !d.Next() { // 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()
|
return nil, d.ArgErr()
|
||||||
}
|
}
|
||||||
modID := "dns.providers." + d.Val()
|
modID := "dns.providers." + d.Val()
|
||||||
@@ -625,22 +619,3 @@ func parseOptECH(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
|
|
||||||
return ech, nil
|
return ech, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptRenewalWindowRatio(d *caddyfile.Dispenser, _ any) (any, error) {
|
|
||||||
d.Next() // consume option name
|
|
||||||
if !d.Next() {
|
|
||||||
return 0, d.ArgErr()
|
|
||||||
}
|
|
||||||
val := d.Val()
|
|
||||||
ratio, err := strconv.ParseFloat(val, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, d.Errf("parsing renewal_window_ratio: %v", err)
|
|
||||||
}
|
|
||||||
if ratio <= 0 || ratio >= 1 {
|
|
||||||
return 0, d.Errf("renewal_window_ratio must be between 0 and 1 (exclusive)")
|
|
||||||
}
|
|
||||||
if d.Next() {
|
|
||||||
return 0, d.ArgErr()
|
|
||||||
}
|
|
||||||
return ratio, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,9 +15,6 @@
|
|||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"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"
|
||||||
@@ -28,16 +25,14 @@ func init() {
|
|||||||
RegisterGlobalOption("pki", parsePKIApp)
|
RegisterGlobalOption("pki", parsePKIApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePKIApp parses the global pki option. Syntax:
|
// parsePKIApp parses the global log option. Syntax:
|
||||||
//
|
//
|
||||||
// pki {
|
// pki {
|
||||||
// ca [<id>] {
|
// ca [<id>] {
|
||||||
// name <name>
|
// name <name>
|
||||||
// root_cn <name>
|
// root_cn <name>
|
||||||
// intermediate_cn <name>
|
// intermediate_cn <name>
|
||||||
// intermediate_lifetime <duration>
|
// intermediate_lifetime <duration>
|
||||||
// maintenance_interval <duration>
|
|
||||||
// renewal_window_ratio <ratio>
|
|
||||||
// root {
|
// root {
|
||||||
// cert <path>
|
// cert <path>
|
||||||
// key <path>
|
// key <path>
|
||||||
@@ -102,26 +97,6 @@ func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) {
|
|||||||
}
|
}
|
||||||
pkiCa.IntermediateLifetime = caddy.Duration(dur)
|
pkiCa.IntermediateLifetime = caddy.Duration(dur)
|
||||||
|
|
||||||
case "maintenance_interval":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
dur, err := caddy.ParseDuration(d.Val())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pkiCa.MaintenanceInterval = caddy.Duration(dur)
|
|
||||||
|
|
||||||
case "renewal_window_ratio":
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
ratio, err := strconv.ParseFloat(d.Val(), 64)
|
|
||||||
if err != nil || ratio <= 0 || ratio > 1 {
|
|
||||||
return nil, d.Errf("renewal_window_ratio must be a number in (0, 1], got %s", d.Val())
|
|
||||||
}
|
|
||||||
pkiCa.RenewalWindowRatio = ratio
|
|
||||||
|
|
||||||
case "root":
|
case "root":
|
||||||
if pkiCa.Root == nil {
|
if pkiCa.Root == nil {
|
||||||
pkiCa.Root = new(caddypki.KeyPair)
|
pkiCa.Root = new(caddypki.KeyPair)
|
||||||
@@ -203,15 +178,6 @@ 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
|
||||||
@@ -252,8 +218,7 @@ 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
|
||||||
// only if auto_https is not completely disabled
|
if len(pkiApp.CAs) == 0 && skipInstallTrust {
|
||||||
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
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package httpcaddyfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParsePKIApp_maintenanceIntervalAndRenewalWindowRatio(t *testing.T) {
|
|
||||||
input := `{
|
|
||||||
pki {
|
|
||||||
ca local {
|
|
||||||
maintenance_interval 5m
|
|
||||||
renewal_window_ratio 0.15
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:8080 {
|
|
||||||
}
|
|
||||||
`
|
|
||||||
adapter := caddyfile.Adapter{ServerType: ServerType{}}
|
|
||||||
out, _, err := adapter.Adapt([]byte(input), nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Adapt failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg struct {
|
|
||||||
Apps struct {
|
|
||||||
PKI struct {
|
|
||||||
CertificateAuthorities map[string]struct {
|
|
||||||
MaintenanceInterval int64 `json:"maintenance_interval,omitempty"`
|
|
||||||
RenewalWindowRatio float64 `json:"renewal_window_ratio,omitempty"`
|
|
||||||
} `json:"certificate_authorities,omitempty"`
|
|
||||||
} `json:"pki,omitempty"`
|
|
||||||
} `json:"apps"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(out, &cfg); err != nil {
|
|
||||||
t.Fatalf("unmarshal config: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ca, ok := cfg.Apps.PKI.CertificateAuthorities["local"]
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("expected certificate_authorities.local to exist")
|
|
||||||
}
|
|
||||||
wantInterval := 5 * time.Minute.Nanoseconds()
|
|
||||||
if ca.MaintenanceInterval != wantInterval {
|
|
||||||
t.Errorf("maintenance_interval = %d, want %d (5m)", ca.MaintenanceInterval, wantInterval)
|
|
||||||
}
|
|
||||||
if ca.RenewalWindowRatio != 0.15 {
|
|
||||||
t.Errorf("renewal_window_ratio = %v, want 0.15", ca.RenewalWindowRatio)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParsePKIApp_renewalWindowRatioInvalid(t *testing.T) {
|
|
||||||
input := `{
|
|
||||||
pki {
|
|
||||||
ca local {
|
|
||||||
renewal_window_ratio 1.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
:8080 {
|
|
||||||
}
|
|
||||||
`
|
|
||||||
adapter := caddyfile.Adapter{ServerType: ServerType{}}
|
|
||||||
_, _, err := adapter.Adapt([]byte(input), nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error for renewal_window_ratio > 1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
|
||||||
@@ -36,30 +35,23 @@ type serverOptions struct {
|
|||||||
ListenerAddress string
|
ListenerAddress string
|
||||||
|
|
||||||
// These will all map 1:1 to the caddyhttp.Server struct
|
// These will all map 1:1 to the caddyhttp.Server struct
|
||||||
Name string
|
Name string
|
||||||
ListenerWrappersRaw []json.RawMessage
|
ListenerWrappersRaw []json.RawMessage
|
||||||
PacketConnWrappersRaw []json.RawMessage
|
ReadTimeout caddy.Duration
|
||||||
ReadTimeout caddy.Duration
|
ReadHeaderTimeout caddy.Duration
|
||||||
ReadHeaderTimeout caddy.Duration
|
WriteTimeout caddy.Duration
|
||||||
WriteTimeout caddy.Duration
|
IdleTimeout caddy.Duration
|
||||||
IdleTimeout caddy.Duration
|
KeepAliveInterval caddy.Duration
|
||||||
KeepAliveInterval caddy.Duration
|
MaxHeaderBytes int
|
||||||
KeepAliveIdle caddy.Duration
|
EnableFullDuplex bool
|
||||||
KeepAliveCount int
|
Protocols []string
|
||||||
MaxHeaderBytes int
|
StrictSNIHost *bool
|
||||||
EnableFullDuplex bool
|
TrustedProxiesRaw json.RawMessage
|
||||||
Protocols []string
|
TrustedProxiesStrict int
|
||||||
StrictSNIHost *bool
|
ClientIPHeaders []string
|
||||||
TrustedProxiesRaw json.RawMessage
|
ShouldLogCredentials bool
|
||||||
TrustedProxiesStrict int
|
Metrics *caddyhttp.Metrics
|
||||||
TrustedProxiesUnix bool
|
Trace bool // TODO: EXPERIMENTAL
|
||||||
ClientIPHeaders []string
|
|
||||||
ShouldLogCredentials bool
|
|
||||||
Metrics *caddyhttp.Metrics
|
|
||||||
Trace bool // TODO: EXPERIMENTAL
|
|
||||||
// If set, overrides whether QUIC listeners allow 0-RTT (early data).
|
|
||||||
// If nil, the default behavior is used (currently allowed).
|
|
||||||
Allow0RTT *bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||||
@@ -103,26 +95,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper)
|
serverOpts.ListenerWrappersRaw = append(serverOpts.ListenerWrappersRaw, jsonListenerWrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "packet_conn_wrappers":
|
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
|
||||||
modID := "caddy.packetconns." + d.Val()
|
|
||||||
unm, err := caddyfile.UnmarshalModule(d, modID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
packetConnWrapper, ok := unm.(caddy.PacketConnWrapper)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("module %s (%T) is not a packet conn wrapper", modID, unm)
|
|
||||||
}
|
|
||||||
jsonPacketConnWrapper := caddyconfig.JSONModuleObject(
|
|
||||||
packetConnWrapper,
|
|
||||||
"wrapper",
|
|
||||||
packetConnWrapper.(caddy.Module).CaddyModule().ID.Name(),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
serverOpts.PacketConnWrappersRaw = append(serverOpts.PacketConnWrappersRaw, jsonPacketConnWrapper)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "timeouts":
|
case "timeouts":
|
||||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
switch d.Val() {
|
switch d.Val() {
|
||||||
@@ -170,7 +142,6 @@ 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()
|
||||||
@@ -181,26 +152,6 @@ 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) {
|
||||||
@@ -276,12 +227,6 @@ 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 {
|
||||||
@@ -312,17 +257,6 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
}
|
}
|
||||||
serverOpts.Trace = true
|
serverOpts.Trace = true
|
||||||
|
|
||||||
case "0rtt":
|
|
||||||
// only supports "off" for now
|
|
||||||
if !d.NextArg() {
|
|
||||||
return nil, d.ArgErr()
|
|
||||||
}
|
|
||||||
if d.Val() != "off" {
|
|
||||||
return nil, d.Errf("unsupported 0rtt argument '%s' (only 'off' is supported)", d.Val())
|
|
||||||
}
|
|
||||||
boolVal := false
|
|
||||||
serverOpts.Allow0RTT = &boolVal
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
|
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
|
||||||
}
|
}
|
||||||
@@ -370,14 +304,11 @@ func applyServerOptions(
|
|||||||
|
|
||||||
// set all the options
|
// set all the options
|
||||||
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
|
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
|
||||||
server.PacketConnWrappersRaw = opts.PacketConnWrappersRaw
|
|
||||||
server.ReadTimeout = opts.ReadTimeout
|
server.ReadTimeout = opts.ReadTimeout
|
||||||
server.ReadHeaderTimeout = opts.ReadHeaderTimeout
|
server.ReadHeaderTimeout = opts.ReadHeaderTimeout
|
||||||
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
|
||||||
@@ -385,9 +316,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
|
||||||
server.Allow0RTT = opts.Allow0RTT
|
|
||||||
if opts.ShouldLogCredentials {
|
if opts.ShouldLogCredentials {
|
||||||
if server.Logs == nil {
|
if server.Logs == nil {
|
||||||
server.Logs = new(caddyhttp.ServerLogConfig)
|
server.Logs = new(caddyhttp.ServerLogConfig)
|
||||||
|
|||||||
@@ -64,13 +64,10 @@ 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,8 +92,26 @@ func (st ServerType) buildTLSApp(
|
|||||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
|
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wildcardHosts []string // collect all hosts that have a wildcard in them, and aren't HTTP
|
||||||
forcedAutomatedNames := make(map[string]struct{}) // explicitly configured to be automated, even if covered by a wildcard
|
forcedAutomatedNames := make(map[string]struct{}) // explicitly configured to be automated, even if covered by a wildcard
|
||||||
|
|
||||||
|
for _, p := range pairings {
|
||||||
|
var addresses []string
|
||||||
|
for _, addressWithProtocols := range p.addressesWithProtocols {
|
||||||
|
addresses = append(addresses, addressWithProtocols.address)
|
||||||
|
}
|
||||||
|
if !listenersUseAnyPortOtherThan(addresses, httpPort) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, sblock := range p.serverBlocks {
|
||||||
|
for _, addr := range sblock.parsedKeys {
|
||||||
|
if strings.HasPrefix(addr.Host, "*.") {
|
||||||
|
wildcardHosts = append(wildcardHosts, addr.Host[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range pairings {
|
for _, p := range pairings {
|
||||||
// avoid setting up TLS automation policies for a server that is HTTP-only
|
// avoid setting up TLS automation policies for a server that is HTTP-only
|
||||||
var addresses []string
|
var addresses []string
|
||||||
@@ -117,6 +135,12 @@ func (st ServerType) buildTLSApp(
|
|||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make a plain copy so we can compare whether we made any changes
|
||||||
|
apCopy, err := newBaseAutomationPolicy(options, warnings, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, warnings, err
|
||||||
|
}
|
||||||
|
|
||||||
sblockHosts := sblock.hostsFromKeys(false)
|
sblockHosts := sblock.hostsFromKeys(false)
|
||||||
if len(sblockHosts) == 0 && catchAllAP != nil {
|
if len(sblockHosts) == 0 && catchAllAP != nil {
|
||||||
ap = catchAllAP
|
ap = catchAllAP
|
||||||
@@ -143,12 +167,6 @@ func (st ServerType) buildTLSApp(
|
|||||||
ap.KeyType = keyTypeVals[0].Value.(string)
|
ap.KeyType = keyTypeVals[0].Value.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if renewalWindowRatioVals, ok := sblock.pile["tls.renewal_window_ratio"]; ok {
|
|
||||||
ap.RenewalWindowRatio = renewalWindowRatioVals[0].Value.(float64)
|
|
||||||
} else if globalRenewalWindowRatio, ok := options["renewal_window_ratio"]; ok {
|
|
||||||
ap.RenewalWindowRatio = globalRenewalWindowRatio.(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// certificate issuers
|
// certificate issuers
|
||||||
if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
|
if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
|
||||||
var issuers []certmagic.Issuer
|
var issuers []certmagic.Issuer
|
||||||
@@ -235,6 +253,16 @@ func (st ServerType) buildTLSApp(
|
|||||||
hostsNotHTTP := sblock.hostsFromKeysNotHTTP(httpPort)
|
hostsNotHTTP := sblock.hostsFromKeysNotHTTP(httpPort)
|
||||||
sort.Strings(hostsNotHTTP) // solely for deterministic test results
|
sort.Strings(hostsNotHTTP) // solely for deterministic test results
|
||||||
|
|
||||||
|
// if the we prefer wildcards and the AP is unchanged,
|
||||||
|
// then we can skip this AP because it should be covered
|
||||||
|
// by an AP with a wildcard
|
||||||
|
if slices.Contains(autoHTTPS, "prefer_wildcard") {
|
||||||
|
if hostsCoveredByWildcard(hostsNotHTTP, wildcardHosts) &&
|
||||||
|
reflect.DeepEqual(ap, apCopy) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// associate our new automation policy with this server block's hosts
|
// associate our new automation policy with this server block's hosts
|
||||||
ap.SubjectsRaw = hostsNotHTTP
|
ap.SubjectsRaw = hostsNotHTTP
|
||||||
|
|
||||||
@@ -436,10 +464,10 @@ 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"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
|
globalACMEDNS := options["acme_dns"]
|
||||||
globalACMEEAB := options["acme_eab"]
|
globalACMEEAB := options["acme_eab"]
|
||||||
globalPreferredChains := options["preferred_chains"]
|
globalPreferredChains := options["preferred_chains"]
|
||||||
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS || globalACMEEAB != nil || globalPreferredChains != nil
|
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil
|
||||||
if hasGlobalACMEDefaults {
|
if hasGlobalACMEDefaults {
|
||||||
for i := range tlsApp.Automation.Policies {
|
for i := range tlsApp.Automation.Policies {
|
||||||
ap := tlsApp.Automation.Policies[i]
|
ap := tlsApp.Automation.Policies[i]
|
||||||
@@ -521,12 +549,11 @@ 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, globalACMEDNSok := options["acme_dns"] // can be set to nil (to use globally-defined "dns" value instead), but it is still set
|
globalACMEDNS := options["acme_dns"]
|
||||||
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)
|
||||||
@@ -537,20 +564,11 @@ 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 globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil || acmeIssuer.Challenges.DNS.ProviderRaw == nil) {
|
if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
|
||||||
globalDNS := options["dns"]
|
acmeIssuer.Challenges = &caddytls.ChallengesConfig{
|
||||||
if globalDNS == nil && globalACMEDNS == nil {
|
DNS: &caddytls.DNSChallengeConfig{
|
||||||
return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
|
ProviderRaw: caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil),
|
||||||
}
|
},
|
||||||
if acmeIssuer.Challenges == nil {
|
|
||||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
|
||||||
}
|
|
||||||
if acmeIssuer.Challenges.DNS == nil {
|
|
||||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
|
||||||
}
|
|
||||||
if globalACMEDNS != nil && acmeIssuer.Challenges.DNS.ProviderRaw == nil {
|
|
||||||
// Set a global DNS provider if `acme_dns` is 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 {
|
||||||
@@ -578,20 +596,6 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -612,19 +616,12 @@ 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"]
|
||||||
renewalWindowRatio, hasRenewalWindowRatio := options["renewal_window_ratio"]
|
|
||||||
hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling || hasRenewalWindowRatio
|
|
||||||
|
|
||||||
globalACMECA := options["acme_ca"]
|
hasGlobalAutomationOpts := hasIssuers || hasLocalCerts || hasKeyType || hasOCSPStapling
|
||||||
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 && !hasGlobalACMEDefaults {
|
if !hasGlobalAutomationOpts {
|
||||||
if always {
|
if always {
|
||||||
return new(caddytls.AutomationPolicy), nil
|
return new(caddytls.AutomationPolicy), nil
|
||||||
}
|
}
|
||||||
@@ -646,24 +643,12 @@ 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
|
||||||
ap.OCSPOverrides = ocspConfig.ResponderOverrides
|
ap.OCSPOverrides = ocspConfig.ResponderOverrides
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasRenewalWindowRatio {
|
|
||||||
ap.RenewalWindowRatio = renewalWindowRatio.(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ap, nil
|
return ap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,3 +810,20 @@ func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
|||||||
func isTailscaleDomain(name string) bool {
|
func isTailscaleDomain(name string) bool {
|
||||||
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
|
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hostsCoveredByWildcard(hosts []string, wildcards []string) bool {
|
||||||
|
if len(hosts) == 0 || len(wildcards) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, host := range hosts {
|
||||||
|
for _, wildcard := range wildcards {
|
||||||
|
if strings.HasPrefix(host, "*.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if certmagic.MatchWildcard(host, "*."+wildcard) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func (hl HTTPLoader) LoadConfig(ctx caddy.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func attemptHttpCall(client *http.Client, request *http.Request) (*http.Response, error) {
|
func attemptHttpCall(client *http.Client, request *http.Request) (*http.Response, error) {
|
||||||
resp, err := client.Do(request) //nolint:gosec // no SSRF; comes from trusted config
|
resp, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("problem calling http loader url: %v", err)
|
return nil, fmt.Errorf("problem calling http loader url: %v", err)
|
||||||
} else if resp.StatusCode < 200 || resp.StatusCode > 499 {
|
} else if resp.StatusCode < 200 || resp.StatusCode > 499 {
|
||||||
|
|||||||
+1
-8
@@ -106,7 +106,7 @@ func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
caddy.Log().Named("admin.api.load").Error(err.Error())
|
caddy.Log().Named("admin.api.load").Error(err.Error())
|
||||||
}
|
}
|
||||||
_, _ = w.Write(respBody) //nolint:gosec // false positive: no XSS here
|
_, _ = w.Write(respBody)
|
||||||
}
|
}
|
||||||
body = result
|
body = result
|
||||||
}
|
}
|
||||||
@@ -121,13 +121,6 @@ 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
|
||||||
|
|||||||
+4
-26
@@ -187,7 +187,7 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
|||||||
req.Header.Add("Content-Type", "text/"+configType)
|
req.Header.Add("Content-Type", "text/"+configType)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := client.Do(req) //nolint:gosec // no SSRF because URL is hard-coded to localhost, and port comes from config
|
res, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("unable to contact caddy server. %s", err)
|
tc.t.Errorf("unable to contact caddy server. %s", err)
|
||||||
return err
|
return err
|
||||||
@@ -279,9 +279,9 @@ func validateTestPrerequisites(tc *Tester) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tc.t.Cleanup(func() {
|
tc.t.Cleanup(func() {
|
||||||
os.Remove(f.Name()) //nolint:gosec // false positive, filename comes from std lib, no path traversal
|
os.Remove(f.Name())
|
||||||
})
|
})
|
||||||
if _, err := fmt.Fprintf(f, initConfig, tc.config.AdminPort); err != nil {
|
if _, err := f.WriteString(fmt.Sprintf(initConfig, tc.config.AdminPort)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,8 +362,6 @@ func CreateTestingTransport() *http.Transport {
|
|||||||
|
|
||||||
// AssertLoadError will load a config and expect an error
|
// AssertLoadError will load a config and expect an error
|
||||||
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
|
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
tc := NewTester(t)
|
tc := NewTester(t)
|
||||||
|
|
||||||
err := tc.initServer(rawConfig, configType)
|
err := tc.initServer(rawConfig, configType)
|
||||||
@@ -374,8 +372,6 @@ func AssertLoadError(t *testing.T, rawConfig string, configType string, expected
|
|||||||
|
|
||||||
// AssertRedirect makes a request and asserts the redirection happens
|
// AssertRedirect makes a request and asserts the redirection happens
|
||||||
func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
||||||
tc.t.Helper()
|
|
||||||
|
|
||||||
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
|
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
}
|
}
|
||||||
@@ -413,8 +409,6 @@ func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, e
|
|||||||
|
|
||||||
// CompareAdapt adapts a config and then compares it against an expected result
|
// CompareAdapt adapts a config and then compares it against an expected result
|
||||||
func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName string, expectedResponse string) bool {
|
func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName string, expectedResponse string) bool {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
cfgAdapter := caddyconfig.GetAdapter(adapterName)
|
cfgAdapter := caddyconfig.GetAdapter(adapterName)
|
||||||
if cfgAdapter == nil {
|
if cfgAdapter == nil {
|
||||||
t.Logf("unrecognized config adapter '%s'", adapterName)
|
t.Logf("unrecognized config adapter '%s'", adapterName)
|
||||||
@@ -474,8 +468,6 @@ func CompareAdapt(t testing.TB, filename, rawConfig string, adapterName string,
|
|||||||
|
|
||||||
// AssertAdapt adapts a config and then tests it against an expected result
|
// AssertAdapt adapts a config and then tests it against an expected result
|
||||||
func AssertAdapt(t testing.TB, rawConfig string, adapterName string, expectedResponse string) {
|
func AssertAdapt(t testing.TB, rawConfig string, adapterName string, expectedResponse string) {
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse)
|
ok := CompareAdapt(t, "Caddyfile", rawConfig, adapterName, expectedResponse)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@@ -504,9 +496,7 @@ func applyHeaders(t testing.TB, req *http.Request, requestHeaders []string) {
|
|||||||
|
|
||||||
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
|
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
|
||||||
func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
|
func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
|
||||||
tc.t.Helper()
|
resp, err := tc.Client.Do(req)
|
||||||
|
|
||||||
resp, err := tc.Client.Do(req) //nolint:gosec // no SSRFs demonstrated
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Fatalf("failed to call server %s", err)
|
tc.t.Fatalf("failed to call server %s", err)
|
||||||
}
|
}
|
||||||
@@ -520,8 +510,6 @@ func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int)
|
|||||||
|
|
||||||
// AssertResponse request a URI and assert the status code and the body contains a string
|
// AssertResponse request a URI and assert the status code and the body contains a string
|
||||||
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||||
tc.t.Helper()
|
|
||||||
|
|
||||||
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
@@ -543,8 +531,6 @@ func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expe
|
|||||||
|
|
||||||
// AssertGetResponse GET a URI and expect a statusCode and body text
|
// AssertGetResponse GET a URI and expect a statusCode and body text
|
||||||
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||||
tc.t.Helper()
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", requestURI, nil)
|
req, err := http.NewRequest("GET", requestURI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Fatalf("unable to create request %s", err)
|
tc.t.Fatalf("unable to create request %s", err)
|
||||||
@@ -555,8 +541,6 @@ func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, e
|
|||||||
|
|
||||||
// AssertDeleteResponse request a URI and expect a statusCode and body text
|
// AssertDeleteResponse request a URI and expect a statusCode and body text
|
||||||
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||||
tc.t.Helper()
|
|
||||||
|
|
||||||
req, err := http.NewRequest("DELETE", requestURI, nil)
|
req, err := http.NewRequest("DELETE", requestURI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Fatalf("unable to create request %s", err)
|
tc.t.Fatalf("unable to create request %s", err)
|
||||||
@@ -567,8 +551,6 @@ func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int
|
|||||||
|
|
||||||
// AssertPostResponseBody POST to a URI and assert the response code and body
|
// AssertPostResponseBody POST to a URI and assert the response code and body
|
||||||
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||||
tc.t.Helper()
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", requestURI, requestBody)
|
req, err := http.NewRequest("POST", requestURI, requestBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("failed to create request %s", err)
|
tc.t.Errorf("failed to create request %s", err)
|
||||||
@@ -582,8 +564,6 @@ func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []str
|
|||||||
|
|
||||||
// AssertPutResponseBody PUT to a URI and assert the response code and body
|
// AssertPutResponseBody PUT to a URI and assert the response code and body
|
||||||
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||||
tc.t.Helper()
|
|
||||||
|
|
||||||
req, err := http.NewRequest("PUT", requestURI, requestBody)
|
req, err := http.NewRequest("PUT", requestURI, requestBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("failed to create request %s", err)
|
tc.t.Errorf("failed to create request %s", err)
|
||||||
@@ -597,8 +577,6 @@ func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []stri
|
|||||||
|
|
||||||
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
|
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
|
||||||
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||||
tc.t.Helper()
|
|
||||||
|
|
||||||
req, err := http.NewRequest("PATCH", requestURI, requestBody)
|
req, err := http.NewRequest("PATCH", requestURI, requestBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tc.t.Errorf("failed to create request %s", err)
|
tc.t.Errorf("failed to create request %s", err)
|
||||||
|
|||||||
@@ -12,14 +12,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
"github.com/mholt/acmez/v3"
|
"github.com/mholt/acmez/v3"
|
||||||
"github.com/mholt/acmez/v3/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
smallstepacme "github.com/smallstep/certificates/acme"
|
smallstepacme "github.com/smallstep/certificates/acme"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/exp/zapslog"
|
"go.uber.org/zap/exp/zapslog"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
|
||||||
"github.com/caddyserver/caddy/v2/caddytest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const acmeChallengePort = 9081
|
const acmeChallengePort = 9081
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
"github.com/mholt/acmez/v3"
|
"github.com/mholt/acmez/v3"
|
||||||
"github.com/mholt/acmez/v3/acme"
|
"github.com/mholt/acmez/v3/acme"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/exp/zapslog"
|
"go.uber.org/zap/exp/zapslog"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddytest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestACMEServerDirectory(t *testing.T) {
|
func TestACMEServerDirectory(t *testing.T) {
|
||||||
@@ -127,7 +126,7 @@ func TestACMEServerAllowPolicy(t *testing.T) {
|
|||||||
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"not-matching.localhost"})
|
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"not-matching.localhost"})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("obtaining certificate for 'not-matching.localhost' domain")
|
t.Errorf("obtaining certificate for 'not-matching.localhost' domain")
|
||||||
} else if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
|
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
|
||||||
t.Logf("unexpected error: %v", err)
|
t.Logf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,7 +199,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
|
|||||||
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"})
|
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("obtaining certificate for 'deny.localhost' domain")
|
t.Errorf("obtaining certificate for 'deny.localhost' domain")
|
||||||
} else if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
|
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
|
||||||
t.Logf("unexpected error: %v", err)
|
t.Logf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
{
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
acme_dns
|
|
||||||
}
|
|
||||||
|
|
||||||
example.com {
|
|
||||||
respond "Hello World"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
acme_dns specified without DNS provider config, but no provider specified with 'dns' global option
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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.
|
|
||||||
-9
@@ -1,9 +0,0 @@
|
|||||||
:8080 {
|
|
||||||
respond "one"
|
|
||||||
}
|
|
||||||
|
|
||||||
:8080 {
|
|
||||||
respond "two"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
ambiguous site definition: :8080
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
servers {
|
|
||||||
srv0 {
|
|
||||||
listen :8080
|
|
||||||
}
|
|
||||||
srv1 {
|
|
||||||
listen :8080
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
parsing caddyfile tokens for 'servers': unrecognized servers option 'srv0', at Caddyfile:3
|
|
||||||
@@ -106,29 +106,20 @@ example.com {
|
|||||||
"handler": "subroute",
|
"handler": "subroute",
|
||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
|
"group": "group0",
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"handler": "rewrite",
|
||||||
"routes": [
|
"uri": "/{http.error.status_code}.html"
|
||||||
{
|
}
|
||||||
"group": "group0",
|
]
|
||||||
"handle": [
|
},
|
||||||
{
|
{
|
||||||
"handler": "rewrite",
|
"handle": [
|
||||||
"uri": "/{http.error.status_code}.html"
|
{
|
||||||
}
|
"handler": "file_server",
|
||||||
]
|
"hide": [
|
||||||
},
|
"./Caddyfile"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "file_server",
|
|
||||||
"hide": [
|
|
||||||
"./Caddyfile"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -165,17 +165,8 @@ bar.localhost {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "404 or 410 error",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "404 or 410 error",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
@@ -187,17 +178,8 @@ bar.localhost {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "Error In range [500 .. 599]",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Error In range [500 .. 599]",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
@@ -226,17 +208,8 @@ bar.localhost {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "404 or 410 error from second site",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "404 or 410 error from second site",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
@@ -248,17 +221,8 @@ bar.localhost {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "Error In range [500 .. 599] from second site",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Error In range [500 .. 599] from second site",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
|
|||||||
@@ -96,17 +96,8 @@ localhost:3010 {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "Error in the [400 .. 499] range",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Error in the [400 .. 499] range",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
|
|||||||
@@ -116,17 +116,8 @@ localhost:2099 {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "Error in the [400 .. 499] range",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Error in the [400 .. 499] range",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
@@ -138,17 +129,8 @@ localhost:2099 {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "Error code is equal to 500 or in the [300..399] range",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Error code is equal to 500 or in the [300..399] range",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
|
|||||||
@@ -96,17 +96,8 @@ localhost:3010 {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "404 or 410 error",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "404 or 410 error",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
|
|||||||
@@ -116,17 +116,8 @@ localhost:2099 {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "Error in the [400 .. 499] range",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Error in the [400 .. 499] range",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"match": [
|
"match": [
|
||||||
@@ -138,17 +129,8 @@ localhost:2099 {
|
|||||||
{
|
{
|
||||||
"handle": [
|
"handle": [
|
||||||
{
|
{
|
||||||
"handler": "subroute",
|
"body": "Fallback route: code outside the [400..499] range",
|
||||||
"routes": [
|
"handler": "static_response"
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Fallback route: code outside the [400..499] range",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,260 +0,0 @@
|
|||||||
{
|
|
||||||
http_port 2099
|
|
||||||
}
|
|
||||||
localhost:2099 {
|
|
||||||
root * /var/www/
|
|
||||||
file_server
|
|
||||||
|
|
||||||
handle_errors 404 {
|
|
||||||
handle /en/* {
|
|
||||||
respond "not found" 404
|
|
||||||
}
|
|
||||||
handle /es/* {
|
|
||||||
respond "no encontrado"
|
|
||||||
}
|
|
||||||
handle {
|
|
||||||
respond "default not found"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handle_errors {
|
|
||||||
handle /en/* {
|
|
||||||
respond "English error"
|
|
||||||
}
|
|
||||||
handle /es/* {
|
|
||||||
respond "Spanish error"
|
|
||||||
}
|
|
||||||
handle {
|
|
||||||
respond "Default error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"http_port": 2099,
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":2099"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "vars",
|
|
||||||
"root": "/var/www/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handler": "file_server",
|
|
||||||
"hide": [
|
|
||||||
"./Caddyfile"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"errors": {
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"group": "group3",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "not found",
|
|
||||||
"handler": "static_response",
|
|
||||||
"status_code": 404
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/en/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"group": "group3",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "no encontrado",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/es/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"group": "group3",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "default not found",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"expression": "{http.error.status_code} in [404]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"group": "group8",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "English error",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/en/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"group": "group8",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Spanish error",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"path": [
|
|
||||||
"/es/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"group": "group8",
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "Default error",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -31,6 +31,9 @@ example.com
|
|||||||
"automation": {
|
"automation": {
|
||||||
"policies": [
|
"policies": [
|
||||||
{
|
{
|
||||||
|
"subjects": [
|
||||||
|
"example.com"
|
||||||
|
],
|
||||||
"issuers": [
|
"issuers": [
|
||||||
{
|
{
|
||||||
"module": "acme",
|
"module": "acme",
|
||||||
|
|||||||
@@ -18,10 +18,6 @@
|
|||||||
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
|
|
||||||
0rtt off
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,9 +45,6 @@ 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": [
|
||||||
@@ -91,10 +84,9 @@ foo.com {
|
|||||||
"h2",
|
"h2",
|
||||||
"h2c",
|
"h2c",
|
||||||
"h3"
|
"h3"
|
||||||
],
|
]
|
||||||
"allow_0rtt": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
: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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
: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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
: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
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
:80
|
|
||||||
|
|
||||||
handle {
|
|
||||||
respond <<END!
|
|
||||||
Hello
|
|
||||||
END!
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
heredoc marker on line #4 must contain only alpha-numeric characters, dashes and underscores; got 'END!'
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
:80
|
|
||||||
|
|
||||||
handle {
|
|
||||||
respond <<END
|
|
||||||
line1
|
|
||||||
line2
|
|
||||||
END
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
mismatched leading whitespace in heredoc <<END on line #5 [ line1], expected whitespace [ ] to match the closing marker
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
: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
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
:80
|
|
||||||
|
|
||||||
handle {
|
|
||||||
respond <<<END
|
|
||||||
Hello
|
|
||||||
END
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
too many '<' for heredoc on line #4; only use two, for example <<END
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
(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
|
|
||||||
-57
@@ -1,57 +0,0 @@
|
|||||||
(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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-57
@@ -1,57 +0,0 @@
|
|||||||
(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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
(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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
(import1) {
|
|
||||||
import import2
|
|
||||||
}
|
|
||||||
|
|
||||||
(import2) {
|
|
||||||
import import1
|
|
||||||
}
|
|
||||||
|
|
||||||
import import1
|
|
||||||
|
|
||||||
----------
|
|
||||||
a cycle of imports exists between Caddyfile:import2 and Caddyfile:import1
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
example.com {
|
|
||||||
invoke foo
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
cannot invoke named route 'foo', which was not defined
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
: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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
@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
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
renewal_window_ratio 0.1666
|
|
||||||
}
|
|
||||||
|
|
||||||
example.com {
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tls": {
|
|
||||||
"automation": {
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"renewal_window_ratio": 0.1666
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-63
@@ -1,63 +0,0 @@
|
|||||||
{
|
|
||||||
renewal_window_ratio 0.1666
|
|
||||||
}
|
|
||||||
|
|
||||||
a.example.com {
|
|
||||||
tls {
|
|
||||||
renewal_window_ratio 0.25
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.example.com {
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"a.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"b.example.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tls": {
|
|
||||||
"automation": {
|
|
||||||
"policies": [
|
|
||||||
{
|
|
||||||
"subjects": [
|
|
||||||
"a.example.com"
|
|
||||||
],
|
|
||||||
"renewal_window_ratio": 0.25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"renewal_window_ratio": 0.1666
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-59
@@ -1,59 +0,0 @@
|
|||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
:70000
|
|
||||||
|
|
||||||
handle {
|
|
||||||
respond "should not work"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
port 70000 is out of range
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
:-1
|
|
||||||
|
|
||||||
handle {
|
|
||||||
respond "should not work"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
port -1 is out of range
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
foo://example.com
|
|
||||||
|
|
||||||
handle {
|
|
||||||
respond "hello"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
unsupported URL scheme foo://
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
wss://example.com:70000
|
|
||||||
|
|
||||||
handle {
|
|
||||||
respond "should not work"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
port 70000 is out of range
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
wss://example.com
|
|
||||||
|
|
||||||
handle {
|
|
||||||
respond "hello"
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
the scheme wss:// is only supported in browsers; use https:// instead
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
{
|
|
||||||
dns mock foo
|
|
||||||
acme_dns mock bar
|
|
||||||
}
|
|
||||||
|
|
||||||
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": {
|
|
||||||
"argument": "bar",
|
|
||||||
"name": "mock"
|
|
||||||
},
|
|
||||||
"resolvers": [
|
|
||||||
"8.8.8.8",
|
|
||||||
"8.8.4.4"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"module": "acme"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"issuers": [
|
|
||||||
{
|
|
||||||
"challenges": {
|
|
||||||
"dns": {
|
|
||||||
"provider": {
|
|
||||||
"argument": "bar",
|
|
||||||
"name": "mock"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"module": "acme"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dns": {
|
|
||||||
"argument": "foo",
|
|
||||||
"name": "mock"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-87
@@ -1,87 +0,0 @@
|
|||||||
localhost
|
|
||||||
|
|
||||||
respond "hello from localhost"
|
|
||||||
tls {
|
|
||||||
client_auth {
|
|
||||||
mode request
|
|
||||||
trust_pool inline {
|
|
||||||
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
|
||||||
}
|
|
||||||
verifier leaf {
|
|
||||||
file ../caddy.ca.cer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "hello from localhost",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls_connection_policies": [
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"sni": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"client_authentication": {
|
|
||||||
"ca": {
|
|
||||||
"provider": "inline",
|
|
||||||
"trusted_ca_certs": [
|
|
||||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"verifiers": [
|
|
||||||
{
|
|
||||||
"leaf_certs_loaders": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"../caddy.ca.cer"
|
|
||||||
],
|
|
||||||
"loader": "file"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"verifier": "leaf"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mode": "request"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-85
@@ -1,85 +0,0 @@
|
|||||||
localhost
|
|
||||||
|
|
||||||
respond "hello from localhost"
|
|
||||||
tls {
|
|
||||||
client_auth {
|
|
||||||
mode request
|
|
||||||
trust_pool inline {
|
|
||||||
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
|
||||||
}
|
|
||||||
verifier leaf file ../caddy.ca.cer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "hello from localhost",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls_connection_policies": [
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"sni": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"client_authentication": {
|
|
||||||
"ca": {
|
|
||||||
"provider": "inline",
|
|
||||||
"trusted_ca_certs": [
|
|
||||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"verifiers": [
|
|
||||||
{
|
|
||||||
"leaf_certs_loaders": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"../caddy.ca.cer"
|
|
||||||
],
|
|
||||||
"loader": "file"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"verifier": "leaf"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mode": "request"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
localhost
|
|
||||||
|
|
||||||
respond "hello from localhost"
|
|
||||||
tls {
|
|
||||||
client_auth {
|
|
||||||
mode request
|
|
||||||
trust_pool inline {
|
|
||||||
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
|
||||||
}
|
|
||||||
verifier leaf {
|
|
||||||
file ../caddy.ca.cer
|
|
||||||
file ../caddy.ca.cer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "hello from localhost",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls_connection_policies": [
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"sni": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"client_authentication": {
|
|
||||||
"ca": {
|
|
||||||
"provider": "inline",
|
|
||||||
"trusted_ca_certs": [
|
|
||||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"verifiers": [
|
|
||||||
{
|
|
||||||
"leaf_certs_loaders": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"../caddy.ca.cer"
|
|
||||||
],
|
|
||||||
"loader": "file"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"../caddy.ca.cer"
|
|
||||||
],
|
|
||||||
"loader": "file"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"verifier": "leaf"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mode": "request"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-87
@@ -1,87 +0,0 @@
|
|||||||
localhost
|
|
||||||
|
|
||||||
respond "hello from localhost"
|
|
||||||
tls {
|
|
||||||
client_auth {
|
|
||||||
mode request
|
|
||||||
trust_pool inline {
|
|
||||||
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
|
||||||
}
|
|
||||||
verifier leaf {
|
|
||||||
folder ../
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "hello from localhost",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls_connection_policies": [
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"sni": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"client_authentication": {
|
|
||||||
"ca": {
|
|
||||||
"provider": "inline",
|
|
||||||
"trusted_ca_certs": [
|
|
||||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"verifiers": [
|
|
||||||
{
|
|
||||||
"leaf_certs_loaders": [
|
|
||||||
{
|
|
||||||
"folders": [
|
|
||||||
"../"
|
|
||||||
],
|
|
||||||
"loader": "folder"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"verifier": "leaf"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mode": "request"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-85
@@ -1,85 +0,0 @@
|
|||||||
localhost
|
|
||||||
|
|
||||||
respond "hello from localhost"
|
|
||||||
tls {
|
|
||||||
client_auth {
|
|
||||||
mode request
|
|
||||||
trust_pool inline {
|
|
||||||
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
|
||||||
}
|
|
||||||
verifier leaf folder ../
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "hello from localhost",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls_connection_policies": [
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"sni": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"client_authentication": {
|
|
||||||
"ca": {
|
|
||||||
"provider": "inline",
|
|
||||||
"trusted_ca_certs": [
|
|
||||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"verifiers": [
|
|
||||||
{
|
|
||||||
"leaf_certs_loaders": [
|
|
||||||
{
|
|
||||||
"folders": [
|
|
||||||
"../"
|
|
||||||
],
|
|
||||||
"loader": "folder"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"verifier": "leaf"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mode": "request"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-94
@@ -1,94 +0,0 @@
|
|||||||
localhost
|
|
||||||
|
|
||||||
respond "hello from localhost"
|
|
||||||
tls {
|
|
||||||
client_auth {
|
|
||||||
mode request
|
|
||||||
trust_pool inline {
|
|
||||||
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
|
||||||
}
|
|
||||||
verifier leaf {
|
|
||||||
folder ../
|
|
||||||
folder ../
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
----------
|
|
||||||
{
|
|
||||||
"apps": {
|
|
||||||
"http": {
|
|
||||||
"servers": {
|
|
||||||
"srv0": {
|
|
||||||
"listen": [
|
|
||||||
":443"
|
|
||||||
],
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"match": [
|
|
||||||
{
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "subroute",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"body": "hello from localhost",
|
|
||||||
"handler": "static_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminal": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls_connection_policies": [
|
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"sni": [
|
|
||||||
"localhost"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"client_authentication": {
|
|
||||||
"ca": {
|
|
||||||
"provider": "inline",
|
|
||||||
"trusted_ca_certs": [
|
|
||||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"verifiers": [
|
|
||||||
{
|
|
||||||
"leaf_certs_loaders": [
|
|
||||||
{
|
|
||||||
"folders": [
|
|
||||||
"../"
|
|
||||||
],
|
|
||||||
"loader": "folder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"folders": [
|
|
||||||
"../"
|
|
||||||
],
|
|
||||||
"loader": "folder"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"verifier": "leaf"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"mode": "request"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-9
@@ -1,9 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
{
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
{
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
: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
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
: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
|
|
||||||
-76
@@ -1,76 +0,0 @@
|
|||||||
{
|
|
||||||
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,7 +2,6 @@ localhost
|
|||||||
|
|
||||||
respond "hello from localhost"
|
respond "hello from localhost"
|
||||||
tls {
|
tls {
|
||||||
dns mock
|
|
||||||
dns_ttl 5m10s
|
dns_ttl 5m10s
|
||||||
}
|
}
|
||||||
----------
|
----------
|
||||||
@@ -55,9 +54,6 @@ tls {
|
|||||||
{
|
{
|
||||||
"challenges": {
|
"challenges": {
|
||||||
"dns": {
|
"dns": {
|
||||||
"provider": {
|
|
||||||
"name": "mock"
|
|
||||||
},
|
|
||||||
"ttl": 310000000000
|
"ttl": 310000000000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ localhost
|
|||||||
|
|
||||||
respond "hello from localhost"
|
respond "hello from localhost"
|
||||||
tls {
|
tls {
|
||||||
dns mock
|
|
||||||
propagation_delay 5m10s
|
propagation_delay 5m10s
|
||||||
propagation_timeout 10m20s
|
propagation_timeout 10m20s
|
||||||
}
|
}
|
||||||
@@ -57,10 +56,7 @@ tls {
|
|||||||
"challenges": {
|
"challenges": {
|
||||||
"dns": {
|
"dns": {
|
||||||
"propagation_delay": 310000000000,
|
"propagation_delay": 310000000000,
|
||||||
"propagation_timeout": 620000000000,
|
"propagation_timeout": 620000000000
|
||||||
"provider": {
|
|
||||||
"name": "mock"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"module": "acme"
|
"module": "acme"
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
|
||||||
"github.com/caddyserver/caddy/v2/caddytest"
|
"github.com/caddyserver/caddy/v2/caddytest"
|
||||||
|
|
||||||
_ "github.com/caddyserver/caddy/v2/internal/testmocks"
|
_ "github.com/caddyserver/caddy/v2/internal/testmocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,48 +28,30 @@ func TestCaddyfileAdaptToJSON(t *testing.T) {
|
|||||||
if f.IsDir() {
|
if f.IsDir() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// read the test file
|
||||||
filename := f.Name()
|
filename := f.Name()
|
||||||
|
data, err := os.ReadFile("./caddyfile_adapt/" + filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to read %s dir: %s", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
// run each file as a subtest, so that we can see which one fails more easily
|
// split the Caddyfile (first) and JSON (second) parts
|
||||||
t.Run(filename, func(t *testing.T) {
|
// (append newline to Caddyfile to match formatter expectations)
|
||||||
// read the test file
|
parts := strings.Split(string(data), "----------")
|
||||||
data, err := os.ReadFile("./caddyfile_adapt/" + filename)
|
caddyfile, json := strings.TrimSpace(parts[0])+"\n", strings.TrimSpace(parts[1])
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to read %s dir: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// split the Caddyfile (first) and JSON (second) parts
|
// replace windows newlines in the json with unix newlines
|
||||||
// (append newline to Caddyfile to match formatter expectations)
|
json = winNewlines.ReplaceAllString(json, "\n")
|
||||||
parts := strings.Split(string(data), "----------")
|
|
||||||
caddyfile, expected := strings.TrimSpace(parts[0])+"\n", strings.TrimSpace(parts[1])
|
|
||||||
|
|
||||||
// replace windows newlines in the json with unix newlines
|
// replace os-specific default path for file_server's hide field
|
||||||
expected = winNewlines.ReplaceAllString(expected, "\n")
|
replacePath, _ := jsonMod.Marshal(fmt.Sprint(".", string(filepath.Separator), "Caddyfile"))
|
||||||
|
json = strings.ReplaceAll(json, `"./Caddyfile"`, string(replacePath))
|
||||||
|
|
||||||
// replace os-specific default path for file_server's hide field
|
// run the test
|
||||||
replacePath, _ := jsonMod.Marshal(fmt.Sprint(".", string(filepath.Separator), "Caddyfile"))
|
ok := caddytest.CompareAdapt(t, filename, caddyfile, "caddyfile", json)
|
||||||
expected = strings.ReplaceAll(expected, `"./Caddyfile"`, string(replacePath))
|
if !ok {
|
||||||
|
t.Errorf("failed to adapt %s", filename)
|
||||||
// if the expected output is JSON, compare it
|
}
|
||||||
if len(expected) > 0 && expected[0] == '{' {
|
|
||||||
ok := caddytest.CompareAdapt(t, filename, caddyfile, "caddyfile", expected)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("failed to adapt %s", filename)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, adapt the Caddyfile and check for errors
|
|
||||||
cfgAdapter := caddyconfig.GetAdapter("caddyfile")
|
|
||||||
_, _, err = cfgAdapter.Adapt([]byte(caddyfile), nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("expected error for %s but got none", filename)
|
|
||||||
} else {
|
|
||||||
normalizedErr := winNewlines.ReplaceAllString(err.Error(), "\n")
|
|
||||||
if !strings.Contains(normalizedErr, expected) {
|
|
||||||
t.Errorf("expected error for %s to contain:\n%s\nbut got:\n%s", filename, expected, normalizedErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user