mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-25 16:22:36 -04:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15faeacb60 | |||
| f8a2c60297 | |||
| 01308b4bae | |||
| b7280e6949 | |||
| a63767d3f8 | |||
| 40c582ce82 | |||
| a52917a37d | |||
| e6f46c8d78 | |||
| f6d2c293e7 | |||
| 2ce5c65269 | |||
| 61917c3443 | |||
| 224316eaec | |||
| 5f6758dab5 | |||
| a6a45ff6c5 | |||
| 73e094e1dd | |||
| d79c0f0dec | |||
| db3e19b7b5 | |||
| 1fc151faec | |||
| 9ba999141b | |||
| f98f449f05 | |||
| e66040a6f0 | |||
| 44860482d2 | |||
| 4c90f1427f | |||
| fb63e2e40c | |||
| 583c585c81 | |||
| 4356635d12 | |||
| 4af38e5ac8 | |||
| 399186abfc | |||
| 6dce4934f0 | |||
| 874d0ce822 | |||
| abdf1ae15c | |||
| d7e3a1974b | |||
| e60148ecc3 | |||
| 0b5720faa5 | |||
| dd203ad41f | |||
| b2b29dcd49 | |||
| c97292b255 | |||
| b52271061d | |||
| d05d715a00 | |||
| 8d7ac18402 | |||
| 7e2510ef43 | |||
| feeb6af403 | |||
| d129ae6aec | |||
| 87c7127c28 | |||
| 2fc620d38d | |||
| a46ff50a1c | |||
| cabb5d71c4 | |||
| ba5811467a | |||
| 1b9042bcdd | |||
| 4d6370bf92 | |||
| c6eb186064 | |||
| 76c4cf5a56 | |||
| 797973944f | |||
| 6d97d8d87b | |||
| d404005339 | |||
| 868af6a062 | |||
| d2668cdbb0 | |||
| 6a02999054 | |||
| 9f97df2275 | |||
| d93e027e01 | |||
| 613d544a47 | |||
| 726a9a8fde | |||
| d00824f4a6 | |||
| 8f87c5d993 | |||
| c6673ad4d8 | |||
| 9ab09433de | |||
| 3067074d9c | |||
| 3efda6fb3a | |||
| 9cd472c031 | |||
| e0daa39cd3 | |||
| 70953e873a | |||
| eafc875ea9 | |||
| 03e0a010d1 | |||
| 3609a4af75 | |||
| 26748d06b4 | |||
| b40cacf5ce | |||
| 81413caea2 | |||
| dc9dd2e4b3 | |||
| 567d96c624 | |||
| 5d8b45c9fb | |||
| 0b381eb766 | |||
| 83ef61de10 |
@@ -25,7 +25,7 @@ Other menu items:
|
||||
|
||||
You can have a huge impact on the project by helping with its code. To contribute code to Caddy, first submit or comment in an issue to discuss your contribution, then open a [pull request](https://github.com/caddyserver/caddy/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.** You can get familiar with Caddy's code base by using [code search at Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy).
|
||||
|
||||
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions—even if it seems small or insignificant. Please don't take it personally. :blue_heart: If your change is on the right track, we can guide you to make it mergable.
|
||||
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions—even if it seems small or insignificant. Please don't take it personally. :blue_heart: If your change is on the right track, we can guide you to make it mergeable.
|
||||
|
||||
Here are some of the expectations we have of contributors:
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
GO_SEMVER: '~1.21.0'
|
||||
|
||||
- go: '1.22'
|
||||
GO_SEMVER: '~1.22.1'
|
||||
GO_SEMVER: '~1.22.3'
|
||||
|
||||
# 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)
|
||||
@@ -101,6 +101,12 @@ jobs:
|
||||
run: |
|
||||
go build -tags nobdger -trimpath -ldflags="-w -s" -v
|
||||
|
||||
- name: Smoke test Caddy
|
||||
working-directory: ./cmd/caddy
|
||||
run: |
|
||||
./caddy start
|
||||
./caddy stop
|
||||
|
||||
- name: Publish Build Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
@@ -17,14 +17,12 @@ jobs:
|
||||
matrix:
|
||||
goos:
|
||||
- 'aix'
|
||||
- 'android'
|
||||
- 'linux'
|
||||
- 'solaris'
|
||||
- 'illumos'
|
||||
- 'dragonfly'
|
||||
- 'freebsd'
|
||||
- 'openbsd'
|
||||
- 'plan9'
|
||||
- 'windows'
|
||||
- 'darwin'
|
||||
- 'netbsd'
|
||||
@@ -35,7 +33,7 @@ jobs:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.22'
|
||||
GO_SEMVER: '~1.22.1'
|
||||
GO_SEMVER: '~1.22.3'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
@@ -69,7 +67,3 @@ jobs:
|
||||
working-directory: ./cmd/caddy
|
||||
run: |
|
||||
GOOS=$GOOS GOARCH=$GOARCH go build -tags nobadger -trimpath -o caddy-"$GOOS"-$GOARCH 2> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "::warning ::$GOOS Build Failed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -43,17 +43,14 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '~1.22.1'
|
||||
go-version: '~1.22.3'
|
||||
check-latest: true
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: v1.55
|
||||
|
||||
# Workaround for https://github.com/golangci/golangci-lint-action/issues/135
|
||||
skip-pkg-cache: true
|
||||
|
||||
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
||||
args: --timeout 10m
|
||||
|
||||
@@ -66,5 +63,5 @@ jobs:
|
||||
- name: govulncheck
|
||||
uses: golang/govulncheck-action@v1
|
||||
with:
|
||||
go-version-input: '~1.22.1'
|
||||
go-version-input: '~1.22.3'
|
||||
check-latest: true
|
||||
|
||||
@@ -13,13 +13,13 @@ jobs:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
go:
|
||||
- '1.21'
|
||||
- '1.22'
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.21'
|
||||
GO_SEMVER: '~1.21.0'
|
||||
- go: '1.22'
|
||||
GO_SEMVER: '~1.22.3'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
||||
|
||||
@@ -47,7 +47,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// The hard-coded default `DefaultAdminListen` can be overidden
|
||||
// The hard-coded default `DefaultAdminListen` can be overridden
|
||||
// by setting the `CADDY_ADMIN` environment variable.
|
||||
// The environment variable may be used by packagers to change
|
||||
// the default admin address to something more appropriate for
|
||||
@@ -474,7 +474,6 @@ func manageIdentity(ctx Context, cfg *Config) error {
|
||||
// import the caddytls package -- but it works
|
||||
if cfg.Admin.Identity.IssuersRaw == nil {
|
||||
cfg.Admin.Identity.IssuersRaw = []json.RawMessage{
|
||||
json.RawMessage(`{"module": "zerossl"}`),
|
||||
json.RawMessage(`{"module": "acme"}`),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,7 +869,7 @@ func InstanceID() (uuid.UUID, error) {
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
err = os.MkdirAll(appDataDir, 0o600)
|
||||
err = os.MkdirAll(appDataDir, 0o700)
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ type Dispenser struct {
|
||||
tokens []Token
|
||||
cursor int
|
||||
nesting int
|
||||
|
||||
// A map of arbitrary context data that can be used
|
||||
// to pass through some information to unmarshalers.
|
||||
context map[string]any
|
||||
}
|
||||
|
||||
// NewDispenser returns a Dispenser filled with the given tokens.
|
||||
@@ -454,6 +458,34 @@ func (d *Dispenser) DeleteN(amount int) []Token {
|
||||
return d.tokens
|
||||
}
|
||||
|
||||
// SetContext sets a key-value pair in the context map.
|
||||
func (d *Dispenser) SetContext(key string, value any) {
|
||||
if d.context == nil {
|
||||
d.context = make(map[string]any)
|
||||
}
|
||||
d.context[key] = value
|
||||
}
|
||||
|
||||
// GetContext gets the value of a key in the context map.
|
||||
func (d *Dispenser) GetContext(key string) any {
|
||||
if d.context == nil {
|
||||
return nil
|
||||
}
|
||||
return d.context[key]
|
||||
}
|
||||
|
||||
// GetContextString gets the value of a key in the context map
|
||||
// as a string, or an empty string if the key does not exist.
|
||||
func (d *Dispenser) GetContextString(key string) string {
|
||||
if d.context == nil {
|
||||
return ""
|
||||
}
|
||||
if val, ok := d.context[key].(string); ok {
|
||||
return val
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// isNewLine determines whether the current token is on a different
|
||||
// line (higher line number) than the previous token. It handles imported
|
||||
// tokens correctly. If there isn't a previous token, it returns true.
|
||||
@@ -485,3 +517,5 @@ func (d *Dispenser) isNextOnNewLine() bool {
|
||||
next := d.tokens[d.cursor+1]
|
||||
return isNextOnNewLine(curr, next)
|
||||
}
|
||||
|
||||
const MatcherNameCtxKey = "matcher_name"
|
||||
|
||||
@@ -340,6 +340,8 @@ func (l *lexer) finalizeHeredoc(val []rune, marker string) ([]rune, error) {
|
||||
return []rune(out), nil
|
||||
}
|
||||
|
||||
// Quoted returns true if the token was enclosed in quotes
|
||||
// (i.e. double quotes, backticks, or heredoc).
|
||||
func (t Token) Quoted() bool {
|
||||
return t.wasQuoted > 0
|
||||
}
|
||||
@@ -356,6 +358,19 @@ func (t Token) NumLineBreaks() int {
|
||||
return lineBreaks
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of the token.
|
||||
func (t Token) Clone() Token {
|
||||
return Token{
|
||||
File: t.File,
|
||||
imports: append([]string{}, t.imports...),
|
||||
Line: t.Line,
|
||||
Text: t.Text,
|
||||
wasQuoted: t.wasQuoted,
|
||||
heredocMarker: t.heredocMarker,
|
||||
snippetName: t.snippetName,
|
||||
}
|
||||
}
|
||||
|
||||
var heredocMarkerRegexp = regexp.MustCompile("^[A-Za-z0-9_-]+$")
|
||||
|
||||
// isNextOnNewLine tests whether t2 is on a different line from t1
|
||||
|
||||
@@ -214,7 +214,12 @@ func (p *parser) addresses() error {
|
||||
value := p.Val()
|
||||
token := p.Token()
|
||||
|
||||
// special case: import directive replaces tokens during parse-time
|
||||
// Reject request matchers if trying to define them globally
|
||||
if strings.HasPrefix(value, "@") {
|
||||
return p.Errf("request matchers may not be defined globally, they must be in a site block; found %s", value)
|
||||
}
|
||||
|
||||
// Special case: import directive replaces tokens during parse-time
|
||||
if value == "import" && p.isNewLine() {
|
||||
err := p.doImport(0)
|
||||
if err != nil {
|
||||
@@ -395,7 +400,6 @@ func (p *parser) doImport(nesting int) error {
|
||||
return p.Errf("Glob pattern may only contain one wildcard (*), but has others: %s", globPattern)
|
||||
}
|
||||
matches, err = filepath.Glob(globPattern)
|
||||
|
||||
if err != nil {
|
||||
return p.Errf("Failed to use import pattern %s: %v", importPattern, err)
|
||||
}
|
||||
|
||||
@@ -857,6 +857,29 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectsGlobalMatcher(t *testing.T) {
|
||||
p := testParser(`
|
||||
@rejected path /foo
|
||||
|
||||
(common) {
|
||||
gzip foo
|
||||
errors stderr
|
||||
}
|
||||
|
||||
http://example.com {
|
||||
import common
|
||||
}
|
||||
`)
|
||||
_, err := p.parseAll()
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error, but got nil")
|
||||
}
|
||||
expected := "request matchers may not be defined globally, they must be in a site block; found @rejected, at Testfile:2"
|
||||
if err.Error() != expected {
|
||||
t.Errorf("Expected error to be '%s' but got '%v'", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func testParser(input string) parser {
|
||||
return parser{Dispenser: NewTestDispenser(input)}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
@@ -51,6 +51,7 @@ func init() {
|
||||
RegisterDirective("log", parseLog)
|
||||
RegisterHandlerDirective("skip_log", parseLogSkip)
|
||||
RegisterHandlerDirective("log_skip", parseLogSkip)
|
||||
RegisterHandlerDirective("log_name", parseLogName)
|
||||
}
|
||||
|
||||
// parseBind parses the bind directive. Syntax:
|
||||
@@ -69,8 +70,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
||||
// curves <curves...>
|
||||
// client_auth {
|
||||
// mode [request|require|verify_if_given|require_and_verify]
|
||||
// trusted_ca_cert <base64_der>
|
||||
// trusted_ca_cert_file <filename>
|
||||
// trust_pool <module_name> [...]
|
||||
// trusted_leaf_cert <base64_der>
|
||||
// trusted_leaf_cert_file <filename>
|
||||
// }
|
||||
@@ -107,7 +107,6 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
var onDemand bool
|
||||
var reusePrivateKeys bool
|
||||
|
||||
// file certificate loader
|
||||
firstLine := h.RemainingArgs()
|
||||
switch len(firstLine) {
|
||||
case 0:
|
||||
@@ -117,13 +116,13 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
} else if !strings.Contains(firstLine[0], "@") {
|
||||
return nil, h.Err("single argument must either be 'internal' or an email address")
|
||||
} else {
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
acmeIssuer = &caddytls.ACMEIssuer{
|
||||
Email: firstLine[0],
|
||||
}
|
||||
acmeIssuer.Email = firstLine[0]
|
||||
}
|
||||
|
||||
case 2:
|
||||
// file certificate loader
|
||||
certFilename := firstLine[0]
|
||||
keyFilename := firstLine[1]
|
||||
|
||||
@@ -488,19 +487,24 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
|
||||
case acmeIssuer != nil:
|
||||
// implicit ACME issuers (from various subdirectives) - use defaults; there might be more than one
|
||||
defaultIssuers := caddytls.DefaultIssuers()
|
||||
defaultIssuers := caddytls.DefaultIssuers(acmeIssuer.Email)
|
||||
|
||||
// if a CA endpoint was set, override multiple implicit issuers since it's a specific one
|
||||
// if an ACME CA endpoint was set, the user expects to use that specific one,
|
||||
// not any others that may be defaults, so replace all defaults with that ACME CA
|
||||
if acmeIssuer.CA != "" {
|
||||
defaultIssuers = []certmagic.Issuer{acmeIssuer}
|
||||
}
|
||||
|
||||
for _, issuer := range defaultIssuers {
|
||||
switch iss := issuer.(type) {
|
||||
case *caddytls.ACMEIssuer:
|
||||
issuer = acmeIssuer
|
||||
case *caddytls.ZeroSSLIssuer:
|
||||
iss.ACMEIssuer = acmeIssuer
|
||||
// apply settings from the implicitly-configured ACMEIssuer to any
|
||||
// default ACMEIssuers, but preserve each default issuer's CA endpoint,
|
||||
// because, for example, if you configure the DNS challenge, it should
|
||||
// apply to any of the default ACMEIssuers, but you don't want to trample
|
||||
// out their unique CA endpoints
|
||||
if iss, ok := issuer.(*caddytls.ACMEIssuer); ok && iss != nil {
|
||||
acmeCopy := *acmeIssuer
|
||||
acmeCopy.CA = iss.CA
|
||||
issuer = &acmeCopy
|
||||
}
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
@@ -911,7 +915,7 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
// this is useful for setting up loggers per subdomain in a site block
|
||||
// with a wildcard domain
|
||||
customHostnames := []string{}
|
||||
|
||||
noHostname := false
|
||||
for h.NextBlock(0) {
|
||||
switch h.Val() {
|
||||
case "hostnames":
|
||||
@@ -997,6 +1001,12 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
cl.Exclude = append(cl.Exclude, h.Val())
|
||||
}
|
||||
|
||||
case "no_hostname":
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
noHostname = true
|
||||
|
||||
default:
|
||||
return nil, h.Errf("unrecognized subdirective: %s", h.Val())
|
||||
}
|
||||
@@ -1004,7 +1014,7 @@ func parseLogHelper(h Helper, globalLogNames map[string]struct{}) ([]ConfigValue
|
||||
|
||||
var val namedCustomLog
|
||||
val.hostnames = customHostnames
|
||||
|
||||
val.noHostname = noHostname
|
||||
isEmptyConfig := reflect.DeepEqual(cl, new(caddy.CustomLog))
|
||||
|
||||
// Skip handling of empty logging configs
|
||||
@@ -1055,3 +1065,13 @@ func parseLogSkip(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
}
|
||||
return caddyhttp.VarsMiddleware{"log_skip": true}, nil
|
||||
}
|
||||
|
||||
// parseLogName parses the log_name directive. Syntax:
|
||||
//
|
||||
// log_name <names...>
|
||||
func parseLogName(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
h.Next() // consume directive name
|
||||
return caddyhttp.VarsMiddleware{
|
||||
caddyhttp.AccessLoggerNameVarKey: h.RemainingArgs(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ var defaultDirectiveOrder = []string{
|
||||
"log_append",
|
||||
"skip_log", // TODO: deprecated, renamed to log_skip
|
||||
"log_skip",
|
||||
"log_name",
|
||||
|
||||
"header",
|
||||
"copy_response_headers", // only in reverse_proxy's handle_response
|
||||
@@ -73,6 +74,7 @@ var defaultDirectiveOrder = []string{
|
||||
"request_header",
|
||||
"encode",
|
||||
"push",
|
||||
"intercept",
|
||||
"templates",
|
||||
|
||||
// special routing & dispatching directives
|
||||
|
||||
@@ -797,6 +797,15 @@ func (st *ServerType) serversFromPairings(
|
||||
sblockLogHosts := sblock.hostsFromKeys(true)
|
||||
for _, cval := range sblock.pile["custom_log"] {
|
||||
ncl := cval.Value.(namedCustomLog)
|
||||
|
||||
// if `no_hostname` is set, then this logger will not
|
||||
// be associated with any of the site block's hostnames,
|
||||
// and only be usable via the `log_name` directive
|
||||
// or the `access_logger_names` variable
|
||||
if ncl.noHostname {
|
||||
continue
|
||||
}
|
||||
|
||||
if sblock.hasHostCatchAllKey() && len(ncl.hostnames) == 0 {
|
||||
// all requests for hosts not able to be listed should use
|
||||
// this log because it's a catch-all-hosts server block
|
||||
@@ -805,22 +814,22 @@ func (st *ServerType) serversFromPairings(
|
||||
// if the logger overrides the hostnames, map that to the logger name
|
||||
for _, h := range ncl.hostnames {
|
||||
if srv.Logs.LoggerNames == nil {
|
||||
srv.Logs.LoggerNames = make(map[string]string)
|
||||
srv.Logs.LoggerNames = make(map[string]caddyhttp.StringArray)
|
||||
}
|
||||
srv.Logs.LoggerNames[h] = ncl.name
|
||||
srv.Logs.LoggerNames[h] = append(srv.Logs.LoggerNames[h], ncl.name)
|
||||
}
|
||||
} else {
|
||||
// otherwise, map each host to the logger name
|
||||
for _, h := range sblockLogHosts {
|
||||
if srv.Logs.LoggerNames == nil {
|
||||
srv.Logs.LoggerNames = make(map[string]string)
|
||||
}
|
||||
// strip the port from the host, if any
|
||||
host, _, err := net.SplitHostPort(h)
|
||||
if err != nil {
|
||||
host = h
|
||||
}
|
||||
srv.Logs.LoggerNames[host] = ncl.name
|
||||
if srv.Logs.LoggerNames == nil {
|
||||
srv.Logs.LoggerNames = make(map[string]caddyhttp.StringArray)
|
||||
}
|
||||
srv.Logs.LoggerNames[host] = append(srv.Logs.LoggerNames[host], ncl.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1282,19 +1291,24 @@ func matcherSetFromMatcherToken(
|
||||
if tkn.Text == "*" {
|
||||
// match all requests == no matchers, so nothing to do
|
||||
return nil, true, nil
|
||||
} else if strings.HasPrefix(tkn.Text, "/") {
|
||||
// convenient way to specify a single path match
|
||||
}
|
||||
|
||||
// convenient way to specify a single path match
|
||||
if strings.HasPrefix(tkn.Text, "/") {
|
||||
return caddy.ModuleMap{
|
||||
"path": caddyconfig.JSON(caddyhttp.MatchPath{tkn.Text}, warnings),
|
||||
}, true, nil
|
||||
} else if strings.HasPrefix(tkn.Text, matcherPrefix) {
|
||||
// pre-defined matcher
|
||||
}
|
||||
|
||||
// pre-defined matcher
|
||||
if strings.HasPrefix(tkn.Text, matcherPrefix) {
|
||||
m, ok := matcherDefs[tkn.Text]
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("unrecognized matcher name: %+v", tkn.Text)
|
||||
}
|
||||
return m, true, nil
|
||||
}
|
||||
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
@@ -1397,6 +1411,14 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
||||
// given a matcher name and the tokens following it, parse
|
||||
// the tokens as a matcher module and record it
|
||||
makeMatcher := func(matcherName string, tokens []caddyfile.Token) error {
|
||||
// create a new dispenser from the tokens
|
||||
dispenser := caddyfile.NewDispenser(tokens)
|
||||
|
||||
// set the matcher name (without @) in the dispenser context so
|
||||
// that matcher modules can access it to use it as their name
|
||||
// (e.g. regexp matchers which use the name for capture groups)
|
||||
dispenser.SetContext(caddyfile.MatcherNameCtxKey, definitionName[1:])
|
||||
|
||||
mod, err := caddy.GetModule("http.matchers." + matcherName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
|
||||
@@ -1405,7 +1427,7 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
||||
if !ok {
|
||||
return fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
|
||||
}
|
||||
err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens))
|
||||
err = unm.UnmarshalCaddyfile(dispenser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1422,11 +1444,13 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M
|
||||
if d.NextArg() {
|
||||
if d.Token().Quoted() {
|
||||
// since it was missing the matcher name, we insert a token
|
||||
// in front of the expression token itself
|
||||
err := makeMatcher("expression", []caddyfile.Token{
|
||||
{Text: "expression", File: d.File(), Line: d.Line()},
|
||||
d.Token(),
|
||||
})
|
||||
// in front of the expression token itself; we use Clone() to
|
||||
// make the new token to keep the same the import location as
|
||||
// the next token, if this is within a snippet or imported file.
|
||||
// see https://github.com/caddyserver/caddy/issues/6287
|
||||
expressionToken := d.Token().Clone()
|
||||
expressionToken.Text = "expression"
|
||||
err := makeMatcher("expression", []caddyfile.Token{expressionToken, d.Token()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1583,9 +1607,10 @@ func (c counter) nextGroup() string {
|
||||
}
|
||||
|
||||
type namedCustomLog struct {
|
||||
name string
|
||||
hostnames []string
|
||||
log *caddy.CustomLog
|
||||
name string
|
||||
hostnames []string
|
||||
log *caddy.CustomLog
|
||||
noHostname bool
|
||||
}
|
||||
|
||||
// sbAddrAssociation is a mapping from a list of
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
@@ -54,6 +54,7 @@ func init() {
|
||||
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
|
||||
RegisterGlobalOption("servers", parseServerOptions)
|
||||
RegisterGlobalOption("ocsp_stapling", parseOCSPStaplingOptions)
|
||||
RegisterGlobalOption("cert_lifetime", parseOptDuration)
|
||||
RegisterGlobalOption("log", parseLogOptions)
|
||||
RegisterGlobalOption("preferred_chains", parseOptPreferredChains)
|
||||
RegisterGlobalOption("persist_config", parseOptPersistConfig)
|
||||
@@ -212,9 +213,9 @@ func parseOptACMEDNS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prov, ok := unm.(certmagic.ACMEDNSProvider)
|
||||
prov, ok := unm.(certmagic.DNSProvider)
|
||||
if !ok {
|
||||
return nil, d.Errf("module %s (%T) is not a certmagic.ACMEDNSProvider", modID, unm)
|
||||
return nil, d.Errf("module %s (%T) is not a certmagic.DNSProvider", modID, unm)
|
||||
}
|
||||
return prov, nil
|
||||
}
|
||||
@@ -345,9 +346,34 @@ func parseOptOnDemand(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||
if ond == nil {
|
||||
ond = new(caddytls.OnDemandConfig)
|
||||
}
|
||||
if ond.PermissionRaw != nil {
|
||||
return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
|
||||
}
|
||||
perm := caddytls.PermissionByHTTP{Endpoint: d.Val()}
|
||||
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", "http", nil)
|
||||
|
||||
case "permission":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
if ond == nil {
|
||||
ond = new(caddytls.OnDemandConfig)
|
||||
}
|
||||
if ond.PermissionRaw != nil {
|
||||
return nil, d.Err("on-demand TLS permission module (or 'ask') already specified")
|
||||
}
|
||||
modName := d.Val()
|
||||
modID := "tls.permission." + modName
|
||||
unm, err := caddyfile.UnmarshalModule(d, modID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
perm, ok := unm.(caddytls.OnDemandPermission)
|
||||
if !ok {
|
||||
return nil, d.Errf("module %s (%T) is not an on-demand TLS permission module", modID, unm)
|
||||
}
|
||||
ond.PermissionRaw = caddyconfig.JSONModuleObject(perm, "module", modName, nil)
|
||||
|
||||
case "interval":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
|
||||
@@ -50,6 +50,7 @@ type serverOptions struct {
|
||||
ClientIPHeaders []string
|
||||
ShouldLogCredentials bool
|
||||
Metrics *caddyhttp.Metrics
|
||||
Trace bool // TODO: EXPERIMENTAL
|
||||
}
|
||||
|
||||
func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
@@ -246,39 +247,11 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
}
|
||||
serverOpts.Metrics = new(caddyhttp.Metrics)
|
||||
|
||||
// TODO: DEPRECATED. (August 2022)
|
||||
case "protocol":
|
||||
caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol sub-option will be removed soon")
|
||||
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "allow_h2c":
|
||||
caddy.Log().Named("caddyfile").Warn("DEPRECATED: allow_h2c will be removed soon; use protocols option instead")
|
||||
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
if sliceContains(serverOpts.Protocols, "h2c") {
|
||||
return nil, d.Errf("protocol h2c already specified")
|
||||
}
|
||||
serverOpts.Protocols = append(serverOpts.Protocols, "h2c")
|
||||
|
||||
case "strict_sni_host":
|
||||
caddy.Log().Named("caddyfile").Warn("DEPRECATED: protocol > strict_sni_host in this position will be removed soon; move up to the servers block instead")
|
||||
|
||||
if d.NextArg() && d.Val() != "insecure_off" && d.Val() != "on" {
|
||||
return nil, d.Errf("strict_sni_host only supports 'on' or 'insecure_off', got '%s'", d.Val())
|
||||
}
|
||||
boolVal := true
|
||||
if d.Val() == "insecure_off" {
|
||||
boolVal = false
|
||||
}
|
||||
serverOpts.StrictSNIHost = &boolVal
|
||||
|
||||
default:
|
||||
return nil, d.Errf("unrecognized protocol option '%s'", d.Val())
|
||||
}
|
||||
case "trace":
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
serverOpts.Trace = true
|
||||
|
||||
default:
|
||||
return nil, d.Errf("unrecognized servers option '%s'", d.Val())
|
||||
@@ -291,7 +264,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
||||
func applyServerOptions(
|
||||
servers map[string]*caddyhttp.Server,
|
||||
options map[string]any,
|
||||
warnings *[]caddyconfig.Warning,
|
||||
_ *[]caddyconfig.Warning,
|
||||
) error {
|
||||
serverOpts, ok := options["servers"].([]serverOptions)
|
||||
if !ok {
|
||||
@@ -351,10 +324,17 @@ func applyServerOptions(
|
||||
server.Metrics = opts.Metrics
|
||||
if opts.ShouldLogCredentials {
|
||||
if server.Logs == nil {
|
||||
server.Logs = &caddyhttp.ServerLogConfig{}
|
||||
server.Logs = new(caddyhttp.ServerLogConfig)
|
||||
}
|
||||
server.Logs.ShouldLogCredentials = opts.ShouldLogCredentials
|
||||
}
|
||||
if opts.Trace {
|
||||
// TODO: THIS IS EXPERIMENTAL (MAY 2024)
|
||||
if server.Logs == nil {
|
||||
server.Logs = new(caddyhttp.ServerLogConfig)
|
||||
}
|
||||
server.Logs.Trace = opts.Trace
|
||||
}
|
||||
|
||||
if opts.Name != "" {
|
||||
nameReplacements[key] = opts.Name
|
||||
|
||||
@@ -36,6 +36,7 @@ func NewShorthandReplacer() ShorthandReplacer {
|
||||
{regexp.MustCompile(`{re\.([\w-\.]*)}`), "{http.regexp.$1}"},
|
||||
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
||||
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
||||
{regexp.MustCompile(`{resp\.([\w-\.]*)}`), "{http.intercept.$1}"},
|
||||
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
||||
{regexp.MustCompile(`{file_match\.([\w-]*)}`), "{http.matchers.file.$1}"},
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
@@ -224,7 +224,7 @@ func (st ServerType) buildTLSApp(
|
||||
var internal, external []string
|
||||
for _, s := range ap.SubjectsRaw {
|
||||
// do not create Issuers for Tailscale domains; they will be given a Manager instead
|
||||
if strings.HasSuffix(strings.ToLower(s), ".ts.net") {
|
||||
if isTailscaleDomain(s) {
|
||||
continue
|
||||
}
|
||||
if !certmagic.SubjectQualifiesForCert(s) {
|
||||
@@ -344,7 +344,7 @@ func (st ServerType) buildTLSApp(
|
||||
internalAP := &caddytls.AutomationPolicy{
|
||||
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
|
||||
}
|
||||
if autoHTTPS != "off" {
|
||||
if autoHTTPS != "off" && autoHTTPS != "disable_certs" {
|
||||
for h := range httpsHostsSharedWithHostlessKey {
|
||||
al = append(al, h)
|
||||
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
||||
@@ -378,15 +378,12 @@ func (st ServerType) buildTLSApp(
|
||||
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
|
||||
// for public names, create default issuers which will later be filled in with configured global defaults
|
||||
// (internal names will implicitly use the internal issuer at auto-https time)
|
||||
ap.Issuers = caddytls.DefaultIssuers()
|
||||
emailStr, _ := globalEmail.(string)
|
||||
ap.Issuers = caddytls.DefaultIssuers(emailStr)
|
||||
|
||||
// if a specific endpoint is configured, can't use multiple default issuers
|
||||
if globalACMECA != nil {
|
||||
if strings.Contains(globalACMECA.(string), "zerossl") {
|
||||
ap.Issuers = []certmagic.Issuer{&caddytls.ZeroSSLIssuer{ACMEIssuer: new(caddytls.ACMEIssuer)}}
|
||||
} else {
|
||||
ap.Issuers = []certmagic.Issuer{new(caddytls.ACMEIssuer)}
|
||||
}
|
||||
ap.Issuers = []certmagic.Issuer{new(caddytls.ACMEIssuer)}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,6 +456,8 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||
globalACMEDNS := options["acme_dns"]
|
||||
globalACMEEAB := options["acme_eab"]
|
||||
globalPreferredChains := options["preferred_chains"]
|
||||
globalCertLifetime := options["cert_lifetime"]
|
||||
globalHTTPPort, globalHTTPSPort := options["http_port"], options["https_port"]
|
||||
|
||||
if globalEmail != nil && acmeIssuer.Email == "" {
|
||||
acmeIssuer.Email = globalEmail.(string)
|
||||
@@ -482,6 +481,27 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||
if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
|
||||
acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
|
||||
}
|
||||
if globalHTTPPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.HTTP == nil || acmeIssuer.Challenges.HTTP.AlternatePort == 0) {
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.HTTP == nil {
|
||||
acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
|
||||
}
|
||||
acmeIssuer.Challenges.HTTP.AlternatePort = globalHTTPPort.(int)
|
||||
}
|
||||
if globalHTTPSPort != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.TLSALPN == nil || acmeIssuer.Challenges.TLSALPN.AlternatePort == 0) {
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.TLSALPN == nil {
|
||||
acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
|
||||
}
|
||||
acmeIssuer.Challenges.TLSALPN.AlternatePort = globalHTTPSPort.(int)
|
||||
}
|
||||
if globalCertLifetime != nil && acmeIssuer.CertificateLifetime == 0 {
|
||||
acmeIssuer.CertificateLifetime = globalCertLifetime.(caddy.Duration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -490,7 +510,11 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
||||
// for any other automation policies. A nil policy (and no error) will be
|
||||
// returned if there are no default/global options. However, if always is
|
||||
// true, a non-nil value will always be returned (unless there is an error).
|
||||
func newBaseAutomationPolicy(options map[string]any, warnings []caddyconfig.Warning, always bool) (*caddytls.AutomationPolicy, error) {
|
||||
func newBaseAutomationPolicy(
|
||||
options map[string]any,
|
||||
_ []caddyconfig.Warning,
|
||||
always bool,
|
||||
) (*caddytls.AutomationPolicy, error) {
|
||||
issuers, hasIssuers := options["cert_issuer"]
|
||||
_, hasLocalCerts := options["local_certs"]
|
||||
keyType, hasKeyType := options["key_type"]
|
||||
@@ -666,17 +690,33 @@ func automationPolicyShadows(i int, aps []*caddytls.AutomationPolicy) int {
|
||||
// subjectQualifiesForPublicCert is like certmagic.SubjectQualifiesForPublicCert() except
|
||||
// that this allows domains with multiple wildcard levels like '*.*.example.com' to qualify
|
||||
// if the automation policy has OnDemand enabled (i.e. this function is more lenient).
|
||||
//
|
||||
// IP subjects are considered as non-qualifying for public certs. Technically, there are
|
||||
// now public ACME CAs as well as non-ACME CAs that issue IP certificates. But this function
|
||||
// is used solely for implicit automation (defaults), where it gets really complicated to
|
||||
// keep track of which issuers support IP certificates in which circumstances. Currently,
|
||||
// issuers that support IP certificates are very few, and all require some sort of config
|
||||
// from the user anyway (such as an account credential). Since we cannot implicitly and
|
||||
// automatically get public IP certs without configuration from the user, we treat IPs as
|
||||
// not qualifying for public certificates. Users should expressly configure an issuer
|
||||
// that supports IP certs for that purpose.
|
||||
func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) bool {
|
||||
return !certmagic.SubjectIsIP(subj) &&
|
||||
!certmagic.SubjectIsInternal(subj) &&
|
||||
(strings.Count(subj, "*.") < 2 || ap.OnDemand)
|
||||
}
|
||||
|
||||
// automationPolicyHasAllPublicNames returns true if all the names on the policy
|
||||
// do NOT qualify for public certs OR are tailscale domains.
|
||||
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
||||
for _, subj := range ap.SubjectsRaw {
|
||||
if !subjectQualifiesForPublicCert(ap, subj) {
|
||||
if !subjectQualifiesForPublicCert(ap, subj) || isTailscaleDomain(subj) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isTailscaleDomain(name string) bool {
|
||||
return strings.HasSuffix(strings.ToLower(name), ".ts.net")
|
||||
}
|
||||
|
||||
@@ -181,19 +181,16 @@ func (hl HTTPLoader) makeClient(ctx caddy.Context) (*http.Client, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting server identity credentials: %v", err)
|
||||
}
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = new(tls.Config)
|
||||
}
|
||||
tlsConfig.Certificates = certs
|
||||
// See https://github.com/securego/gosec/issues/1054#issuecomment-2072235199
|
||||
//nolint:gosec
|
||||
tlsConfig = &tls.Config{Certificates: certs}
|
||||
} else if hl.TLS.ClientCertificateFile != "" && hl.TLS.ClientCertificateKeyFile != "" {
|
||||
cert, err := tls.LoadX509KeyPair(hl.TLS.ClientCertificateFile, hl.TLS.ClientCertificateKeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = new(tls.Config)
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
//nolint:gosec
|
||||
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
// trusted server certs
|
||||
|
||||
+17
-3
@@ -36,7 +36,7 @@ type Defaults struct {
|
||||
// Port we expect caddy to listening on
|
||||
AdminPort int
|
||||
// Certificates we expect to be loaded before attempting to run the tests
|
||||
Certifcates []string
|
||||
Certificates []string
|
||||
// TestRequestTimeout is the time to wait for a http request to
|
||||
TestRequestTimeout time.Duration
|
||||
// LoadRequestTimeout is the time to wait for the config to be loaded against the caddy server
|
||||
@@ -46,7 +46,7 @@ type Defaults struct {
|
||||
// Default testing values
|
||||
var Default = Defaults{
|
||||
AdminPort: 2999, // different from what a real server also running on a developer's machine might be
|
||||
Certifcates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
||||
Certificates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
||||
TestRequestTimeout: 5 * time.Second,
|
||||
LoadRequestTimeout: 5 * time.Second,
|
||||
}
|
||||
@@ -136,6 +136,20 @@ func (tc *Tester) initServer(rawConfig string, configType string) error {
|
||||
})
|
||||
|
||||
rawConfig = prependCaddyFilePath(rawConfig)
|
||||
// normalize JSON config
|
||||
if configType == "json" {
|
||||
tc.t.Logf("Before: %s", rawConfig)
|
||||
var conf any
|
||||
if err := json.Unmarshal([]byte(rawConfig), &conf); err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawConfig = string(c)
|
||||
tc.t.Logf("After: %s", rawConfig)
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: Default.LoadRequestTimeout,
|
||||
}
|
||||
@@ -231,7 +245,7 @@ const initConfig = `{
|
||||
// designated path and Caddy sub-process is running.
|
||||
func validateTestPrerequisites(t testing.TB) error {
|
||||
// check certificates are found
|
||||
for _, certName := range Default.Certifcates {
|
||||
for _, certName := range Default.Certificates {
|
||||
if _, err := os.Stat(getIntegrationDir() + certName); errors.Is(err, fs.ErrNotExist) {
|
||||
return fmt.Errorf("caddy integration test certificates (%s) not found", certName)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package caddytest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -31,3 +32,98 @@ func TestReplaceCertificatePaths(t *testing.T) {
|
||||
t.Error("expected redirect uri to be unchanged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadUnorderedJSON(t *testing.T) {
|
||||
tester := NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "DEBUG",
|
||||
"writer": {
|
||||
"output": "stdout"
|
||||
}
|
||||
},
|
||||
"sStdOutLogs": {
|
||||
"level": "DEBUG",
|
||||
"writer": {
|
||||
"output": "stdout"
|
||||
},
|
||||
"include": [
|
||||
"http.*",
|
||||
"admin.*"
|
||||
]
|
||||
},
|
||||
"sFileLogs": {
|
||||
"level": "DEBUG",
|
||||
"writer": {
|
||||
"output": "stdout"
|
||||
},
|
||||
"include": [
|
||||
"http.*",
|
||||
"admin.*"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"s_server": {
|
||||
"listen": [
|
||||
":9443",
|
||||
":9080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"body": "Hello"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost",
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"logs": {
|
||||
"default_logger_name": "sStdOutLogs",
|
||||
"logger_names": {
|
||||
"localhost": "sStdOutLogs",
|
||||
"127.0.0.1": "sFileLogs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
tester.AssertResponseCode(req, 200)
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import (
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
smallstepacme "github.com/smallstep/certificates/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -77,7 +77,7 @@ func TestACMEServerWithDefaults(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
certs, err := client.ObtainCertificate(ctx, account, certPrivateKey, []string{"localhost"})
|
||||
certs, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"localhost"})
|
||||
if err != nil {
|
||||
t.Errorf("obtaining certificate: %v", err)
|
||||
return
|
||||
@@ -146,7 +146,7 @@ func TestACMEServerWithMismatchedChallenges(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
certs, err := client.ObtainCertificate(ctx, account, certPrivateKey, []string{"localhost"})
|
||||
certs, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"localhost"})
|
||||
if len(certs) > 0 {
|
||||
t.Errorf("expected '0' certificates, but received '%d'", len(certs))
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v2"
|
||||
"github.com/mholt/acmez/v2/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -105,12 +105,7 @@ func TestACMEServerAllowPolicy(t *testing.T) {
|
||||
return
|
||||
}
|
||||
{
|
||||
certs, err := client.ObtainCertificate(
|
||||
ctx,
|
||||
account,
|
||||
certPrivateKey,
|
||||
[]string{"localhost"},
|
||||
)
|
||||
certs, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"localhost"})
|
||||
if err != nil {
|
||||
t.Errorf("obtaining certificate for allowed domain: %v", err)
|
||||
return
|
||||
@@ -126,7 +121,7 @@ func TestACMEServerAllowPolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
{
|
||||
_, err := client.ObtainCertificate(ctx, account, certPrivateKey, []string{"not-matching.localhost"})
|
||||
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"not-matching.localhost"})
|
||||
if err == nil {
|
||||
t.Errorf("obtaining certificate for 'not-matching.localhost' domain")
|
||||
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
|
||||
@@ -199,7 +194,7 @@ func TestACMEServerDenyPolicy(t *testing.T) {
|
||||
return
|
||||
}
|
||||
{
|
||||
_, err := client.ObtainCertificate(ctx, account, certPrivateKey, []string{"deny.localhost"})
|
||||
_, err := client.ObtainCertificateForSANs(ctx, account, certPrivateKey, []string{"deny.localhost"})
|
||||
if err == nil {
|
||||
t.Errorf("obtaining certificate for 'deny.localhost' domain")
|
||||
} else if err != nil && !strings.Contains(err.Error(), "urn:ietf:params:acme:error:rejectedIdentifier") {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
pki {
|
||||
ca internal {
|
||||
name "Internal"
|
||||
root_cn "Internal Root Cert"
|
||||
intermediate_cn "Internal Intermediate Cert"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
acme.example.com {
|
||||
acme_server {
|
||||
ca internal
|
||||
sign_with_root
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"acme.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"ca": "internal",
|
||||
"handler": "acme_server",
|
||||
"sign_with_root": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"internal": {
|
||||
"name": "Internal",
|
||||
"root_common_name": "Internal Root Cert",
|
||||
"intermediate_common_name": "Internal Intermediate Cert"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
(snippet) {
|
||||
@g `{http.error.status_code} == 404`
|
||||
}
|
||||
|
||||
example.com
|
||||
|
||||
@a expression {http.error.status_code} == 400
|
||||
@@ -14,6 +18,12 @@ abort @d
|
||||
|
||||
@e expression `{http.error.status_code} == 404`
|
||||
abort @e
|
||||
|
||||
@f `{http.error.status_code} == 404`
|
||||
abort @f
|
||||
|
||||
import snippet
|
||||
abort @g
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
@@ -84,7 +94,10 @@ abort @e
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"expression": "{http.error.status_code} == 403"
|
||||
"expression": {
|
||||
"expr": "{http.error.status_code} == 403",
|
||||
"name": "d"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -97,7 +110,42 @@ abort @e
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"expression": "{http.error.status_code} == 404"
|
||||
"expression": {
|
||||
"expr": "{http.error.status_code} == 404",
|
||||
"name": "e"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"abort": true,
|
||||
"handler": "static_response"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"expression": {
|
||||
"expr": "{http.error.status_code} == 404",
|
||||
"name": "f"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"abort": true,
|
||||
"handler": "static_response"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"expression": {
|
||||
"expr": "{http.error.status_code} == 404",
|
||||
"name": "g"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
:8080 {
|
||||
root * ./
|
||||
file_server {
|
||||
etag_file_extensions .b3sum .sha256
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "vars",
|
||||
"root": "./"
|
||||
},
|
||||
{
|
||||
"etag_file_extensions": [
|
||||
".b3sum",
|
||||
".sha256"
|
||||
],
|
||||
"handler": "file_server",
|
||||
"hide": [
|
||||
"./Caddyfile"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,14 @@
|
||||
"issuers": [
|
||||
{
|
||||
"ca": "https://example.com",
|
||||
"challenges": {
|
||||
"http": {
|
||||
"alternate_port": 8080
|
||||
},
|
||||
"tls-alpn": {
|
||||
"alternate_port": 8443
|
||||
}
|
||||
},
|
||||
"email": "test@example.com",
|
||||
"external_account": {
|
||||
"key_id": "4K2scIVbBpNd-78scadB2g",
|
||||
|
||||
@@ -40,12 +40,6 @@ example.com
|
||||
"preferred_chains": {
|
||||
"smallest": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "zerossl",
|
||||
"preferred_chains": {
|
||||
"smallest": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -72,8 +72,12 @@ b.example.com {
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"a.example.com": "log0",
|
||||
"b.example.com": "log1"
|
||||
"a.example.com": [
|
||||
"log0"
|
||||
],
|
||||
"b.example.com": [
|
||||
"log1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
localhost
|
||||
|
||||
respond "To intercept"
|
||||
|
||||
intercept {
|
||||
@500 status 500
|
||||
replace_status @500 400
|
||||
|
||||
@all status 2xx 3xx 4xx 5xx
|
||||
replace_status @all {http.error.status_code}
|
||||
|
||||
replace_status {http.error.status_code}
|
||||
|
||||
@accel header X-Accel-Redirect *
|
||||
handle_response @accel {
|
||||
respond "Header X-Accel-Redirect!"
|
||||
}
|
||||
|
||||
@another {
|
||||
header X-Another *
|
||||
}
|
||||
handle_response @another {
|
||||
respond "Header X-Another!"
|
||||
}
|
||||
|
||||
@401 status 401
|
||||
handle_response @401 {
|
||||
respond "Status 401!"
|
||||
}
|
||||
|
||||
handle_response {
|
||||
respond "Any! This should be last in the JSON!"
|
||||
}
|
||||
|
||||
@403 {
|
||||
status 403
|
||||
}
|
||||
handle_response @403 {
|
||||
respond "Status 403!"
|
||||
}
|
||||
|
||||
@multi {
|
||||
status 401 403
|
||||
status 404
|
||||
header Foo *
|
||||
header Bar *
|
||||
}
|
||||
handle_response @multi {
|
||||
respond "Headers Foo, Bar AND statuses 401, 403 and 404!"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handle_response": [
|
||||
{
|
||||
"match": {
|
||||
"status_code": [
|
||||
500
|
||||
]
|
||||
},
|
||||
"status_code": 400
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"status_code": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
},
|
||||
"status_code": "{http.error.status_code}"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"headers": {
|
||||
"X-Accel-Redirect": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Header X-Accel-Redirect!",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"headers": {
|
||||
"X-Another": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Header X-Another!",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"status_code": [
|
||||
401
|
||||
]
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Status 401!",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"status_code": [
|
||||
403
|
||||
]
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Status 403!",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"headers": {
|
||||
"Bar": [
|
||||
"*"
|
||||
],
|
||||
"Foo": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
"status_code": [
|
||||
401,
|
||||
403,
|
||||
404
|
||||
]
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Headers Foo, Bar AND statuses 401, 403 and 404!",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"status_code": "{http.error.status_code}"
|
||||
},
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Any! This should be last in the JSON!",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"handler": "intercept"
|
||||
},
|
||||
{
|
||||
"body": "To intercept",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,9 @@ http://localhost:2020 {
|
||||
},
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost": ""
|
||||
"localhost": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"skip_unmapped_hosts": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
localhost {
|
||||
log {
|
||||
output file ./caddy.access.log
|
||||
}
|
||||
log health_check_log {
|
||||
output file ./caddy.access.health.log
|
||||
no_hostname
|
||||
}
|
||||
log general_log {
|
||||
output file ./caddy.access.general.log
|
||||
no_hostname
|
||||
}
|
||||
@healthCheck `header_regexp('User-Agent', '^some-regexp$') || path('/healthz*')`
|
||||
handle @healthCheck {
|
||||
log_name health_check_log general_log
|
||||
respond "Healthy"
|
||||
}
|
||||
|
||||
handle {
|
||||
respond "Hello World"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.general_log",
|
||||
"http.log.access.health_check_log",
|
||||
"http.log.access.log0"
|
||||
]
|
||||
},
|
||||
"general_log": {
|
||||
"writer": {
|
||||
"filename": "./caddy.access.general.log",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.general_log"
|
||||
]
|
||||
},
|
||||
"health_check_log": {
|
||||
"writer": {
|
||||
"filename": "./caddy.access.health.log",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.health_check_log"
|
||||
]
|
||||
},
|
||||
"log0": {
|
||||
"writer": {
|
||||
"filename": "./caddy.access.log",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"access_logger_names": [
|
||||
"health_check_log",
|
||||
"general_log"
|
||||
],
|
||||
"handler": "vars"
|
||||
},
|
||||
{
|
||||
"body": "Healthy",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"expression": {
|
||||
"expr": "header_regexp('User-Agent', '^some-regexp$') || path('/healthz*')",
|
||||
"name": "healthCheck"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group2",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Hello World",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost": [
|
||||
"log0"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
(log-both) {
|
||||
log {args[0]}-json {
|
||||
hostnames {args[0]}
|
||||
output file /var/log/{args[0]}.log
|
||||
format json
|
||||
}
|
||||
log {args[0]}-console {
|
||||
hostnames {args[0]}
|
||||
output file /var/log/{args[0]}.json
|
||||
format console
|
||||
}
|
||||
}
|
||||
|
||||
*.example.com {
|
||||
# Subdomains log to multiple files at once, with
|
||||
# different output files and formats.
|
||||
import log-both foo.example.com
|
||||
import log-both bar.example.com
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"bar.example.com-console": {
|
||||
"writer": {
|
||||
"filename": "/var/log/bar.example.com.json",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "console"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.bar.example.com-console"
|
||||
]
|
||||
},
|
||||
"bar.example.com-json": {
|
||||
"writer": {
|
||||
"filename": "/var/log/bar.example.com.log",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "json"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.bar.example.com-json"
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.bar.example.com-console",
|
||||
"http.log.access.bar.example.com-json",
|
||||
"http.log.access.foo.example.com-console",
|
||||
"http.log.access.foo.example.com-json"
|
||||
]
|
||||
},
|
||||
"foo.example.com-console": {
|
||||
"writer": {
|
||||
"filename": "/var/log/foo.example.com.json",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "console"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.foo.example.com-console"
|
||||
]
|
||||
},
|
||||
"foo.example.com-json": {
|
||||
"writer": {
|
||||
"filename": "/var/log/foo.example.com.log",
|
||||
"output": "file"
|
||||
},
|
||||
"encoder": {
|
||||
"format": "json"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.foo.example.com-json"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"bar.example.com": [
|
||||
"bar.example.com-json",
|
||||
"bar.example.com-console"
|
||||
],
|
||||
"foo.example.com": [
|
||||
"foo.example.com-json",
|
||||
"foo.example.com-console"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,15 @@ example.com:8443 {
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"bar.example.com": "log0",
|
||||
"baz.example.com": "log1",
|
||||
"foo.example.com": "log0"
|
||||
"bar.example.com": [
|
||||
"log0"
|
||||
],
|
||||
"baz.example.com": [
|
||||
"log1"
|
||||
],
|
||||
"foo.example.com": [
|
||||
"log0"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -99,7 +105,9 @@ example.com:8443 {
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"example.com": "log2"
|
||||
"example.com": [
|
||||
"log2"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,9 @@ http://localhost:8881 {
|
||||
},
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost": "foo"
|
||||
"localhost": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -81,7 +81,9 @@ http://localhost:8881 {
|
||||
},
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"localhost": "foo"
|
||||
"localhost": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,9 @@ example.com {
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"one.example.com": ""
|
||||
"one.example.com": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"skip_hosts": [
|
||||
"example.com",
|
||||
|
||||
@@ -46,6 +46,18 @@
|
||||
|
||||
@matcher12 client_ip private_ranges
|
||||
respond @matcher12 "client_ip matcher with private ranges"
|
||||
|
||||
@matcher13 {
|
||||
remote_ip 1.1.1.1
|
||||
remote_ip 2.2.2.2
|
||||
}
|
||||
respond @matcher13 "remote_ip merged"
|
||||
|
||||
@matcher14 {
|
||||
client_ip 1.1.1.1
|
||||
client_ip 2.2.2.2
|
||||
}
|
||||
respond @matcher14 "client_ip merged"
|
||||
}
|
||||
----------
|
||||
{
|
||||
@@ -146,6 +158,7 @@
|
||||
{
|
||||
"vars_regexp": {
|
||||
"{http.request.uri}": {
|
||||
"name": "matcher6",
|
||||
"pattern": "\\.([a-f0-9]{6})\\.(css|js)$"
|
||||
}
|
||||
}
|
||||
@@ -161,7 +174,10 @@
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"expression": "path('/foo*') \u0026\u0026 method('GET')"
|
||||
"expression": {
|
||||
"expr": "path('/foo*') \u0026\u0026 method('GET')",
|
||||
"name": "matcher7"
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
@@ -275,6 +291,42 @@
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"remote_ip": {
|
||||
"ranges": [
|
||||
"1.1.1.1",
|
||||
"2.2.2.2"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "remote_ip merged",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"client_ip": {
|
||||
"ranges": [
|
||||
"1.1.1.1",
|
||||
"2.2.2.2"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "client_ip merged",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
:8884 {
|
||||
reverse_proxy {
|
||||
dynamic srv {
|
||||
name foo
|
||||
refresh 5m
|
||||
grace_period 5s
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"dynamic_upstreams": {
|
||||
"grace_period": 5000000000,
|
||||
"name": "foo",
|
||||
"refresh": 300000000000,
|
||||
"source": "srv"
|
||||
},
|
||||
"handler": "reverse_proxy"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
:8884
|
||||
reverse_proxy 127.0.0.1:65535 {
|
||||
transport http {
|
||||
tls_trust_pool file {
|
||||
pem_file ../caddy.ca.cer
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {
|
||||
"ca": {
|
||||
"pem_files": [
|
||||
"../caddy.ca.cer"
|
||||
],
|
||||
"provider": "file"
|
||||
}
|
||||
}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "127.0.0.1:65535"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
:8884
|
||||
reverse_proxy 127.0.0.1:65535 {
|
||||
transport http {
|
||||
tls_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==
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {
|
||||
"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=="
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "127.0.0.1:65535"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -36,6 +36,7 @@ respond @match "{re.1}"
|
||||
"match": [
|
||||
{
|
||||
"path_regexp": {
|
||||
"name": "match",
|
||||
"pattern": "^/foo(.*)$"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,9 @@ c.example.com {
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"ca": "https://acme.zerossl.com/v2/DV90",
|
||||
"email": "abc@example.com",
|
||||
"module": "zerossl"
|
||||
"module": "acme"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -131,8 +131,9 @@ abc.de {
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"ca": "https://acme.zerossl.com/v2/DV90",
|
||||
"email": "my.email@example.com",
|
||||
"module": "zerossl"
|
||||
"module": "acme"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -86,8 +86,9 @@ http://localhost:8081 {
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"ca": "https://acme.zerossl.com/v2/DV90",
|
||||
"email": "abc@example.com",
|
||||
"module": "zerossl"
|
||||
"module": "acme"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+2
-1
@@ -54,8 +54,9 @@ example.com {
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"ca": "https://acme.zerossl.com/v2/DV90",
|
||||
"email": "foo@bar",
|
||||
"module": "zerossl"
|
||||
"module": "acme"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -58,14 +58,6 @@ tls {
|
||||
}
|
||||
},
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"ttl": 310000000000
|
||||
}
|
||||
},
|
||||
"module": "zerossl"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ tls {
|
||||
issuer acme {
|
||||
dns_ttl 5m10s
|
||||
}
|
||||
issuer zerossl {
|
||||
issuer zerossl api_key {
|
||||
dns_ttl 10m20s
|
||||
}
|
||||
}
|
||||
@@ -65,10 +65,9 @@ tls {
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"ttl": 620000000000
|
||||
}
|
||||
"api_key": "api_key",
|
||||
"cname_validation": {
|
||||
"ttl": 620000000000
|
||||
},
|
||||
"module": "zerossl"
|
||||
}
|
||||
|
||||
+5
-6
@@ -6,7 +6,7 @@ tls {
|
||||
propagation_delay 5m10s
|
||||
propagation_timeout 10m20s
|
||||
}
|
||||
issuer zerossl {
|
||||
issuer zerossl api_key {
|
||||
propagation_delay 5m30s
|
||||
propagation_timeout -1
|
||||
}
|
||||
@@ -68,11 +68,10 @@ tls {
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"propagation_delay": 330000000000,
|
||||
"propagation_timeout": -1
|
||||
}
|
||||
"api_key": "api_key",
|
||||
"cname_validation": {
|
||||
"propagation_delay": 330000000000,
|
||||
"propagation_timeout": -1
|
||||
},
|
||||
"module": "zerossl"
|
||||
}
|
||||
|
||||
@@ -60,15 +60,6 @@ tls {
|
||||
}
|
||||
},
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"propagation_delay": 310000000000,
|
||||
"propagation_timeout": 620000000000
|
||||
}
|
||||
},
|
||||
"module": "zerossl"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@@ -29,3 +30,30 @@ func TestBrowse(t *testing.T) {
|
||||
}
|
||||
tester.AssertResponseCode(req, 200)
|
||||
}
|
||||
|
||||
func TestRespondWithJSON(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
localhost {
|
||||
respond {http.request.body}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
res, _ := tester.AssertPostResponseBody("https://localhost:9443/",
|
||||
nil,
|
||||
bytes.NewBufferString(`{
|
||||
"greeting": "Hello, world!"
|
||||
}`), 200, `{
|
||||
"greeting": "Hello, world!"
|
||||
}`)
|
||||
if res.Header.Get("Content-Type") != "application/json" {
|
||||
t.Errorf("expected Content-Type to be application/json, but was %s", res.Header.Get("Content-Type"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestIntercept(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
respond /intercept "I'm a teapot" 408
|
||||
respond /no-intercept "I'm not a teapot"
|
||||
|
||||
intercept {
|
||||
@teapot status 408
|
||||
handle_response @teapot {
|
||||
respond /intercept "I'm a combined coffee/tea pot that is temporarily out of coffee" 503
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
|
||||
tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot")
|
||||
}
|
||||
@@ -334,7 +334,7 @@ func TestH2ToH1ChunkedResponse(t *testing.T) {
|
||||
ProtoMinor: 0,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
// underlying transport will automaticlly add gzip
|
||||
// underlying transport will automatically add gzip
|
||||
// req.Header.Set("Accept-Encoding", "gzip")
|
||||
go func() {
|
||||
fmt.Fprint(w, expectedBody)
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
foo
|
||||
+6
-3
@@ -1,13 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
# USAGE: go run -exec ./setcap.sh main.go <args...>
|
||||
# USAGE:
|
||||
# go run -exec ./setcap.sh main.go <args...>
|
||||
#
|
||||
# (Example: `go run -exec ./setcap.sh main.go run --config caddy.json`)
|
||||
#
|
||||
# For some reason this does not work on my Arch system, so if you find that's
|
||||
# the case, you can instead do: go build && ./setcap.sh ./caddy <args...>
|
||||
# but this will leave the ./caddy binary laying around.
|
||||
# the case, you can instead do:
|
||||
#
|
||||
# go build && ./setcap.sh ./caddy <args...>
|
||||
#
|
||||
# but this will leave the ./caddy binary laying around.
|
||||
#
|
||||
|
||||
sudo setcap cap_net_bind_service=+ep "$1"
|
||||
|
||||
+13
-4
@@ -163,9 +163,18 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
|
||||
// caddyfile adapter for convenience
|
||||
baseConfig := strings.ToLower(filepath.Base(configFile))
|
||||
baseConfigExt := filepath.Ext(baseConfig)
|
||||
if (strings.HasPrefix(baseConfig, "caddyfile") ||
|
||||
strings.HasSuffix(baseConfig, ".caddyfile")) &&
|
||||
(len(baseConfigExt) == 0 || caddyconfig.GetAdapter(baseConfigExt[1:]) == nil) &&
|
||||
startsOrEndsInCaddyfile := strings.HasPrefix(baseConfig, "caddyfile") || strings.HasSuffix(baseConfig, ".caddyfile")
|
||||
|
||||
// If the adapter is not specified, the config file is not starts with "caddyfile", and isn't a JSON file (e.g. Caddyfile.yaml),
|
||||
// then we don't know what the config format is.
|
||||
if adapterName == "" && startsOrEndsInCaddyfile && baseConfigExt != ".caddyfile" && baseConfigExt != ".json" {
|
||||
return nil, "", fmt.Errorf("ambiguous config file format; please specify adapter (use --adapter)")
|
||||
}
|
||||
|
||||
// If the config file starts or ends with "caddyfile",
|
||||
// the extension of the config file is not ".json", AND
|
||||
// the user did not specify an adapter, then we assume it's Caddyfile.
|
||||
if startsOrEndsInCaddyfile &&
|
||||
baseConfigExt != ".json" &&
|
||||
adapterName == "" {
|
||||
adapterName = "caddyfile"
|
||||
@@ -199,7 +208,7 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
|
||||
zap.Int("line", warn.Line))
|
||||
}
|
||||
config = adaptedConfig
|
||||
} else {
|
||||
} else if len(config) != 0 {
|
||||
// validate that the config is at least valid JSON
|
||||
err = json.Unmarshal(config, new(any))
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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 caddycmd
|
||||
|
||||
import (
|
||||
// For running in minimal environments, this can ease
|
||||
// headaches related to establishing TLS connections.
|
||||
// "Package fallback embeds a set of fallback X.509 trusted
|
||||
// roots in the application by automatically invoking
|
||||
// x509.SetFallbackRoots. This allows the application to
|
||||
// work correctly even if the operating system does not
|
||||
// provide a verifier or system roots pool. ... It's
|
||||
// recommended that only binaries, and not libraries,
|
||||
// import this package. This package must be kept up to
|
||||
// date for security and compatibility reasons."
|
||||
//
|
||||
// This is in its own file only because of conflicts
|
||||
// between gci and goimports when in main.go.
|
||||
// See https://github.com/daixiang0/gci/issues/76
|
||||
_ "golang.org/x/crypto/x509roots/fallback"
|
||||
)
|
||||
+31
-15
@@ -453,25 +453,29 @@ func (ctx Context) App(name string) (any, error) {
|
||||
return modVal, nil
|
||||
}
|
||||
|
||||
// AppIfConfigured returns an app by its name if it has been
|
||||
// configured. Can be called instead of App() to avoid
|
||||
// instantiating an empty app when that's not desirable. If
|
||||
// the app has not been loaded, nil is returned.
|
||||
//
|
||||
// We return any type instead of the App type because it is not
|
||||
// intended for the caller of this method to be the one to start
|
||||
// or stop App modules. The caller is expected to assert to the
|
||||
// concrete type.
|
||||
func (ctx Context) AppIfConfigured(name string) any {
|
||||
// AppIfConfigured is like App, but it returns an error if the
|
||||
// app has not been configured. This is useful when the app is
|
||||
// required and its absence is a configuration error; or when
|
||||
// the app is optional and you don't want to instantiate a
|
||||
// new one that hasn't been explicitly configured. If the app
|
||||
// is not in the configuration, the error wraps ErrNotConfigured.
|
||||
func (ctx Context) AppIfConfigured(name string) (any, error) {
|
||||
if ctx.cfg == nil {
|
||||
// this can happen if the currently-active context
|
||||
// is being accessed, but no config has successfully
|
||||
// been loaded yet
|
||||
return nil
|
||||
return nil, fmt.Errorf("app module %s: %w", name, ErrNotConfigured)
|
||||
}
|
||||
return ctx.cfg.apps[name]
|
||||
if app, ok := ctx.cfg.apps[name]; ok {
|
||||
return app, nil
|
||||
}
|
||||
appRaw := ctx.cfg.AppsRaw[name]
|
||||
if appRaw == nil {
|
||||
return nil, fmt.Errorf("app module %s: %w", name, ErrNotConfigured)
|
||||
}
|
||||
return ctx.App(name)
|
||||
}
|
||||
|
||||
// ErrNotConfigured indicates a module is not configured.
|
||||
var ErrNotConfigured = fmt.Errorf("module not configured")
|
||||
|
||||
// Storage returns the configured Caddy storage implementation.
|
||||
func (ctx Context) Storage() certmagic.Storage {
|
||||
return ctx.cfg.storage
|
||||
@@ -556,3 +560,15 @@ func (ctx Context) Module() Module {
|
||||
}
|
||||
return ctx.ancestry[len(ctx.ancestry)-1]
|
||||
}
|
||||
|
||||
// WithValue returns a new context with the given key-value pair.
|
||||
func (ctx *Context) WithValue(key, value any) Context {
|
||||
return Context{
|
||||
Context: context.WithValue(ctx.Context, key, value),
|
||||
moduleInstances: ctx.moduleInstances,
|
||||
cfg: ctx.cfg,
|
||||
ancestry: ctx.ancestry,
|
||||
cleanupFuncs: ctx.cleanupFuncs,
|
||||
exitFuncs: ctx.exitFuncs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,47 @@
|
||||
module github.com/caddyserver/caddy/v2
|
||||
|
||||
go 1.21
|
||||
go 1.21.0
|
||||
|
||||
toolchain go1.21.4
|
||||
toolchain go1.22.2
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/alecthomas/chroma/v2 v2.13.0
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/caddyserver/certmagic v0.21.3
|
||||
github.com/caddyserver/zerossl v0.1.3
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/google/cel-go v0.20.0
|
||||
github.com/google/cel-go v0.20.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/klauspost/compress v1.17.0
|
||||
github.com/klauspost/cpuid/v2 v2.2.5
|
||||
github.com/mholt/acmez v1.2.0
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/quic-go/quic-go v0.42.0
|
||||
github.com/smallstep/certificates v0.25.3-rc5
|
||||
github.com/smallstep/nosql v0.6.0
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/klauspost/cpuid/v2 v2.2.7
|
||||
github.com/mholt/acmez/v2 v2.0.1
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/quic-go/quic-go v0.44.0
|
||||
github.com/smallstep/certificates v0.26.1
|
||||
github.com/smallstep/nosql v0.6.1
|
||||
github.com/smallstep/truststore v0.13.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046
|
||||
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933
|
||||
github.com/yuin/goldmark v1.7.1
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0
|
||||
go.opentelemetry.io/otel v1.21.0
|
||||
go.opentelemetry.io/otel v1.24.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
|
||||
go.opentelemetry.io/otel/sdk v1.21.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
go.uber.org/zap v1.27.0
|
||||
go.uber.org/zap/exp v0.2.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/crypto v0.23.0
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/term v0.19.0
|
||||
golang.org/x/term v0.20.0
|
||||
golang.org/x/time v0.5.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -71,9 +73,9 @@ require (
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -112,12 +114,12 @@ require (
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgtype v1.14.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.18.3 // indirect
|
||||
github.com/libdns/libdns v0.2.1 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/miekg/dns v1.1.59 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
@@ -135,20 +137,20 @@ require (
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/urfave/cli v1.22.14 // indirect
|
||||
go.etcd.io/bbolt v1.3.8 // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.21.0
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
go.step.sm/cli-utils v0.8.0 // indirect
|
||||
go.step.sm/crypto v0.42.1
|
||||
go.step.sm/cli-utils v0.9.0 // indirect
|
||||
go.step.sm/crypto v0.45.0
|
||||
go.step.sm/linkedca v0.20.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/grpc v1.62.1 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.21.0 // indirect
|
||||
google.golang.org/grpc v1.63.2 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM=
|
||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM=
|
||||
cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI=
|
||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
|
||||
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
|
||||
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
|
||||
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
|
||||
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
|
||||
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
|
||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||
@@ -38,38 +43,40 @@ github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.27.9 h1:W9PbZAZAEcelhhjb7KuwUtf+Lbc+i7ByYJRuWLlnxyQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.27.9/go.mod h1:2tFmR7fQnOdQlM2ZCEPpFnBIQD1U8wmXmduBgZbOag0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0=
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
|
||||
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
@@ -164,8 +171,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/cel-go v0.20.0 h1:h4n6DOCppEMpWERzllyNkntl7JrDyxoE543KWS6BLpc=
|
||||
github.com/google/cel-go v0.20.0/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
|
||||
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
|
||||
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
|
||||
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
|
||||
@@ -174,8 +181,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||
github.com/google/go-tpm-tools v0.4.2 h1:iyaCPKt2N5Rd0yz0G8ANa022SgCNZkMpp+db6QELtvI=
|
||||
github.com/google/go-tpm-tools v0.4.2/go.mod h1:fGUDZu4tw3V4hUVuFHmiYgRd0c58/IXivn9v3Ea/ck4=
|
||||
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
|
||||
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
|
||||
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
||||
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU=
|
||||
@@ -188,8 +195,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
@@ -253,11 +260,11 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -275,8 +282,8 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||
@@ -292,10 +299,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
|
||||
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
@@ -322,8 +329,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
@@ -332,8 +339,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
|
||||
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
@@ -362,12 +369,12 @@ github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
|
||||
github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||
github.com/smallstep/certificates v0.25.3-rc5 h1:a5ALBerbePSIxcDrrzDd4Q4iggfJt8qy1t2WIL/26RU=
|
||||
github.com/smallstep/certificates v0.25.3-rc5/go.mod h1:PI/5pMaKYcnufMK2eVmsHZOS3IAzezYeUIWu7/I2ILs=
|
||||
github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o=
|
||||
github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA=
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
|
||||
github.com/smallstep/nosql v0.6.0 h1:ur7ysI8s9st0cMXnTvB8tA3+x5Eifmkb6hl4uqNV5jc=
|
||||
github.com/smallstep/nosql v0.6.0/go.mod h1:jOXwLtockXORUPPZ2MCUcIkGR6w0cN1QGZniY9DITQA=
|
||||
github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y=
|
||||
github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y=
|
||||
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg=
|
||||
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y=
|
||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw=
|
||||
@@ -408,8 +415,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 h1:8rUlviSVOEe7TMk7W0gIPrW8MqEzYfZHpsNWSf8s2vg=
|
||||
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
||||
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 h1:pV0H+XIvFoP7pl1MRtyPXh5hqoxB5I7snOtTHgrn6HU=
|
||||
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
||||
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
||||
@@ -429,14 +436,14 @@ github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvv
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
|
||||
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 h1:s2RzYOAqHVgG23q8fPWYChobUoZM6rJZ98EnylJr66w=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0/go.mod h1:Mv/tWNtZn+NbALDb2XcItP0OM3lWWZjAfSroINxfW+Y=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.17.0 h1:IX8d7l2uRw61BlmZBOTQFaK+y22j6vytMVTs9wFrO+c=
|
||||
@@ -447,24 +454,24 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWE
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0 h1:ufo2Vsz8l76eI47jFjuVyjyB3Ae2DmfiCV/o6Vc8ii0=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0/go.mod h1:SbKPj5XGp8K/sGm05XblaIABgMgw2jDczP8gGeuaVLk=
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.step.sm/cli-utils v0.8.0 h1:b/Tc1/m3YuQq+u3ghTFP7Dz5zUekZj6GUmd5pCvkEXQ=
|
||||
go.step.sm/cli-utils v0.8.0/go.mod h1:S77aISrC0pKuflqiDfxxJlUbiXcAanyJ4POOnzFSxD4=
|
||||
go.step.sm/crypto v0.42.1 h1:OmwHm3GJO8S4VGWL3k4+I+Q4P/F2s+j8msvTyGnh1Vg=
|
||||
go.step.sm/crypto v0.42.1/go.mod h1:yNcTLFQBnYCA75fC5bklBoTAT7y0dRZsB1TkinB8JMs=
|
||||
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
|
||||
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
|
||||
go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
|
||||
go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY=
|
||||
go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU=
|
||||
go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
@@ -503,17 +510,19 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -523,10 +532,10 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -558,8 +567,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -567,8 +576,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -578,8 +587,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -593,27 +603,25 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20=
|
||||
google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
|
||||
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
|
||||
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
|
||||
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
|
||||
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
+8
-8
@@ -149,11 +149,11 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net
|
||||
|
||||
func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
||||
var (
|
||||
ln any
|
||||
err error
|
||||
address string
|
||||
unixFileMode fs.FileMode
|
||||
isAbtractUnixSocket bool
|
||||
ln any
|
||||
err error
|
||||
address string
|
||||
unixFileMode fs.FileMode
|
||||
isAbstractUnixSocket bool
|
||||
)
|
||||
|
||||
// split unix socket addr early so lnKey
|
||||
@@ -164,7 +164,7 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isAbtractUnixSocket = strings.HasPrefix(address, "@")
|
||||
isAbstractUnixSocket = strings.HasPrefix(address, "@")
|
||||
} else {
|
||||
address = na.JoinHostPort(portOffset)
|
||||
}
|
||||
@@ -172,7 +172,7 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
||||
// if this is a unix socket, see if we already have it open,
|
||||
// force socket permissions on it and return early
|
||||
if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
|
||||
if !isAbtractUnixSocket {
|
||||
if !isAbstractUnixSocket {
|
||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
||||
}
|
||||
@@ -195,7 +195,7 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
||||
}
|
||||
|
||||
if IsUnixNetwork(na.Network) {
|
||||
if !isAbtractUnixSocket {
|
||||
if !isAbstractUnixSocket {
|
||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -372,7 +372,7 @@ func (cl *BaseLog) provisionCommon(ctx Context, logging *Logging) error {
|
||||
func (cl *BaseLog) buildCore() {
|
||||
// logs which only discard their output don't need
|
||||
// to perform encoding or any other processing steps
|
||||
// at all, so just shorcut to a nop core instead
|
||||
// at all, so just shortcut to a nop core instead
|
||||
if _, ok := cl.writerOpener.(*DiscardWriter); ok {
|
||||
cl.core = zapcore.NewNopCore()
|
||||
return
|
||||
|
||||
@@ -261,7 +261,9 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
|
||||
return nil, false
|
||||
})
|
||||
|
||||
logger.Debug("event", zap.Any("data", e.Data))
|
||||
logger = logger.With(zap.Any("data", e.Data))
|
||||
|
||||
logger.Debug("event")
|
||||
|
||||
// invoke handlers bound to the event by name and also all events; this for loop
|
||||
// iterates twice at most: once for the event name, once for "" (all events)
|
||||
@@ -282,6 +284,12 @@ func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) E
|
||||
default:
|
||||
}
|
||||
|
||||
// this log can be a useful sanity check to ensure your handlers are in fact being invoked
|
||||
// (see https://github.com/mholt/caddy-events-exec/issues/6)
|
||||
logger.Debug("invoking subscribed handler",
|
||||
zap.String("subscribed_to", eventName),
|
||||
zap.Any("handler", handler))
|
||||
|
||||
if err := handler.Handle(ctx, e); err != nil {
|
||||
aborted := errors.Is(err, ErrAborted)
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ func (xs *Filesystems) Provision(ctx caddy.Context) error {
|
||||
ctx.Filesystems().Register(f.Key, f.fileSystem)
|
||||
// remember to unregister the module when we are done
|
||||
xs.defers = append(xs.defers, func() {
|
||||
ctx.Logger().Debug("registering fs", zap.String("fs", f.Key))
|
||||
ctx.Logger().Debug("unregistering fs", zap.String("fs", f.Key))
|
||||
ctx.Filesystems().Unregister(f.Key)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -198,6 +198,9 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
// only enable access logs if configured
|
||||
if srv.Logs != nil {
|
||||
srv.accessLogger = app.logger.Named("log.access")
|
||||
if srv.Logs.Trace {
|
||||
srv.traceLogger = app.logger.Named("log.trace")
|
||||
}
|
||||
}
|
||||
|
||||
// the Go standard library does not let us serve only HTTP/2 using
|
||||
@@ -329,9 +332,10 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
|
||||
// Validate ensures the app's configuration is valid.
|
||||
func (app *App) Validate() error {
|
||||
// each server must use distinct listener addresses
|
||||
lnAddrs := make(map[string]string)
|
||||
|
||||
for srvName, srv := range app.Servers {
|
||||
// each server must use distinct listener addresses
|
||||
for _, addr := range srv.Listen {
|
||||
listenAddr, err := caddy.ParseNetworkAddress(addr)
|
||||
if err != nil {
|
||||
@@ -347,6 +351,15 @@ func (app *App) Validate() error {
|
||||
lnAddrs[addr] = srvName
|
||||
}
|
||||
}
|
||||
|
||||
// logger names must not have ports
|
||||
if srv.Logs != nil {
|
||||
for host := range srv.Logs.LoggerNames {
|
||||
if _, _, err := net.SplitHostPort(host); err == nil {
|
||||
return fmt.Errorf("server %s: logger name must not have a port: %s", srvName, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -526,7 +539,7 @@ func (app *App) Stop() error {
|
||||
ctx := context.Background()
|
||||
|
||||
// see if any listeners in our config will be closing or if they are continuing
|
||||
// hrough a reload; because if any are closing, we will enforce shutdown delay
|
||||
// through a reload; because if any are closing, we will enforce shutdown delay
|
||||
var delay bool
|
||||
scheduledTime := time.Now().Add(time.Duration(app.ShutdownDelay))
|
||||
if app.ShutdownDelay > 0 {
|
||||
|
||||
@@ -117,7 +117,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
srv.AutoHTTPS = new(AutoHTTPSConfig)
|
||||
}
|
||||
if srv.AutoHTTPS.Disabled {
|
||||
logger.Warn("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName))
|
||||
logger.Info("automatic HTTPS is completely disabled for server", zap.String("server_name", srvName))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
|
||||
// nothing left to do if auto redirects are disabled
|
||||
if srv.AutoHTTPS.DisableRedir {
|
||||
logger.Warn("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName))
|
||||
logger.Info("automatic HTTP->HTTPS redirects are disabled", zap.String("server_name", srvName))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
// port, we'll have to choose one, so prefer the HTTPS port
|
||||
if _, ok := redirDomains[d]; !ok ||
|
||||
addr.StartPort == uint(app.httpsPort()) {
|
||||
redirDomains[d] = []caddy.NetworkAddress{addr}
|
||||
redirDomains[d] = append(redirDomains[d], addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,6 +287,16 @@ uniqueDomainsLoop:
|
||||
for _, ap := range app.tlsApp.Automation.Policies {
|
||||
for _, apHost := range ap.Subjects() {
|
||||
if apHost == d {
|
||||
// if the automation policy has all internal subjects but no issuers,
|
||||
// it will default to CertMagic's issuers which are public CAs; use
|
||||
// our internal issuer instead
|
||||
if len(ap.Issuers) == 0 && ap.AllInternalSubjects() {
|
||||
iss := new(caddytls.InternalIssuer)
|
||||
if err := iss.Provision(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
ap.Issuers = append(ap.Issuers, iss)
|
||||
}
|
||||
continue uniqueDomainsLoop
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,10 @@ type MiddlewareHandler interface {
|
||||
}
|
||||
|
||||
// emptyHandler is used as a no-op handler.
|
||||
var emptyHandler Handler = HandlerFunc(func(http.ResponseWriter, *http.Request) error { return nil })
|
||||
var emptyHandler Handler = HandlerFunc(func(_ http.ResponseWriter, req *http.Request) error {
|
||||
SetVar(req.Context(), "unhandled", true)
|
||||
return nil
|
||||
})
|
||||
|
||||
// An implicit suffix middleware that, if reached, sets the StatusCode to the
|
||||
// error stored in the ErrorCtxKey. This is to prevent situations where the
|
||||
@@ -120,7 +123,7 @@ type ResponseHandler struct {
|
||||
Routes RouteList `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
// Provision sets up the routse in rh.
|
||||
// Provision sets up the routes in rh.
|
||||
func (rh *ResponseHandler) Provision(ctx caddy.Context) error {
|
||||
if rh.Routes != nil {
|
||||
err := rh.Routes.Provision(ctx)
|
||||
@@ -226,13 +229,22 @@ func StatusCodeMatches(actual, configured int) bool {
|
||||
// in the implementation of http.Dir. The root is assumed to
|
||||
// be a trusted path, but reqPath is not; and the output will
|
||||
// never be outside of root. The resulting path can be used
|
||||
// with the local file system.
|
||||
// with the local file system. If root is empty, the current
|
||||
// directory is assumed. If the cleaned request path is deemed
|
||||
// not local according to lexical processing (i.e. ignoring links),
|
||||
// it will be rejected as unsafe and only the root will be returned.
|
||||
func SanitizedPathJoin(root, reqPath string) string {
|
||||
if root == "" {
|
||||
root = "."
|
||||
}
|
||||
|
||||
path := filepath.Join(root, path.Clean("/"+reqPath))
|
||||
relPath := path.Clean("/" + reqPath)[1:] // clean path and trim the leading /
|
||||
if relPath != "" && !filepath.IsLocal(relPath) {
|
||||
// path is unsafe (see https://github.com/golang/go/issues/56336#issuecomment-1416214885)
|
||||
return root
|
||||
}
|
||||
|
||||
path := filepath.Join(root, filepath.FromSlash(relPath))
|
||||
|
||||
// filepath.Join also cleans the path, and cleaning strips
|
||||
// the trailing slash, so we need to re-add it afterwards.
|
||||
|
||||
@@ -3,6 +3,7 @@ package caddyhttp
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -12,9 +13,10 @@ func TestSanitizedPathJoin(t *testing.T) {
|
||||
// %2f = /
|
||||
// %5c = \
|
||||
for i, tc := range []struct {
|
||||
inputRoot string
|
||||
inputPath string
|
||||
expect string
|
||||
inputRoot string
|
||||
inputPath string
|
||||
expect string
|
||||
expectWindows string
|
||||
}{
|
||||
{
|
||||
inputPath: "",
|
||||
@@ -24,22 +26,28 @@ func TestSanitizedPathJoin(t *testing.T) {
|
||||
inputPath: "/",
|
||||
expect: ".",
|
||||
},
|
||||
{
|
||||
// fileserver.MatchFile passes an inputPath of "//" for some try_files values.
|
||||
// See https://github.com/caddyserver/caddy/issues/6352
|
||||
inputPath: "//",
|
||||
expect: filepath.FromSlash("./"),
|
||||
},
|
||||
{
|
||||
inputPath: "/foo",
|
||||
expect: "foo",
|
||||
},
|
||||
{
|
||||
inputPath: "/foo/",
|
||||
expect: "foo" + separator,
|
||||
expect: filepath.FromSlash("foo/"),
|
||||
},
|
||||
{
|
||||
inputPath: "/foo/bar",
|
||||
expect: filepath.Join("foo", "bar"),
|
||||
expect: filepath.FromSlash("foo/bar"),
|
||||
},
|
||||
{
|
||||
inputRoot: "/a",
|
||||
inputPath: "/foo/bar",
|
||||
expect: filepath.Join("/", "a", "foo", "bar"),
|
||||
expect: filepath.FromSlash("/a/foo/bar"),
|
||||
},
|
||||
{
|
||||
inputPath: "/foo/../bar",
|
||||
@@ -48,32 +56,34 @@ func TestSanitizedPathJoin(t *testing.T) {
|
||||
{
|
||||
inputRoot: "/a/b",
|
||||
inputPath: "/foo/../bar",
|
||||
expect: filepath.Join("/", "a", "b", "bar"),
|
||||
expect: filepath.FromSlash("/a/b/bar"),
|
||||
},
|
||||
{
|
||||
inputRoot: "/a/b",
|
||||
inputPath: "/..%2fbar",
|
||||
expect: filepath.Join("/", "a", "b", "bar"),
|
||||
expect: filepath.FromSlash("/a/b/bar"),
|
||||
},
|
||||
{
|
||||
inputRoot: "/a/b",
|
||||
inputPath: "/%2e%2e%2fbar",
|
||||
expect: filepath.Join("/", "a", "b", "bar"),
|
||||
expect: filepath.FromSlash("/a/b/bar"),
|
||||
},
|
||||
{
|
||||
// inputPath fails the IsLocal test so only the root is returned,
|
||||
// but with a trailing slash since one was included in inputPath
|
||||
inputRoot: "/a/b",
|
||||
inputPath: "/%2e%2e%2f%2e%2e%2f",
|
||||
expect: filepath.Join("/", "a", "b") + separator,
|
||||
expect: filepath.FromSlash("/a/b/"),
|
||||
},
|
||||
{
|
||||
inputRoot: "/a/b",
|
||||
inputPath: "/foo%2fbar",
|
||||
expect: filepath.Join("/", "a", "b", "foo", "bar"),
|
||||
expect: filepath.FromSlash("/a/b/foo/bar"),
|
||||
},
|
||||
{
|
||||
inputRoot: "/a/b",
|
||||
inputPath: "/foo%252fbar",
|
||||
expect: filepath.Join("/", "a", "b", "foo%2fbar"),
|
||||
expect: filepath.FromSlash("/a/b/foo%2fbar"),
|
||||
},
|
||||
{
|
||||
inputRoot: "C:\\www",
|
||||
@@ -81,9 +91,16 @@ func TestSanitizedPathJoin(t *testing.T) {
|
||||
expect: filepath.Join("C:\\www", "foo", "bar"),
|
||||
},
|
||||
{
|
||||
inputRoot: "C:\\www",
|
||||
inputPath: "/D:\\foo\\bar",
|
||||
expect: filepath.Join("C:\\www", "D:\\foo\\bar"),
|
||||
inputRoot: "C:\\www",
|
||||
inputPath: "/D:\\foo\\bar",
|
||||
expect: filepath.Join("C:\\www", "D:\\foo\\bar"),
|
||||
expectWindows: filepath.Join("C:\\www"), // inputPath fails IsLocal on Windows
|
||||
},
|
||||
{
|
||||
// https://github.com/golang/go/issues/56336#issuecomment-1416214885
|
||||
inputRoot: "root",
|
||||
inputPath: "/a/b/../../c",
|
||||
expect: filepath.FromSlash("root/c"),
|
||||
},
|
||||
} {
|
||||
// we don't *need* to use an actual parsed URL, but it
|
||||
@@ -96,6 +113,9 @@ func TestSanitizedPathJoin(t *testing.T) {
|
||||
t.Fatalf("Test %d: invalid URL: %v", i, err)
|
||||
}
|
||||
actual := SanitizedPathJoin(tc.inputRoot, u.Path)
|
||||
if runtime.GOOS == "windows" && tc.expectWindows != "" {
|
||||
tc.expect = tc.expectWindows
|
||||
}
|
||||
if actual != tc.expect {
|
||||
t.Errorf("Test %d: SanitizedPathJoin('%s', '%s') => '%s' (expected '%s')",
|
||||
i, tc.inputRoot, tc.inputPath, actual, tc.expect)
|
||||
|
||||
@@ -62,7 +62,12 @@ type MatchExpression struct {
|
||||
// The CEL expression to evaluate. Any Caddy placeholders
|
||||
// will be expanded and situated into proper CEL function
|
||||
// calls before evaluating.
|
||||
Expr string
|
||||
Expr string `json:"expr,omitempty"`
|
||||
|
||||
// Name is an optional name for this matcher.
|
||||
// This is used to populate the name for regexp
|
||||
// matchers that appear in the expression.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
expandedExpr string
|
||||
prg cel.Program
|
||||
@@ -81,12 +86,36 @@ func (MatchExpression) CaddyModule() caddy.ModuleInfo {
|
||||
|
||||
// MarshalJSON marshals m's expression.
|
||||
func (m MatchExpression) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(m.Expr)
|
||||
// if the name is empty, then we can marshal just the expression string
|
||||
if m.Name == "" {
|
||||
return json.Marshal(m.Expr)
|
||||
}
|
||||
// otherwise, we need to marshal the full object, using an
|
||||
// anonymous struct to avoid infinite recursion
|
||||
return json.Marshal(struct {
|
||||
Expr string `json:"expr"`
|
||||
Name string `json:"name"`
|
||||
}{
|
||||
Expr: m.Expr,
|
||||
Name: m.Name,
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals m's expression.
|
||||
func (m *MatchExpression) UnmarshalJSON(data []byte) error {
|
||||
return json.Unmarshal(data, &m.Expr)
|
||||
// if the data is a string, then it's just the expression
|
||||
if data[0] == '"' {
|
||||
return json.Unmarshal(data, &m.Expr)
|
||||
}
|
||||
// otherwise, it's a full object, so unmarshal it,
|
||||
// using an temp map to avoid infinite recursion
|
||||
var tmpJson map[string]any
|
||||
err := json.Unmarshal(data, &tmpJson)
|
||||
*m = MatchExpression{
|
||||
Expr: tmpJson["expr"].(string),
|
||||
Name: tmpJson["name"].(string),
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Provision sets ups m.
|
||||
@@ -109,6 +138,11 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
||||
matcherLibProducers = append(matcherLibProducers, p)
|
||||
}
|
||||
}
|
||||
|
||||
// add the matcher name to the context so that the matcher name
|
||||
// can be used by regexp matchers being provisioned
|
||||
ctx = ctx.WithValue(MatcherNameCtxKey, m.Name)
|
||||
|
||||
// Assemble the compilation and program options from the different library
|
||||
// producers into a single cel.Library implementation.
|
||||
matcherEnvOpts := []cel.EnvOption{}
|
||||
@@ -197,6 +231,11 @@ func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
// quoted string; commonly quotes are used in Caddyfile to
|
||||
// define the expression
|
||||
m.Expr = d.Val()
|
||||
|
||||
// use the named matcher's name, to fill regexp
|
||||
// matchers names by default
|
||||
m.Name = d.GetContextString(caddyfile.MatcherNameCtxKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -673,6 +712,8 @@ var httpRequestObjectType = cel.ObjectType("http.Request")
|
||||
// The name of the CEL function which accesses Replacer values.
|
||||
const placeholderFuncName = "caddyPlaceholder"
|
||||
|
||||
const MatcherNameCtxKey = "matcher_name"
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*MatchExpression)(nil)
|
||||
|
||||
@@ -380,7 +380,9 @@ func TestMatchExpressionMatch(t *testing.T) {
|
||||
for _, tst := range matcherTests {
|
||||
tc := tst
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.expression.Provision(caddy.Context{})
|
||||
caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
||||
defer cancel()
|
||||
err := tc.expression.Provision(caddyCtx)
|
||||
if err != nil {
|
||||
if !tc.wantErr {
|
||||
t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tc.wantErr)
|
||||
@@ -482,7 +484,9 @@ func TestMatchExpressionProvision(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.expression.Provision(caddy.Context{}); (err != nil) != tt.wantErr {
|
||||
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
||||
defer cancel()
|
||||
if err := tt.expression.Provision(ctx); (err != nil) != tt.wantErr {
|
||||
t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -156,6 +156,21 @@ func (enc *Encode) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
|
||||
}
|
||||
w = enc.openResponseWriter(encName, w)
|
||||
defer w.(*responseWriter).Close()
|
||||
|
||||
// to comply with RFC 9110 section 8.8.3(.3), we modify the Etag when encoding
|
||||
// by appending a hyphen and the encoder name; the problem is, the client will
|
||||
// send back that Etag in a If-None-Match header, but upstream handlers that set
|
||||
// the Etag in the first place don't know that we appended to their Etag! so here
|
||||
// we have to strip our addition so the upstream handlers can still honor client
|
||||
// caches without knowing about our changes...
|
||||
if etag := r.Header.Get("If-None-Match"); etag != "" && !strings.HasPrefix(etag, "W/") {
|
||||
ourSuffix := "-" + encName + `"`
|
||||
if strings.HasSuffix(etag, ourSuffix) {
|
||||
etag = strings.TrimSuffix(etag, ourSuffix) + `"`
|
||||
r.Header.Set("If-None-Match", etag)
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -220,6 +235,14 @@ type responseWriter struct {
|
||||
func (rw *responseWriter) WriteHeader(status int) {
|
||||
rw.statusCode = status
|
||||
|
||||
// See #5849 and RFC 9110 section 15.4.5 (https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4.5) - 304
|
||||
// Not Modified must have certain headers set as if it was a 200 response, and according to the issue
|
||||
// we would miss the Vary header in this case when compression was also enabled; note that we set this
|
||||
// header in the responseWriter.init() method but that is only called if we are writing a response body
|
||||
if status == http.StatusNotModified && !hasVaryValue(rw.Header(), "Accept-Encoding") {
|
||||
rw.Header().Add("Vary", "Accept-Encoding")
|
||||
}
|
||||
|
||||
// write status immediately when status code is informational
|
||||
// see: https://caddy.community/t/disappear-103-early-hints-response-with-encode-enable-caddy-v2-7-6/23081/5
|
||||
if 100 <= status && status <= 199 {
|
||||
@@ -326,17 +349,44 @@ func (rw *responseWriter) Unwrap() http.ResponseWriter {
|
||||
|
||||
// init should be called before we write a response, if rw.buf has contents.
|
||||
func (rw *responseWriter) init() {
|
||||
if rw.Header().Get("Content-Encoding") == "" && isEncodeAllowed(rw.Header()) &&
|
||||
hdr := rw.Header()
|
||||
if hdr.Get("Content-Encoding") == "" && isEncodeAllowed(hdr) &&
|
||||
rw.config.Match(rw) {
|
||||
rw.w = rw.config.writerPools[rw.encodingName].Get().(Encoder)
|
||||
rw.w.Reset(rw.ResponseWriter)
|
||||
rw.Header().Del("Content-Length") // https://github.com/golang/go/issues/14975
|
||||
rw.Header().Set("Content-Encoding", rw.encodingName)
|
||||
rw.Header().Add("Vary", "Accept-Encoding")
|
||||
rw.Header().Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
|
||||
hdr.Del("Content-Length") // https://github.com/golang/go/issues/14975
|
||||
hdr.Set("Content-Encoding", rw.encodingName)
|
||||
if !hasVaryValue(hdr, "Accept-Encoding") {
|
||||
hdr.Add("Vary", "Accept-Encoding")
|
||||
}
|
||||
hdr.Del("Accept-Ranges") // we don't know ranges for dynamically-encoded content
|
||||
|
||||
// strong ETags need to be distinct depending on the encoding ("selected representation")
|
||||
// see RFC 9110 section 8.8.3.3:
|
||||
// https://www.rfc-editor.org/rfc/rfc9110.html#name-example-entity-tags-varying
|
||||
// I don't know a great way to do this... how about appending? That's a neat trick!
|
||||
// (We have to strip the value we append from If-None-Match headers before
|
||||
// sending subsequent requests back upstream, however, since upstream handlers
|
||||
// don't know about our appending to their Etag since they've already done their work)
|
||||
if etag := hdr.Get("Etag"); etag != "" && !strings.HasPrefix(etag, "W/") {
|
||||
etag = fmt.Sprintf(`%s-%s"`, strings.TrimSuffix(etag, `"`), rw.encodingName)
|
||||
hdr.Set("Etag", etag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasVaryValue(hdr http.Header, target string) bool {
|
||||
for _, vary := range hdr.Values("Vary") {
|
||||
vals := strings.Split(vary, ",")
|
||||
for _, val := range vals {
|
||||
if strings.EqualFold(strings.TrimSpace(val), target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AcceptedEncodings returns the list of encodings that the
|
||||
// client supports, in descending order of preference.
|
||||
// The client preference via q-factor and the server
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package caddyzstd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
@@ -27,7 +29,13 @@ func init() {
|
||||
}
|
||||
|
||||
// Zstd can create Zstandard encoders.
|
||||
type Zstd struct{}
|
||||
type Zstd struct {
|
||||
// The compression level. Accepted values: fastest, better, best, default.
|
||||
Level string `json:"level,omitempty"`
|
||||
|
||||
// Compression level refer to type constants value from zstd.SpeedFastest to zstd.SpeedBestCompression
|
||||
level zstd.EncoderLevel
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (Zstd) CaddyModule() caddy.ModuleInfo {
|
||||
@@ -39,6 +47,37 @@ func (Zstd) CaddyModule() caddy.ModuleInfo {
|
||||
|
||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
||||
func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume option name
|
||||
if !d.NextArg() {
|
||||
return nil
|
||||
}
|
||||
levelStr := d.Val()
|
||||
if ok, _ := zstd.EncoderLevelFromString(levelStr); !ok {
|
||||
return d.Errf("unexpected compression level, use one of '%s', '%s', '%s', '%s'",
|
||||
zstd.SpeedFastest,
|
||||
zstd.SpeedBetterCompression,
|
||||
zstd.SpeedBestCompression,
|
||||
zstd.SpeedDefault,
|
||||
)
|
||||
}
|
||||
z.Level = levelStr
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provision provisions z's configuration.
|
||||
func (z *Zstd) Provision(ctx caddy.Context) error {
|
||||
if z.Level == "" {
|
||||
z.Level = zstd.SpeedDefault.String()
|
||||
}
|
||||
var ok bool
|
||||
if ok, z.level = zstd.EncoderLevelFromString(z.Level); !ok {
|
||||
return fmt.Errorf("unexpected compression level, use one of '%s', '%s', '%s', '%s'",
|
||||
zstd.SpeedFastest,
|
||||
zstd.SpeedDefault,
|
||||
zstd.SpeedBetterCompression,
|
||||
zstd.SpeedBestCompression,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -51,7 +90,13 @@ func (z Zstd) NewEncoder() encode.Encoder {
|
||||
// The default of 8MB for the window is
|
||||
// too large for many clients, so we limit
|
||||
// it to 128K to lighten their load.
|
||||
writer, _ := zstd.NewWriter(nil, zstd.WithWindowSize(128<<10), zstd.WithEncoderConcurrency(1), zstd.WithZeroFrames(true))
|
||||
writer, _ := zstd.NewWriter(
|
||||
nil,
|
||||
zstd.WithWindowSize(128<<10),
|
||||
zstd.WithEncoderConcurrency(1),
|
||||
zstd.WithZeroFrames(true),
|
||||
zstd.WithEncoderLevel(z.level),
|
||||
)
|
||||
return writer
|
||||
}
|
||||
|
||||
@@ -59,4 +104,5 @@ func (z Zstd) NewEncoder() encode.Encoder {
|
||||
var (
|
||||
_ encode.Encoding = (*Zstd)(nil)
|
||||
_ caddyfile.Unmarshaler = (*Zstd)(nil)
|
||||
_ caddy.Provisioner = (*Zstd)(nil)
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
@@ -104,6 +105,18 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
|
||||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
w.Header().Add("Vary", "Accept, Accept-Encoding")
|
||||
|
||||
// speed up browser/client experience and caching by supporting If-Modified-Since
|
||||
if ifModSinceStr := r.Header.Get("If-Modified-Since"); ifModSinceStr != "" {
|
||||
ifModSince, err := time.ParseInLocation(http.TimeFormat, ifModSinceStr, time.Local)
|
||||
lastModTrunc := listing.lastModified.Truncate(time.Second)
|
||||
if err == nil && (lastModTrunc.Equal(ifModSince) || lastModTrunc.Before(ifModSince)) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
fsrv.browseApplyQueryParams(w, r, listing)
|
||||
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
@@ -111,6 +124,7 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
acceptHeader := strings.ToLower(strings.Join(r.Header["Accept"], ","))
|
||||
w.Header().Set("Last-Modified", listing.lastModified.Format(http.TimeFormat))
|
||||
|
||||
switch {
|
||||
case strings.Contains(acceptHeader, "application/json"):
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</svg>
|
||||
{{- else if .HasExt ".jpg" ".jpeg" ".png" ".gif" ".webp" ".tiff" ".bmp" ".heif" ".heic" ".svg"}}
|
||||
{{- if eq .Tpl.Layout "grid"}}
|
||||
<img loading="lazy" src="{{html .Name}}">
|
||||
<img loading="lazy" src="{{.Name | pathEscape}}">
|
||||
{{- else}}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-photo" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
|
||||
@@ -63,6 +63,12 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
||||
continue
|
||||
}
|
||||
|
||||
// keep track of the most recently modified item in the listing
|
||||
modTime := info.ModTime()
|
||||
if tplCtx.lastModified.IsZero() || modTime.After(tplCtx.lastModified) {
|
||||
tplCtx.lastModified = modTime
|
||||
}
|
||||
|
||||
isDir := entry.IsDir() || fsrv.isSymlinkTargetDir(fileSystem, info, root, urlPath)
|
||||
|
||||
// add the slash after the escape of path to avoid escaping the slash as well
|
||||
@@ -108,7 +114,7 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
||||
Name: name,
|
||||
Size: size,
|
||||
URL: u.String(),
|
||||
ModTime: info.ModTime().UTC(),
|
||||
ModTime: modTime.UTC(),
|
||||
Mode: info.Mode(),
|
||||
Tpl: tplCtx, // a reference up to the template context is useful
|
||||
SymlinkPath: symlinkPath,
|
||||
@@ -126,7 +132,7 @@ type browseTemplateContext struct {
|
||||
// The full path of the request.
|
||||
Path string `json:"path"`
|
||||
|
||||
// Whether the parent directory is browseable.
|
||||
// Whether the parent directory is browsable.
|
||||
CanGoUp bool `json:"can_go_up"`
|
||||
|
||||
// The items (files and folders) in the path.
|
||||
@@ -155,6 +161,10 @@ type browseTemplateContext struct {
|
||||
|
||||
// Display format (list or grid)
|
||||
Layout string `json:"layout,omitempty"`
|
||||
|
||||
// The most recent file modification date in the listing.
|
||||
// Used for HTTP header purposes.
|
||||
lastModified time.Time
|
||||
}
|
||||
|
||||
// Breadcrumbs returns l.Path where every element maps
|
||||
|
||||
@@ -164,6 +164,13 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
}
|
||||
fsrv.PassThru = true
|
||||
|
||||
case "etag_file_extensions":
|
||||
etagFileExtensions := d.RemainingArgs()
|
||||
if len(etagFileExtensions) == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
fsrv.EtagFileExtensions = etagFileExtensions
|
||||
|
||||
default:
|
||||
return d.Errf("unknown subdirective '%s'", d.Val())
|
||||
}
|
||||
|
||||
@@ -360,7 +360,9 @@ func TestMatchExpressionMatch(t *testing.T) {
|
||||
for _, tst := range expressionTests {
|
||||
tc := tst
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.expression.Provision(caddy.Context{})
|
||||
caddyCtx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
||||
defer cancel()
|
||||
err := tc.expression.Provision(caddyCtx)
|
||||
if err != nil {
|
||||
if !tc.wantErr {
|
||||
t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tc.wantErr)
|
||||
|
||||
@@ -60,7 +60,7 @@ func init() {
|
||||
// 404 response. Alternatively, file browsing can be enabled with
|
||||
// the "browse" parameter which shows a list of files when directories
|
||||
// are requested if no index file is present. If "browse" is enabled,
|
||||
// Caddy may serve a JSON array of the dirctory listing when the `Accept`
|
||||
// Caddy may serve a JSON array of the directory listing when the `Accept`
|
||||
// header mentions `application/json` with the following structure:
|
||||
//
|
||||
// [{
|
||||
@@ -161,6 +161,12 @@ type FileServer struct {
|
||||
PrecompressedOrder []string `json:"precompressed_order,omitempty"`
|
||||
precompressors map[string]encode.Precompressed
|
||||
|
||||
// List of file extensions to try to read Etags from.
|
||||
// If set, file Etags will be read from sidecar files
|
||||
// with any of these suffixes, instead of generating
|
||||
// our own Etag.
|
||||
EtagFileExtensions []string `json:"etag_file_extensions,omitempty"`
|
||||
|
||||
fsmap caddy.FileSystems
|
||||
|
||||
logger *zap.Logger
|
||||
@@ -365,9 +371,17 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||
}
|
||||
|
||||
var file fs.File
|
||||
respHeader := w.Header()
|
||||
|
||||
// etag is usually unset, but if the user knows what they're doing, let them override it
|
||||
etag := w.Header().Get("Etag")
|
||||
etag := respHeader.Get("Etag")
|
||||
|
||||
// static file responses are often compressed, either on-the-fly
|
||||
// or with precompressed sidecar files; in any case, the headers
|
||||
// should contain "Vary: Accept-Encoding" even when not compressed
|
||||
// so caches can craft a reliable key (according to REDbot results)
|
||||
// see #5849
|
||||
respHeader.Add("Vary", "Accept-Encoding")
|
||||
|
||||
// check for precompressed files
|
||||
for _, ae := range encode.AcceptedEncodings(r, fsrv.PrecompressedOrder) {
|
||||
@@ -392,9 +406,16 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||
continue
|
||||
}
|
||||
defer file.Close()
|
||||
w.Header().Set("Content-Encoding", ae)
|
||||
w.Header().Del("Accept-Ranges")
|
||||
w.Header().Add("Vary", "Accept-Encoding")
|
||||
respHeader.Set("Content-Encoding", ae)
|
||||
respHeader.Del("Accept-Ranges")
|
||||
|
||||
// try to get the etag from pre computed files if an etag suffix list was provided
|
||||
if etag == "" && fsrv.EtagFileExtensions != nil {
|
||||
etag, err = fsrv.getEtagFromFile(fileSystem, compressedFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// don't assign info = compressedInfo because sidecars are kind
|
||||
// of transparent; however we do need to set the Etag:
|
||||
@@ -420,7 +441,13 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||
return err // error is already structured
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// try to get the etag from pre computed files if an etag suffix list was provided
|
||||
if etag == "" && fsrv.EtagFileExtensions != nil {
|
||||
etag, err = fsrv.getEtagFromFile(fileSystem, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if etag == "" {
|
||||
etag = calculateEtag(info)
|
||||
}
|
||||
@@ -434,7 +461,7 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||
// to repeat the error; just continue because we're probably
|
||||
// trying to write an error page response (see issue #5703)
|
||||
if _, ok := r.Context().Value(caddyhttp.ErrorCtxKey).(error); !ok {
|
||||
w.Header().Add("Allow", "GET, HEAD")
|
||||
respHeader.Add("Allow", "GET, HEAD")
|
||||
return caddyhttp.Error(http.StatusMethodNotAllowed, nil)
|
||||
}
|
||||
}
|
||||
@@ -442,16 +469,16 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||
// set the Etag - note that a conditional If-None-Match request is handled
|
||||
// by http.ServeContent below, which checks against this Etag value
|
||||
if etag != "" {
|
||||
w.Header().Set("Etag", etag)
|
||||
respHeader.Set("Etag", etag)
|
||||
}
|
||||
|
||||
if w.Header().Get("Content-Type") == "" {
|
||||
if respHeader.Get("Content-Type") == "" {
|
||||
mtyp := mime.TypeByExtension(filepath.Ext(filename))
|
||||
if mtyp == "" {
|
||||
// do not allow Go to sniff the content-type; see https://www.youtube.com/watch?v=8t8JYpt0egE
|
||||
w.Header()["Content-Type"] = nil
|
||||
respHeader["Content-Type"] = nil
|
||||
} else {
|
||||
w.Header().Set("Content-Type", mtyp)
|
||||
respHeader.Set("Content-Type", mtyp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,19 +651,48 @@ func (fsrv *FileServer) notFound(w http.ResponseWriter, r *http.Request, next ca
|
||||
return caddyhttp.Error(http.StatusNotFound, nil)
|
||||
}
|
||||
|
||||
// calculateEtag produces a strong etag by default, although, for
|
||||
// efficiency reasons, it does not actually consume the contents
|
||||
// of the file to make a hash of all the bytes. ¯\_(ツ)_/¯
|
||||
// Prefix the etag with "W/" to convert it into a weak etag.
|
||||
// See: https://tools.ietf.org/html/rfc7232#section-2.3
|
||||
// calculateEtag computes an entity tag using a strong validator
|
||||
// without consuming the contents of the file. It requires the
|
||||
// file info contain the correct size and modification time.
|
||||
// It strives to implement the semantics regarding ETags as defined
|
||||
// by RFC 9110 section 8.8.3 and 8.8.1. See
|
||||
// https://www.rfc-editor.org/rfc/rfc9110.html#section-8.8.3.
|
||||
//
|
||||
// As our implementation uses file modification timestamp and size,
|
||||
// note the following from RFC 9110 section 8.8.1: "A representation's
|
||||
// modification time, if defined with only one-second resolution,
|
||||
// might be a weak validator if it is possible for the representation to
|
||||
// be modified twice during a single second and retrieved between those
|
||||
// modifications." The ext4 file system, which underpins the vast majority
|
||||
// of Caddy deployments, stores mod times with millisecond precision,
|
||||
// which we consider precise enough to qualify as a strong validator.
|
||||
func calculateEtag(d os.FileInfo) string {
|
||||
mtime := d.ModTime().Unix()
|
||||
if mtime == 0 || mtime == 1 {
|
||||
mtime := d.ModTime()
|
||||
if mtimeUnix := mtime.Unix(); mtimeUnix == 0 || mtimeUnix == 1 {
|
||||
return "" // not useful anyway; see issue #5548
|
||||
}
|
||||
t := strconv.FormatInt(mtime, 36)
|
||||
s := strconv.FormatInt(d.Size(), 36)
|
||||
return `"` + t + s + `"`
|
||||
var sb strings.Builder
|
||||
sb.WriteRune('"')
|
||||
sb.WriteString(strconv.FormatInt(mtime.UnixNano(), 36))
|
||||
sb.WriteString(strconv.FormatInt(d.Size(), 36))
|
||||
sb.WriteRune('"')
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Finds the first corresponding etag file for a given file in the file system and return its content
|
||||
func (fsrv *FileServer) getEtagFromFile(fileSystem fs.FS, filename string) (string, error) {
|
||||
for _, suffix := range fsrv.EtagFileExtensions {
|
||||
etagFilename := filename + suffix
|
||||
etag, err := fs.ReadFile(fileSystem, etagFilename)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot read etag from file %s: %v", etagFilename, err)
|
||||
}
|
||||
return string(etag), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// redirect performs a redirect to a given path. The 'toPath' parameter
|
||||
|
||||
@@ -184,7 +184,7 @@ type RespHeaderOps struct {
|
||||
Require *caddyhttp.ResponseMatcher `json:"require,omitempty"`
|
||||
|
||||
// If true, header operations will be deferred until
|
||||
// they are written out. Superceded if Require is set.
|
||||
// they are written out. Superseded if Require is set.
|
||||
// Usually you will need to set this to true if any
|
||||
// fields are being deleted.
|
||||
Deferred bool `json:"deferred,omitempty"`
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
// 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 intercept
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(Intercept{})
|
||||
httpcaddyfile.RegisterHandlerDirective("intercept", parseCaddyfile)
|
||||
}
|
||||
|
||||
// Intercept is a middleware that intercepts then replaces or modifies the original response.
|
||||
// It can, for instance, be used to implement X-Sendfile/X-Accel-Redirect-like features
|
||||
// when using modules like FrankenPHP or Caddy Snake.
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
type Intercept struct {
|
||||
// List of handlers and their associated matchers to evaluate
|
||||
// after successful response generation.
|
||||
// The first handler that matches the original response will
|
||||
// be invoked. The original response body will not be
|
||||
// written to the client;
|
||||
// it is up to the handler to finish handling the response.
|
||||
//
|
||||
// Three new placeholders are available in this handler chain:
|
||||
// - `{http.intercept.status_code}` The status code from the response
|
||||
// - `{http.intercept.status_text}` The status text from the response
|
||||
// - `{http.intercept.header.*}` The headers from the response
|
||||
HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"`
|
||||
|
||||
// Holds the named response matchers from the Caddyfile while adapting
|
||||
responseMatchers map[string]caddyhttp.ResponseMatcher
|
||||
|
||||
// Holds the handle_response Caddyfile tokens while adapting
|
||||
handleResponseSegments []*caddyfile.Dispenser
|
||||
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
func (Intercept) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "http.handlers.intercept",
|
||||
New: func() caddy.Module { return new(Intercept) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision ensures that i is set up properly before use.
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
func (irh *Intercept) Provision(ctx caddy.Context) error {
|
||||
// set up any response routes
|
||||
for i, rh := range irh.HandleResponse {
|
||||
err := rh.Provision(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("provisioning response handler %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
irh.logger = ctx.Logger()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: handle status code replacement
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
type interceptedResponseHandler struct {
|
||||
caddyhttp.ResponseRecorder
|
||||
replacer *caddy.Replacer
|
||||
handler caddyhttp.ResponseHandler
|
||||
handlerIndex int
|
||||
statusCode int
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
func (irh interceptedResponseHandler) WriteHeader(statusCode int) {
|
||||
if irh.statusCode != 0 && (statusCode < 100 || statusCode >= 200) {
|
||||
irh.ResponseRecorder.WriteHeader(irh.statusCode)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
irh.ResponseRecorder.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
rec := interceptedResponseHandler{replacer: repl}
|
||||
rec.ResponseRecorder = caddyhttp.NewResponseRecorder(w, buf, func(status int, header http.Header) bool {
|
||||
// see if any response handler is configured for this original response
|
||||
for i, rh := range ir.HandleResponse {
|
||||
if rh.Match != nil && !rh.Match.Match(status, header) {
|
||||
continue
|
||||
}
|
||||
rec.handler = rh
|
||||
rec.handlerIndex = i
|
||||
|
||||
// if configured to only change the status code,
|
||||
// do that then stream
|
||||
if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" {
|
||||
sc, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, ""))
|
||||
if err != nil {
|
||||
rec.statusCode = http.StatusInternalServerError
|
||||
} else {
|
||||
rec.statusCode = sc
|
||||
}
|
||||
}
|
||||
|
||||
return rec.statusCode == 0
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if err := next.ServeHTTP(rec, r); err != nil {
|
||||
return err
|
||||
}
|
||||
if !rec.Buffered() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// set up the replacer so that parts of the original response can be
|
||||
// used for routing decisions
|
||||
for field, value := range r.Header {
|
||||
repl.Set("http.intercept.header."+field, strings.Join(value, ","))
|
||||
}
|
||||
repl.Set("http.intercept.status_code", rec.Status())
|
||||
|
||||
ir.logger.Debug("handling response", zap.Int("handler", rec.handlerIndex))
|
||||
|
||||
// pass the request through the response handler routes
|
||||
return rec.handler.Routes.Compile(next).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// intercept [<matcher>] {
|
||||
// # intercept original responses
|
||||
// @name {
|
||||
// status <code...>
|
||||
// header <field> [<value>]
|
||||
// }
|
||||
// replace_status [<matcher>] <status_code>
|
||||
// handle_response [<matcher>] {
|
||||
// <directives...>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The FinalizeUnmarshalCaddyfile method should be called after this
|
||||
// to finalize parsing of "handle_response" blocks, if possible.
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
func (i *Intercept) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
// collect the response matchers defined as subdirectives
|
||||
// prefixed with "@" for use with "handle_response" blocks
|
||||
i.responseMatchers = make(map[string]caddyhttp.ResponseMatcher)
|
||||
|
||||
d.Next() // consume the directive name
|
||||
for d.NextBlock(0) {
|
||||
// if the subdirective has an "@" prefix then we
|
||||
// parse it as a response matcher for use with "handle_response"
|
||||
if strings.HasPrefix(d.Val(), matcherPrefix) {
|
||||
err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), i.responseMatchers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch d.Val() {
|
||||
case "handle_response":
|
||||
// delegate the parsing of handle_response to the caller,
|
||||
// since we need the httpcaddyfile.Helper to parse subroutes.
|
||||
// See h.FinalizeUnmarshalCaddyfile
|
||||
i.handleResponseSegments = append(i.handleResponseSegments, d.NewFromNextSegment())
|
||||
|
||||
case "replace_status":
|
||||
args := d.RemainingArgs()
|
||||
if len(args) != 1 && len(args) != 2 {
|
||||
return d.Errf("must have one or two arguments: an optional response matcher, and a status code")
|
||||
}
|
||||
|
||||
responseHandler := caddyhttp.ResponseHandler{}
|
||||
|
||||
if len(args) == 2 {
|
||||
if !strings.HasPrefix(args[0], matcherPrefix) {
|
||||
return d.Errf("must use a named response matcher, starting with '@'")
|
||||
}
|
||||
foundMatcher, ok := i.responseMatchers[args[0]]
|
||||
if !ok {
|
||||
return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
|
||||
}
|
||||
responseHandler.Match = &foundMatcher
|
||||
responseHandler.StatusCode = caddyhttp.WeakString(args[1])
|
||||
} else if len(args) == 1 {
|
||||
responseHandler.StatusCode = caddyhttp.WeakString(args[0])
|
||||
}
|
||||
|
||||
// make sure there's no block, cause it doesn't make sense
|
||||
if nesting := d.Nesting(); d.NextBlock(nesting) {
|
||||
return d.Errf("cannot define routes for 'replace_status', use 'handle_response' instead.")
|
||||
}
|
||||
|
||||
i.HandleResponse = append(
|
||||
i.HandleResponse,
|
||||
responseHandler,
|
||||
)
|
||||
|
||||
default:
|
||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FinalizeUnmarshalCaddyfile finalizes the Caddyfile parsing which
|
||||
// requires having an httpcaddyfile.Helper to function, to parse subroutes.
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
func (i *Intercept) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error {
|
||||
for _, d := range i.handleResponseSegments {
|
||||
// consume the "handle_response" token
|
||||
d.Next()
|
||||
args := d.RemainingArgs()
|
||||
|
||||
// TODO: Remove this check at some point in the future
|
||||
if len(args) == 2 {
|
||||
return d.Errf("configuring 'handle_response' for status code replacement is no longer supported. Use 'replace_status' instead.")
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
return d.Errf("too many arguments for 'handle_response': %s", args)
|
||||
}
|
||||
|
||||
var matcher *caddyhttp.ResponseMatcher
|
||||
if len(args) == 1 {
|
||||
// the first arg should always be a matcher.
|
||||
if !strings.HasPrefix(args[0], matcherPrefix) {
|
||||
return d.Errf("must use a named response matcher, starting with '@'")
|
||||
}
|
||||
|
||||
foundMatcher, ok := i.responseMatchers[args[0]]
|
||||
if !ok {
|
||||
return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
|
||||
}
|
||||
matcher = &foundMatcher
|
||||
}
|
||||
|
||||
// parse the block as routes
|
||||
handler, err := httpcaddyfile.ParseSegmentAsSubroute(helper.WithDispenser(d.NewFromNextSegment()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subroute, ok := handler.(*caddyhttp.Subroute)
|
||||
if !ok {
|
||||
return helper.Errf("segment was not parsed as a subroute")
|
||||
}
|
||||
i.HandleResponse = append(
|
||||
i.HandleResponse,
|
||||
caddyhttp.ResponseHandler{
|
||||
Match: matcher,
|
||||
Routes: subroute.Routes,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// move the handle_response entries without a matcher to the end.
|
||||
// we can't use sort.SliceStable because it will reorder the rest of the
|
||||
// entries which may be undesirable because we don't have a good
|
||||
// heuristic to use for sorting.
|
||||
withoutMatchers := []caddyhttp.ResponseHandler{}
|
||||
withMatchers := []caddyhttp.ResponseHandler{}
|
||||
for _, hr := range i.HandleResponse {
|
||||
if hr.Match == nil {
|
||||
withoutMatchers = append(withoutMatchers, hr)
|
||||
} else {
|
||||
withMatchers = append(withMatchers, hr)
|
||||
}
|
||||
}
|
||||
i.HandleResponse = append(withMatchers, withoutMatchers...)
|
||||
|
||||
// clean up the bits we only needed for adapting
|
||||
i.handleResponseSegments = nil
|
||||
i.responseMatchers = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const matcherPrefix = "@"
|
||||
|
||||
func parseCaddyfile(helper httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
var ir Intercept
|
||||
if err := ir.UnmarshalCaddyfile(helper.Dispenser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := ir.FinalizeUnmarshalCaddyfile(helper); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ir, nil
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*Intercept)(nil)
|
||||
_ caddyfile.Unmarshaler = (*Intercept)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*Intercept)(nil)
|
||||
)
|
||||
@@ -72,19 +72,21 @@ func (MatchRemoteIP) CaddyModule() caddy.ModuleInfo {
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume matcher name
|
||||
for d.NextArg() {
|
||||
if d.Val() == "forwarded" {
|
||||
return d.Err("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
|
||||
// iterate to merge multiple matchers into one
|
||||
for d.Next() {
|
||||
for d.NextArg() {
|
||||
if d.Val() == "forwarded" {
|
||||
return d.Err("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
|
||||
}
|
||||
if d.Val() == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
||||
continue
|
||||
}
|
||||
m.Ranges = append(m.Ranges, d.Val())
|
||||
}
|
||||
if d.Val() == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
||||
continue
|
||||
if d.NextBlock(0) {
|
||||
return d.Err("malformed remote_ip matcher: blocks are not supported")
|
||||
}
|
||||
m.Ranges = append(m.Ranges, d.Val())
|
||||
}
|
||||
if d.NextBlock(0) {
|
||||
return d.Err("malformed remote_ip matcher: blocks are not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -164,16 +166,18 @@ func (MatchClientIP) CaddyModule() caddy.ModuleInfo {
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (m *MatchClientIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume matcher name
|
||||
for d.NextArg() {
|
||||
if d.Val() == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
||||
continue
|
||||
// iterate to merge multiple matchers into one
|
||||
for d.Next() {
|
||||
for d.NextArg() {
|
||||
if d.Val() == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
||||
continue
|
||||
}
|
||||
m.Ranges = append(m.Ranges, d.Val())
|
||||
}
|
||||
if d.NextBlock(0) {
|
||||
return d.Err("malformed client_ip matcher: blocks are not supported")
|
||||
}
|
||||
m.Ranges = append(m.Ranges, d.Val())
|
||||
}
|
||||
if d.NextBlock(0) {
|
||||
return d.Err("malformed client_ip matcher: blocks are not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -274,7 +278,7 @@ func parseIPZoneFromString(address string) (netip.Addr, string, error) {
|
||||
ipStr = address // OK; probably didn't have a port
|
||||
}
|
||||
|
||||
// Some IPv6-Adresses can contain zone identifiers at the end,
|
||||
// Some IPv6-Addresses can contain zone identifiers at the end,
|
||||
// which are separated with "%"
|
||||
zoneID := ""
|
||||
if strings.Contains(ipStr, "%") {
|
||||
|
||||
+111
-35
@@ -15,6 +15,7 @@
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -32,14 +33,23 @@ import (
|
||||
// customized per-request-host.
|
||||
type ServerLogConfig struct {
|
||||
// The default logger name for all logs emitted by this server for
|
||||
// hostnames that are not in the LoggerNames (logger_names) map.
|
||||
// hostnames that are not in the logger_names map.
|
||||
DefaultLoggerName string `json:"default_logger_name,omitempty"`
|
||||
|
||||
// LoggerNames maps request hostnames to a custom logger name.
|
||||
// For example, a mapping of "example.com" to "example" would
|
||||
// cause access logs from requests with a Host of example.com
|
||||
// to be emitted by a logger named "http.log.access.example".
|
||||
LoggerNames map[string]string `json:"logger_names,omitempty"`
|
||||
// LoggerNames maps request hostnames to one or more custom logger
|
||||
// names. For example, a mapping of `"example.com": ["example"]` would
|
||||
// cause access logs from requests with a Host of example.com to be
|
||||
// emitted by a logger named "http.log.access.example". If there are
|
||||
// multiple logger names, then the log will be emitted to all of them.
|
||||
// If the logger name is an empty, the default logger is used, i.e.
|
||||
// the logger "http.log.access".
|
||||
//
|
||||
// Keys must be hostnames (without ports), and may contain wildcards
|
||||
// to match subdomains. The value is an array of logger names.
|
||||
//
|
||||
// For backwards compatibility, if the value is a string, it is treated
|
||||
// as a single-element array.
|
||||
LoggerNames map[string]StringArray `json:"logger_names,omitempty"`
|
||||
|
||||
// By default, all requests to this server will be logged if
|
||||
// access logging is enabled. This field lists the request
|
||||
@@ -47,7 +57,7 @@ type ServerLogConfig struct {
|
||||
SkipHosts []string `json:"skip_hosts,omitempty"`
|
||||
|
||||
// If true, requests to any host not appearing in the
|
||||
// LoggerNames (logger_names) map will not be logged.
|
||||
// logger_names map will not be logged.
|
||||
SkipUnmappedHosts bool `json:"skip_unmapped_hosts,omitempty"`
|
||||
|
||||
// If true, credentials that are otherwise omitted, will be logged.
|
||||
@@ -55,35 +65,67 @@ type ServerLogConfig struct {
|
||||
// and this includes some request and response headers, i.e `Cookie`,
|
||||
// `Set-Cookie`, `Authorization`, and `Proxy-Authorization`.
|
||||
ShouldLogCredentials bool `json:"should_log_credentials,omitempty"`
|
||||
|
||||
// Log each individual handler that is invoked.
|
||||
// Requires that the log emit at DEBUG level.
|
||||
//
|
||||
// NOTE: This may log the configuration of your
|
||||
// HTTP handler modules; do not enable this in
|
||||
// insecure contexts when there is sensitive
|
||||
// data in the configuration.
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
Trace bool `json:"trace,omitempty"`
|
||||
}
|
||||
|
||||
// wrapLogger wraps logger in a logger named according to user preferences for the given host.
|
||||
func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) *zap.Logger {
|
||||
if loggerName := slc.getLoggerName(host); loggerName != "" {
|
||||
return logger.Named(loggerName)
|
||||
// wrapLogger wraps logger in one or more logger named
|
||||
// according to user preferences for the given host.
|
||||
func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, req *http.Request) []*zap.Logger {
|
||||
// using the `log_name` directive or the `access_logger_names` variable,
|
||||
// the logger names can be overridden for the current request
|
||||
if names := GetVar(req.Context(), AccessLoggerNameVarKey); names != nil {
|
||||
if namesSlice, ok := names.([]any); ok {
|
||||
loggers := make([]*zap.Logger, 0, len(namesSlice))
|
||||
for _, loggerName := range namesSlice {
|
||||
// no name, use the default logger
|
||||
if loggerName == "" {
|
||||
loggers = append(loggers, logger)
|
||||
continue
|
||||
}
|
||||
// make a logger with the given name
|
||||
loggers = append(loggers, logger.Named(loggerName.(string)))
|
||||
}
|
||||
return loggers
|
||||
}
|
||||
}
|
||||
return logger
|
||||
|
||||
// get the hostname from the request, with the port number stripped
|
||||
host, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
host = req.Host
|
||||
}
|
||||
|
||||
// get the logger names for this host from the config
|
||||
hosts := slc.getLoggerHosts(host)
|
||||
|
||||
// make a list of named loggers, or the default logger
|
||||
loggers := make([]*zap.Logger, 0, len(hosts))
|
||||
for _, loggerName := range hosts {
|
||||
// no name, use the default logger
|
||||
if loggerName == "" {
|
||||
loggers = append(loggers, logger)
|
||||
continue
|
||||
}
|
||||
// make a logger with the given name
|
||||
loggers = append(loggers, logger.Named(loggerName))
|
||||
}
|
||||
return loggers
|
||||
}
|
||||
|
||||
func (slc ServerLogConfig) getLoggerName(host string) string {
|
||||
tryHost := func(key string) (string, bool) {
|
||||
// first try exact match
|
||||
if loggerName, ok := slc.LoggerNames[key]; ok {
|
||||
return loggerName, ok
|
||||
}
|
||||
// strip port and try again (i.e. Host header of "example.com:1234" should
|
||||
// match "example.com" if there is no "example.com:1234" in the map)
|
||||
hostOnly, _, err := net.SplitHostPort(key)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
loggerName, ok := slc.LoggerNames[hostOnly]
|
||||
return loggerName, ok
|
||||
}
|
||||
|
||||
func (slc ServerLogConfig) getLoggerHosts(host string) []string {
|
||||
// try the exact hostname first
|
||||
if loggerName, ok := tryHost(host); ok {
|
||||
return loggerName
|
||||
if hosts, ok := slc.LoggerNames[host]; ok {
|
||||
return hosts
|
||||
}
|
||||
|
||||
// try matching wildcard domains if other non-specific loggers exist
|
||||
@@ -94,28 +136,59 @@ func (slc ServerLogConfig) getLoggerName(host string) string {
|
||||
}
|
||||
labels[i] = "*"
|
||||
wildcardHost := strings.Join(labels, ".")
|
||||
if loggerName, ok := tryHost(wildcardHost); ok {
|
||||
return loggerName
|
||||
if hosts, ok := slc.LoggerNames[wildcardHost]; ok {
|
||||
return hosts
|
||||
}
|
||||
}
|
||||
|
||||
return slc.DefaultLoggerName
|
||||
return []string{slc.DefaultLoggerName}
|
||||
}
|
||||
|
||||
func (slc *ServerLogConfig) clone() *ServerLogConfig {
|
||||
clone := &ServerLogConfig{
|
||||
DefaultLoggerName: slc.DefaultLoggerName,
|
||||
LoggerNames: make(map[string]string),
|
||||
LoggerNames: make(map[string]StringArray),
|
||||
SkipHosts: append([]string{}, slc.SkipHosts...),
|
||||
SkipUnmappedHosts: slc.SkipUnmappedHosts,
|
||||
ShouldLogCredentials: slc.ShouldLogCredentials,
|
||||
}
|
||||
for k, v := range slc.LoggerNames {
|
||||
clone.LoggerNames[k] = v
|
||||
clone.LoggerNames[k] = append([]string{}, v...)
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
// StringArray is a slices of strings, but also accepts
|
||||
// a single string as a value when JSON unmarshaling,
|
||||
// converting it to a slice of one string.
|
||||
type StringArray []string
|
||||
|
||||
// UnmarshalJSON satisfies json.Unmarshaler.
|
||||
func (sa *StringArray) UnmarshalJSON(b []byte) error {
|
||||
var jsonObj any
|
||||
err := json.Unmarshal(b, &jsonObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch obj := jsonObj.(type) {
|
||||
case string:
|
||||
*sa = StringArray([]string{obj})
|
||||
return nil
|
||||
case []any:
|
||||
s := make([]string, 0, len(obj))
|
||||
for _, v := range obj {
|
||||
value, ok := v.(string)
|
||||
if !ok {
|
||||
return errors.New("unsupported type")
|
||||
}
|
||||
s = append(s, value)
|
||||
}
|
||||
*sa = StringArray(s)
|
||||
return nil
|
||||
}
|
||||
return errors.New("unsupported type")
|
||||
}
|
||||
|
||||
// errLogValues inspects err and returns the status code
|
||||
// to use, the error log message, and any extra fields.
|
||||
// If err is a HandlerError, the returned values will
|
||||
@@ -170,4 +243,7 @@ const (
|
||||
|
||||
// For adding additional fields to the access logs
|
||||
ExtraLogFieldsCtxKey caddy.CtxKey = "extra_log_fields"
|
||||
|
||||
// Variable name used to indicate the logger to be used
|
||||
AccessLoggerNameVarKey string = "access_logger_names"
|
||||
)
|
||||
|
||||
@@ -675,7 +675,10 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
[]*cel.Type{cel.StringType},
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
pattern := data.(types.String)
|
||||
matcher := MatchPathRE{MatchRegexp{Pattern: string(pattern)}}
|
||||
matcher := MatchPathRE{MatchRegexp{
|
||||
Name: ctx.Value(MatcherNameCtxKey).(string),
|
||||
Pattern: string(pattern),
|
||||
}}
|
||||
err := matcher.Provision(ctx)
|
||||
return matcher, err
|
||||
},
|
||||
@@ -694,7 +697,14 @@ func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
return nil, err
|
||||
}
|
||||
strParams := params.([]string)
|
||||
matcher := MatchPathRE{MatchRegexp{Name: strParams[0], Pattern: strParams[1]}}
|
||||
name := strParams[0]
|
||||
if name == "" {
|
||||
name = ctx.Value(MatcherNameCtxKey).(string)
|
||||
}
|
||||
matcher := MatchPathRE{MatchRegexp{
|
||||
Name: name,
|
||||
Pattern: strParams[1],
|
||||
}}
|
||||
err = matcher.Provision(ctx)
|
||||
return matcher, err
|
||||
},
|
||||
@@ -1023,6 +1033,11 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
val = second
|
||||
}
|
||||
|
||||
// Default to the named matcher's name, if no regexp name is provided
|
||||
if name == "" {
|
||||
name = d.GetContextString(caddyfile.MatcherNameCtxKey)
|
||||
}
|
||||
|
||||
// If there's already a pattern for this field
|
||||
// then we would end up overwriting the old one
|
||||
if (*m)[field] != nil {
|
||||
@@ -1099,7 +1114,10 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
}
|
||||
strParams := params.([]string)
|
||||
matcher := MatchHeaderRE{}
|
||||
matcher[strParams[0]] = &MatchRegexp{Pattern: strParams[1], Name: ""}
|
||||
matcher[strParams[0]] = &MatchRegexp{
|
||||
Pattern: strParams[1],
|
||||
Name: ctx.Value(MatcherNameCtxKey).(string),
|
||||
}
|
||||
err = matcher.Provision(ctx)
|
||||
return matcher, err
|
||||
},
|
||||
@@ -1118,8 +1136,15 @@ func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
return nil, err
|
||||
}
|
||||
strParams := params.([]string)
|
||||
name := strParams[0]
|
||||
if name == "" {
|
||||
name = ctx.Value(MatcherNameCtxKey).(string)
|
||||
}
|
||||
matcher := MatchHeaderRE{}
|
||||
matcher[strParams[1]] = &MatchRegexp{Pattern: strParams[2], Name: strParams[0]}
|
||||
matcher[strParams[1]] = &MatchRegexp{
|
||||
Pattern: strParams[2],
|
||||
Name: name,
|
||||
}
|
||||
err = matcher.Provision(ctx)
|
||||
return matcher, err
|
||||
},
|
||||
@@ -1284,7 +1309,6 @@ type MatchRegexp struct {
|
||||
Pattern string `json:"pattern"`
|
||||
|
||||
compiled *regexp.Regexp
|
||||
phPrefix string
|
||||
}
|
||||
|
||||
// Provision compiles the regular expression.
|
||||
@@ -1294,10 +1318,6 @@ func (mre *MatchRegexp) Provision(caddy.Context) error {
|
||||
return fmt.Errorf("compiling matcher regexp %s: %v", mre.Pattern, err)
|
||||
}
|
||||
mre.compiled = re
|
||||
mre.phPrefix = regexpPlaceholderPrefix
|
||||
if mre.Name != "" {
|
||||
mre.phPrefix += "." + mre.Name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1321,16 +1341,25 @@ func (mre *MatchRegexp) Match(input string, repl *caddy.Replacer) bool {
|
||||
|
||||
// save all capture groups, first by index
|
||||
for i, match := range matches {
|
||||
key := mre.phPrefix + "." + strconv.Itoa(i)
|
||||
repl.Set(key, match)
|
||||
keySuffix := "." + strconv.Itoa(i)
|
||||
if mre.Name != "" {
|
||||
repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, match)
|
||||
}
|
||||
repl.Set(regexpPlaceholderPrefix+keySuffix, match)
|
||||
}
|
||||
|
||||
// then by name
|
||||
for i, name := range mre.compiled.SubexpNames() {
|
||||
if i != 0 && name != "" {
|
||||
key := mre.phPrefix + "." + name
|
||||
repl.Set(key, matches[i])
|
||||
// skip the first element (the full match), and empty names
|
||||
if i == 0 || name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
keySuffix := "." + name
|
||||
if mre.Name != "" {
|
||||
repl.Set(regexpPlaceholderPrefix+"."+mre.Name+keySuffix, matches[i])
|
||||
}
|
||||
repl.Set(regexpPlaceholderPrefix+keySuffix, matches[i])
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -1357,6 +1386,12 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
default:
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
// Default to the named matcher's name, if no regexp name is provided
|
||||
if mre.Name == "" {
|
||||
mre.Name = d.GetContextString(caddyfile.MatcherNameCtxKey)
|
||||
}
|
||||
|
||||
if d.NextBlock(0) {
|
||||
return d.Err("malformed path_regexp matcher: blocks are not supported")
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package requestbody
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
@@ -44,8 +46,30 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
}
|
||||
rb.MaxSize = int64(size)
|
||||
|
||||
case "read_timeout":
|
||||
var timeoutStr string
|
||||
if !h.AllArgs(&timeoutStr) {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
timeout, err := time.ParseDuration(timeoutStr)
|
||||
if err != nil {
|
||||
return nil, h.Errf("parsing read_timeout: %v", err)
|
||||
}
|
||||
rb.ReadTimeout = timeout
|
||||
|
||||
case "write_timeout":
|
||||
var timeoutStr string
|
||||
if !h.AllArgs(&timeoutStr) {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
timeout, err := time.ParseDuration(timeoutStr)
|
||||
if err != nil {
|
||||
return nil, h.Errf("parsing write_timeout: %v", err)
|
||||
}
|
||||
rb.WriteTimeout = timeout
|
||||
|
||||
default:
|
||||
return nil, h.Errf("unrecognized servers option '%s'", h.Val())
|
||||
return nil, h.Errf("unrecognized request_body subdirective '%s'", h.Val())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ package requestbody
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
@@ -31,6 +34,14 @@ type RequestBody struct {
|
||||
// The maximum number of bytes to allow reading from the body by a later handler.
|
||||
// If more bytes are read, an error with HTTP status 413 is returned.
|
||||
MaxSize int64 `json:"max_size,omitempty"`
|
||||
|
||||
// EXPERIMENTAL. Subject to change/removal.
|
||||
ReadTimeout time.Duration `json:"read_timeout,omitempty"`
|
||||
|
||||
// EXPERIMENTAL. Subject to change/removal.
|
||||
WriteTimeout time.Duration `json:"write_timeout,omitempty"`
|
||||
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -41,6 +52,11 @@ func (RequestBody) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
func (rb *RequestBody) Provision(ctx caddy.Context) error {
|
||||
rb.logger = ctx.Logger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||
if r.Body == nil {
|
||||
return next.ServeHTTP(w, r)
|
||||
@@ -48,6 +64,20 @@ func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next cad
|
||||
if rb.MaxSize > 0 {
|
||||
r.Body = errorWrapper{http.MaxBytesReader(w, r.Body, rb.MaxSize)}
|
||||
}
|
||||
if rb.ReadTimeout > 0 || rb.WriteTimeout > 0 {
|
||||
//nolint:bodyclose
|
||||
rc := http.NewResponseController(w)
|
||||
if rb.ReadTimeout > 0 {
|
||||
if err := rc.SetReadDeadline(time.Now().Add(rb.ReadTimeout)); err != nil {
|
||||
rb.logger.Error("could not set read deadline", zap.Error(err))
|
||||
}
|
||||
}
|
||||
if rb.WriteTimeout > 0 {
|
||||
if err := rc.SetWriteDeadline(time.Now().Add(rb.WriteTimeout)); err != nil {
|
||||
rb.logger.Error("could not set write deadline", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ type responseRecorder struct {
|
||||
size int
|
||||
wroteHeader bool
|
||||
stream bool
|
||||
|
||||
readSize *int
|
||||
}
|
||||
|
||||
// NewResponseRecorder returns a new ResponseRecorder that can be
|
||||
@@ -240,6 +242,12 @@ func (rr *responseRecorder) FlushError() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Private interface so it can only be used in this package
|
||||
// #TODO: maybe export it later
|
||||
func (rr *responseRecorder) setReadSize(size *int) {
|
||||
rr.readSize = size
|
||||
}
|
||||
|
||||
func (rr *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
//nolint:bodyclose
|
||||
conn, brw, err := http.NewResponseController(rr.ResponseWriterWrapper).Hijack()
|
||||
@@ -249,6 +257,17 @@ func (rr *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
// Per http documentation, returned bufio.Writer is empty, but bufio.Read maybe not
|
||||
conn = &hijackedConn{conn, rr}
|
||||
brw.Writer.Reset(conn)
|
||||
|
||||
buffered := brw.Reader.Buffered()
|
||||
if buffered != 0 {
|
||||
conn.(*hijackedConn).updateReadSize(buffered)
|
||||
data, _ := brw.Peek(buffered)
|
||||
brw.Reader.Reset(io.MultiReader(bytes.NewReader(data), conn))
|
||||
// peek to make buffered data appear, as Reset will make it 0
|
||||
_, _ = brw.Peek(buffered)
|
||||
} else {
|
||||
brw.Reader.Reset(conn)
|
||||
}
|
||||
return conn, brw, nil
|
||||
}
|
||||
|
||||
@@ -258,6 +277,24 @@ type hijackedConn struct {
|
||||
rr *responseRecorder
|
||||
}
|
||||
|
||||
func (hc *hijackedConn) updateReadSize(n int) {
|
||||
if hc.rr.readSize != nil {
|
||||
*hc.rr.readSize += n
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *hijackedConn) Read(p []byte) (int, error) {
|
||||
n, err := hc.Conn.Read(p)
|
||||
hc.updateReadSize(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (hc *hijackedConn) WriteTo(w io.Writer) (int64, error) {
|
||||
n, err := io.Copy(w, hc.Conn)
|
||||
hc.updateReadSize(int(n))
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (hc *hijackedConn) Write(p []byte) (int, error) {
|
||||
n, err := hc.Conn.Write(p)
|
||||
hc.rr.size += n
|
||||
@@ -298,4 +335,6 @@ var (
|
||||
_ io.ReaderFrom = (*ResponseWriterWrapper)(nil)
|
||||
_ io.ReaderFrom = (*responseRecorder)(nil)
|
||||
_ io.ReaderFrom = (*hijackedConn)(nil)
|
||||
|
||||
_ io.WriterTo = (*hijackedConn)(nil)
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@ package caddyhttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -75,20 +74,19 @@ func TestResponseWriterWrapperReadFrom(t *testing.T) {
|
||||
// take precedence over our ReadFrom.
|
||||
src := struct{ io.Reader }{strings.NewReader(srcData)}
|
||||
|
||||
fmt.Println(name)
|
||||
if _, err := io.Copy(wrapped, src); err != nil {
|
||||
t.Errorf("Copy() err = %v", err)
|
||||
t.Errorf("%s: Copy() err = %v", name, err)
|
||||
}
|
||||
|
||||
if got := tt.responseWriter.Written(); got != srcData {
|
||||
t.Errorf("data = %q, want %q", got, srcData)
|
||||
t.Errorf("%s: data = %q, want %q", name, got, srcData)
|
||||
}
|
||||
|
||||
if tt.responseWriter.CalledReadFrom() != tt.wantReadFrom {
|
||||
if tt.wantReadFrom {
|
||||
t.Errorf("ReadFrom() should have been called")
|
||||
t.Errorf("%s: ReadFrom() should have been called", name)
|
||||
} else {
|
||||
t.Errorf("ReadFrom() should not have been called")
|
||||
t.Errorf("%s: ReadFrom() should not have been called", name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -74,6 +75,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
// health_timeout <duration>
|
||||
// health_status <status>
|
||||
// health_body <regexp>
|
||||
// health_follow_redirects
|
||||
// health_headers {
|
||||
// <field> [<values...>]
|
||||
// }
|
||||
@@ -449,6 +451,18 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
}
|
||||
h.HealthChecks.Active.ExpectBody = d.Val()
|
||||
|
||||
case "health_follow_redirects":
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if h.HealthChecks == nil {
|
||||
h.HealthChecks = new(HealthChecks)
|
||||
}
|
||||
if h.HealthChecks.Active == nil {
|
||||
h.HealthChecks.Active = new(ActiveHealthChecks)
|
||||
}
|
||||
h.HealthChecks.Active.FollowRedirects = true
|
||||
|
||||
case "health_passes":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
@@ -606,33 +620,6 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
h.ResponseBuffers = size
|
||||
}
|
||||
|
||||
// TODO: These three properties are deprecated; remove them sometime after v2.6.4
|
||||
case "buffer_requests": // TODO: deprecated
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: buffer_requests: use request_buffers instead (with a maximum buffer size)")
|
||||
h.DeprecatedBufferRequests = true
|
||||
case "buffer_responses": // TODO: deprecated
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: buffer_responses: use response_buffers instead (with a maximum buffer size)")
|
||||
h.DeprecatedBufferResponses = true
|
||||
case "max_buffer_size": // TODO: deprecated
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
size, err := humanize.ParseBytes(d.Val())
|
||||
if err != nil {
|
||||
return d.Errf("invalid byte size '%s': %v", d.Val(), err)
|
||||
}
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
caddy.Log().Named("config.adapter.caddyfile").Warn("DEPRECATED: max_buffer_size: use request_buffers and/or response_buffers instead (with maximum buffer sizes)")
|
||||
h.DeprecatedMaxBufferSize = int64(size)
|
||||
|
||||
case "stream_timeout":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
@@ -1138,6 +1125,7 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
h.TLS.HandshakeTimeout = caddy.Duration(dur)
|
||||
|
||||
case "tls_trusted_ca_certs":
|
||||
caddy.Log().Warn("The 'tls_trusted_ca_certs' field is deprecated. Use the 'tls_trust_pool' field instead.")
|
||||
args := d.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
return d.ArgErr()
|
||||
@@ -1145,6 +1133,9 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
if h.TLS == nil {
|
||||
h.TLS = new(TLSConfig)
|
||||
}
|
||||
if len(h.TLS.CARaw) != 0 {
|
||||
return d.Err("cannot specify both 'tls_trust_pool' and 'tls_trusted_ca_certs")
|
||||
}
|
||||
h.TLS.RootCAPEMFiles = args
|
||||
|
||||
case "tls_server_name":
|
||||
@@ -1260,6 +1251,31 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
}
|
||||
h.MaxConnsPerHost = num
|
||||
|
||||
case "tls_trust_pool":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
modStem := d.Val()
|
||||
modID := "tls.ca_pool.source." + modStem
|
||||
unm, err := caddyfile.UnmarshalModule(d, modID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ca, ok := unm.(caddytls.CA)
|
||||
if !ok {
|
||||
return d.Errf("module %s is not a caddytls.CA", modID)
|
||||
}
|
||||
if h.TLS == nil {
|
||||
h.TLS = new(TLSConfig)
|
||||
}
|
||||
if len(h.TLS.RootCAPEMFiles) != 0 {
|
||||
return d.Err("cannot specify both 'tls_trust_pool' and 'tls_trusted_ca_certs'")
|
||||
}
|
||||
if h.TLS.CARaw != nil {
|
||||
return d.Err("cannot specify \"tls_trust_pool\" twice in caddyfile")
|
||||
}
|
||||
h.TLS.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil)
|
||||
|
||||
default:
|
||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||
}
|
||||
@@ -1354,6 +1370,7 @@ func (h *CopyResponseHeadersHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser)
|
||||
// resolvers <resolvers...>
|
||||
// dial_timeout <timeout>
|
||||
// dial_fallback_delay <timeout>
|
||||
// grace_period <duration>
|
||||
// }
|
||||
func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume upstream source name
|
||||
@@ -1433,7 +1450,15 @@ func (u *SRVUpstreams) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return d.Errf("bad delay value '%s': %v", d.Val(), err)
|
||||
}
|
||||
u.FallbackDelay = caddy.Duration(dur)
|
||||
|
||||
case "grace_period":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
dur, err := caddy.ParseDuration(d.Val())
|
||||
if err != nil {
|
||||
return d.Errf("bad grace period value '%s': %v", d.Val(), err)
|
||||
}
|
||||
u.GracePeriod = caddy.Duration(dur)
|
||||
default:
|
||||
return d.Errf("unrecognized srv option '%s'", d.Val())
|
||||
}
|
||||
|
||||
@@ -213,8 +213,6 @@ func DisabledTest(t *testing.T) {
|
||||
// TODO: test chunked reader
|
||||
globalt = t
|
||||
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
// server
|
||||
go func() {
|
||||
listener, err := net.Listen("tcp", ipPort)
|
||||
|
||||
@@ -82,6 +82,9 @@ type ActiveHealthChecks struct {
|
||||
// HTTP headers to set on health check requests.
|
||||
Headers http.Header `json:"headers,omitempty"`
|
||||
|
||||
// Whether to follow HTTP redirects in response to active health checks (default off).
|
||||
FollowRedirects bool `json:"follow_redirects,omitempty"`
|
||||
|
||||
// How frequently to perform active health checks (default 30s).
|
||||
Interval caddy.Duration `json:"interval,omitempty"`
|
||||
|
||||
@@ -153,6 +156,12 @@ func (a *ActiveHealthChecks) Provision(ctx caddy.Context, h *Handler) error {
|
||||
a.httpClient = &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: h.Transport,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
if !a.FollowRedirects {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
for _, upstream := range h.Upstreams {
|
||||
@@ -453,7 +462,7 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
||||
markUnhealthy()
|
||||
return nil
|
||||
}
|
||||
} else if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
h.HealthChecks.Active.logger.Info("status code out of tolerances",
|
||||
zap.Int("status_code", resp.StatusCode),
|
||||
zap.String("host", hostAddr),
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
@@ -123,12 +125,18 @@ type HTTPTransport struct {
|
||||
// can be specified to use H2C (HTTP/2 over Cleartext) to the
|
||||
// upstream (this feature is experimental and subject to
|
||||
// change or removal). Default: ["1.1", "2"]
|
||||
//
|
||||
// EXPERIMENTAL: "3" enables HTTP/3, but it must be the only
|
||||
// version specified if enabled. Additionally, HTTPS must be
|
||||
// enabled to the upstream as HTTP/3 requires TLS. Subject
|
||||
// to change or removal while experimental.
|
||||
Versions []string `json:"versions,omitempty"`
|
||||
|
||||
// The pre-configured underlying HTTP transport.
|
||||
Transport *http.Transport `json:"-"`
|
||||
|
||||
h2cTransport *http2.Transport
|
||||
h3Transport *http3.RoundTripper // TODO: EXPERIMENTAL (May 2024)
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -224,41 +232,47 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get proxy protocol info from context")
|
||||
}
|
||||
header := proxyproto.Header{
|
||||
SourceAddr: &net.TCPAddr{
|
||||
IP: proxyProtocolInfo.AddrPort.Addr().AsSlice(),
|
||||
Port: int(proxyProtocolInfo.AddrPort.Port()),
|
||||
Zone: proxyProtocolInfo.AddrPort.Addr().Zone(),
|
||||
},
|
||||
var proxyv byte
|
||||
switch h.ProxyProtocol {
|
||||
case "v1":
|
||||
proxyv = 1
|
||||
case "v2":
|
||||
proxyv = 2
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected proxy protocol version")
|
||||
}
|
||||
|
||||
// The src and dst have to be of the same address family. As we don't know the original
|
||||
// dst address (it's kind of impossible to know) and this address is generally of very
|
||||
// little interest, we just set it to all zeros.
|
||||
var destAddr net.Addr
|
||||
switch {
|
||||
case proxyProtocolInfo.AddrPort.Addr().Is4():
|
||||
header.TransportProtocol = proxyproto.TCPv4
|
||||
header.DestinationAddr = &net.TCPAddr{
|
||||
destAddr = &net.TCPAddr{
|
||||
IP: net.IPv4zero,
|
||||
}
|
||||
case proxyProtocolInfo.AddrPort.Addr().Is6():
|
||||
header.TransportProtocol = proxyproto.TCPv6
|
||||
header.DestinationAddr = &net.TCPAddr{
|
||||
destAddr = &net.TCPAddr{
|
||||
IP: net.IPv6zero,
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected remote addr type in proxy protocol info")
|
||||
}
|
||||
sourceAddr := &net.TCPAddr{
|
||||
IP: proxyProtocolInfo.AddrPort.Addr().AsSlice(),
|
||||
Port: int(proxyProtocolInfo.AddrPort.Port()),
|
||||
Zone: proxyProtocolInfo.AddrPort.Addr().Zone(),
|
||||
}
|
||||
header := proxyproto.HeaderProxyFromAddrs(proxyv, sourceAddr, destAddr)
|
||||
|
||||
// retain the log message structure
|
||||
switch h.ProxyProtocol {
|
||||
case "v1":
|
||||
header.Version = 1
|
||||
caddyCtx.Logger().Debug("sending proxy protocol header v1", zap.Any("header", header))
|
||||
case "v2":
|
||||
header.Version = 2
|
||||
caddyCtx.Logger().Debug("sending proxy protocol header v2", zap.Any("header", header))
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected proxy protocol version")
|
||||
}
|
||||
|
||||
_, err = header.WriteTo(conn)
|
||||
if err != nil {
|
||||
// identify this error as one that occurred during
|
||||
@@ -343,6 +357,16 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
||||
}
|
||||
}
|
||||
|
||||
// configure HTTP/3 transport if enabled; however, this does not
|
||||
// automatically fall back to lower versions like most web browsers
|
||||
// do (that'd add latency and complexity, besides, we expect that
|
||||
// site owners control the backends), so it must be exclusive
|
||||
if len(h.Versions) == 1 && h.Versions[0] == "3" {
|
||||
h.h3Transport = new(http3.RoundTripper)
|
||||
} else if len(h.Versions) > 1 && sliceContains(h.Versions, "3") {
|
||||
return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported")
|
||||
}
|
||||
|
||||
// if h2c is enabled, configure its transport (std lib http.Transport
|
||||
// does not "HTTP/2 over cleartext TCP")
|
||||
if sliceContains(h.Versions, "h2c") {
|
||||
@@ -407,6 +431,11 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
|
||||
transport.SetScheme(req)
|
||||
|
||||
// use HTTP/3 if enabled (TODO: This is EXPERIMENTAL)
|
||||
if h.h3Transport != nil {
|
||||
return h.h3Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
// if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is
|
||||
// HTTP without TLS, use the alternate H2C-capable transport instead
|
||||
if req.URL.Scheme == "http" && h.h2cTransport != nil {
|
||||
@@ -472,9 +501,14 @@ func (h HTTPTransport) Cleanup() error {
|
||||
// TLSConfig holds configuration related to the TLS configuration for the
|
||||
// transport/client.
|
||||
type TLSConfig struct {
|
||||
// Certificate authority module which provides the certificate pool of trusted certificates
|
||||
CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
|
||||
|
||||
// DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.inline` module instead.
|
||||
// Optional list of base64-encoded DER-encoded CA certificates to trust.
|
||||
RootCAPool []string `json:"root_ca_pool,omitempty"`
|
||||
|
||||
// DEPRECATED: Use the `ca` field with the `tls.ca_pool.source.file` module instead.
|
||||
// List of PEM-encoded CA certificate files to add to the same trust
|
||||
// store as RootCAPool (or root_ca_pool in the JSON).
|
||||
RootCAPEMFiles []string `json:"root_ca_pem_files,omitempty"`
|
||||
@@ -529,7 +563,7 @@ type TLSConfig struct {
|
||||
|
||||
// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
|
||||
// If there is no custom TLS configuration, a nil config may be returned.
|
||||
func (t TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
func (t *TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
cfg := new(tls.Config)
|
||||
|
||||
// client auth
|
||||
@@ -576,6 +610,7 @@ func (t TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
|
||||
// trusted root CAs
|
||||
if len(t.RootCAPool) > 0 || len(t.RootCAPEMFiles) > 0 {
|
||||
ctx.Logger().Warn("root_ca_pool and root_ca_pem_files are deprecated. Use one of the tls.ca_pool.source modules instead")
|
||||
rootPool := x509.NewCertPool()
|
||||
for _, encodedCACert := range t.RootCAPool {
|
||||
caCert, err := decodeBase64DERCert(encodedCACert)
|
||||
@@ -594,6 +629,21 @@ func (t TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
cfg.RootCAs = rootPool
|
||||
}
|
||||
|
||||
if t.CARaw != nil {
|
||||
if len(t.RootCAPool) > 0 || len(t.RootCAPEMFiles) > 0 {
|
||||
return nil, fmt.Errorf("conflicting config for Root CA pool")
|
||||
}
|
||||
caRaw, err := ctx.LoadModule(t, "CARaw")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load ca module: %v", err)
|
||||
}
|
||||
ca, ok := caRaw.(caddytls.CA)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("CA module '%s' is not a certificate pool provider", ca)
|
||||
}
|
||||
cfg.RootCAs = ca.CertPool()
|
||||
}
|
||||
|
||||
// Renegotiation
|
||||
switch t.Renegotiation {
|
||||
case "never", "":
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package reverseproxy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func TestHTTPTransportUnmarshalCaddyFileWithCaPools(t *testing.T) {
|
||||
const test_der_1 = `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==`
|
||||
type args struct {
|
||||
d *caddyfile.Dispenser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expectedTLSConfig TLSConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "tls_trust_pool without a module argument returns an error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(
|
||||
`http {
|
||||
tls_trust_pool
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "providing both 'tls_trust_pool' and 'tls_trusted_ca_certs' returns an error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(fmt.Sprintf(
|
||||
`http {
|
||||
tls_trust_pool inline %s
|
||||
tls_trusted_ca_certs %s
|
||||
}`, test_der_1, test_der_1)),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "setting 'tls_trust_pool' and 'tls_trusted_ca_certs' produces an error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(fmt.Sprintf(
|
||||
`http {
|
||||
tls_trust_pool inline {
|
||||
trust_der %s
|
||||
}
|
||||
tls_trusted_ca_certs %s
|
||||
}`, test_der_1, test_der_1)),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "using 'inline' tls_trust_pool loads the module successfully",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(fmt.Sprintf(
|
||||
`http {
|
||||
tls_trust_pool inline {
|
||||
trust_der %s
|
||||
}
|
||||
}
|
||||
`, test_der_1)),
|
||||
},
|
||||
expectedTLSConfig: TLSConfig{CARaw: json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1))},
|
||||
},
|
||||
{
|
||||
name: "setting 'tls_trusted_ca_certs' and 'tls_trust_pool' produces an error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(fmt.Sprintf(
|
||||
`http {
|
||||
tls_trusted_ca_certs %s
|
||||
tls_trust_pool inline {
|
||||
trust_der %s
|
||||
}
|
||||
}`, test_der_1, test_der_1)),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ht := &HTTPTransport{}
|
||||
if err := ht.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
|
||||
t.Errorf("HTTPTransport.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr && !reflect.DeepEqual(&tt.expectedTLSConfig, ht.TLS) {
|
||||
t.Errorf("HTTPTransport.UnmarshalCaddyfile() = %v, want %v", ht, tt.expectedTLSConfig)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -129,15 +129,6 @@ type Handler struct {
|
||||
// are also set implicitly.
|
||||
Headers *headers.Handler `json:"headers,omitempty"`
|
||||
|
||||
// DEPRECATED: Do not use; will be removed. See request_buffers instead.
|
||||
DeprecatedBufferRequests bool `json:"buffer_requests,omitempty"`
|
||||
|
||||
// DEPRECATED: Do not use; will be removed. See response_buffers instead.
|
||||
DeprecatedBufferResponses bool `json:"buffer_responses,omitempty"`
|
||||
|
||||
// DEPRECATED: Do not use; will be removed. See request_buffers and response_buffers instead.
|
||||
DeprecatedMaxBufferSize int64 `json:"max_buffer_size,omitempty"`
|
||||
|
||||
// If nonzero, the entire request body up to this size will be read
|
||||
// and buffered in memory before being proxied to the backend. This
|
||||
// should be avoided if at all possible for performance reasons, but
|
||||
@@ -241,17 +232,6 @@ func (h *Handler) Provision(ctx caddy.Context) error {
|
||||
h.connections = make(map[io.ReadWriteCloser]openConnection)
|
||||
h.connectionsMu = new(sync.Mutex)
|
||||
|
||||
// TODO: remove deprecated fields sometime after v2.6.4
|
||||
if h.DeprecatedBufferRequests {
|
||||
h.logger.Warn("DEPRECATED: buffer_requests: this property will be removed soon; use request_buffers instead (and set a maximum buffer size)")
|
||||
}
|
||||
if h.DeprecatedBufferResponses {
|
||||
h.logger.Warn("DEPRECATED: buffer_responses: this property will be removed soon; use response_buffers instead (and set a maximum buffer size)")
|
||||
}
|
||||
if h.DeprecatedMaxBufferSize != 0 {
|
||||
h.logger.Warn("DEPRECATED: max_buffer_size: this property will be removed soon; use request_buffers and/or response_buffers instead (and set maximum buffer sizes)")
|
||||
}
|
||||
|
||||
// warn about unsafe buffering config
|
||||
if h.RequestBuffers == -1 || h.ResponseBuffers == -1 {
|
||||
h.logger.Warn("UNLIMITED BUFFERING: buffering is enabled without any cap on buffer size, which can result in OOM crashes")
|
||||
@@ -439,11 +419,27 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
||||
var proxyErr error
|
||||
var retries int
|
||||
for {
|
||||
// if the request body was buffered (and only the entire body, hence no body
|
||||
// set to read from after the buffer), make reading from the body idempotent
|
||||
// and reusable, so if a backend partially or fully reads the body but then
|
||||
// produces an error, the request can be repeated to the next backend with
|
||||
// the full body (retries should only happen for idempotent requests) (see #6259)
|
||||
if reqBodyBuf, ok := r.Body.(bodyReadCloser); ok && reqBodyBuf.body == nil {
|
||||
r.Body = io.NopCloser(bytes.NewReader(reqBodyBuf.buf.Bytes()))
|
||||
}
|
||||
|
||||
var done bool
|
||||
done, proxyErr = h.proxyLoopIteration(clonedReq, r, w, proxyErr, start, retries, repl, reqHeader, reqHost, next)
|
||||
if done {
|
||||
break
|
||||
}
|
||||
if h.VerboseLogs {
|
||||
var lbWait time.Duration
|
||||
if h.LoadBalancing != nil {
|
||||
lbWait = time.Duration(h.LoadBalancing.TryInterval)
|
||||
}
|
||||
h.logger.Debug("retrying", zap.Error(proxyErr), zap.Duration("after", lbWait))
|
||||
}
|
||||
retries++
|
||||
}
|
||||
|
||||
@@ -922,7 +918,9 @@ func (h *Handler) finalizeResponse(
|
||||
) error {
|
||||
// deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
|
||||
if res.StatusCode == http.StatusSwitchingProtocols {
|
||||
h.handleUpgradeResponse(logger, rw, req, res)
|
||||
var wg sync.WaitGroup
|
||||
h.handleUpgradeResponse(logger, &wg, rw, req, res)
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1129,7 +1127,7 @@ func (h Handler) bufferedBody(originalBody io.ReadCloser, limit int64) (io.ReadC
|
||||
buf.Reset()
|
||||
if limit > 0 {
|
||||
n, err := io.CopyN(buf, originalBody, limit)
|
||||
if err != nil || n == limit {
|
||||
if (err != nil && err != io.EOF) || n == limit {
|
||||
return bodyReadCloser{
|
||||
Reader: io.MultiReader(buf, originalBody),
|
||||
buf: buf,
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
func (h *Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWriter, req *http.Request, res *http.Response) {
|
||||
func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup, rw http.ResponseWriter, req *http.Request, res *http.Response) {
|
||||
reqUpType := upgradeType(req.Header)
|
||||
resUpType := upgradeType(res.Header)
|
||||
|
||||
@@ -101,6 +101,17 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWrit
|
||||
return
|
||||
}
|
||||
|
||||
// There may be buffered data in the *bufio.Reader
|
||||
// see: https://github.com/caddyserver/caddy/issues/6273
|
||||
if buffered := brw.Reader.Buffered(); buffered > 0 {
|
||||
data, _ := brw.Peek(buffered)
|
||||
_, err := backConn.Write(data)
|
||||
if err != nil {
|
||||
logger.Debug("backConn write failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the hijacked client connection, and the new connection established
|
||||
// with the backend, are both closed in the event of a server shutdown. This
|
||||
// is done by registering them. We also try to gracefully close connections
|
||||
@@ -121,7 +132,7 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWrit
|
||||
defer deleteFrontConn()
|
||||
defer deleteBackConn()
|
||||
|
||||
spc := switchProtocolCopier{user: conn, backend: backConn}
|
||||
spc := switchProtocolCopier{user: conn, backend: backConn, wg: wg}
|
||||
|
||||
// setup the timeout if requested
|
||||
var timeoutc <-chan time.Time
|
||||
@@ -132,6 +143,7 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, rw http.ResponseWrit
|
||||
}
|
||||
|
||||
errc := make(chan error, 1)
|
||||
wg.Add(2)
|
||||
go spc.copyToBackend(errc)
|
||||
go spc.copyFromBackend(errc)
|
||||
select {
|
||||
@@ -529,16 +541,19 @@ func (m *maxLatencyWriter) stop() {
|
||||
// forth have nice names in stacks.
|
||||
type switchProtocolCopier struct {
|
||||
user, backend io.ReadWriteCloser
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.user, c.backend)
|
||||
errc <- err
|
||||
c.wg.Done()
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.backend, c.user)
|
||||
errc <- err
|
||||
c.wg.Done()
|
||||
}
|
||||
|
||||
var streamingBufPool = sync.Pool{
|
||||
|
||||
@@ -311,7 +311,7 @@ func wrapRoute(route Route) Middleware {
|
||||
// we need to pull this particular MiddlewareHandler
|
||||
// pointer into its own stack frame to preserve it so it
|
||||
// won't be overwritten in future loop iterations.
|
||||
func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware {
|
||||
func wrapMiddleware(_ caddy.Context, mh MiddlewareHandler, metrics *Metrics) Middleware {
|
||||
handlerToUse := mh
|
||||
if metrics != nil {
|
||||
// wrap the middleware with metrics instrumentation
|
||||
@@ -326,8 +326,10 @@ func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler, metrics *Metrics) M
|
||||
nextCopy := next
|
||||
|
||||
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
// TODO: This is where request tracing could be implemented
|
||||
// TODO: see what the std lib gives us in terms of stack tracing too
|
||||
// EXPERIMENTAL: Trace each module that gets invoked
|
||||
if server, ok := r.Context().Value(ServerCtxKey).(*Server); ok && server != nil {
|
||||
server.logTrace(handlerToUse)
|
||||
}
|
||||
return handlerToUse.ServeHTTP(w, r, nextCopy)
|
||||
})
|
||||
}
|
||||
|
||||
+58
-25
@@ -234,6 +234,7 @@ type Server struct {
|
||||
logger *zap.Logger
|
||||
accessLogger *zap.Logger
|
||||
errorLogger *zap.Logger
|
||||
traceLogger *zap.Logger
|
||||
ctx caddy.Context
|
||||
|
||||
server *http.Server
|
||||
@@ -272,7 +273,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// advertise HTTP/3, if enabled
|
||||
if s.h3server != nil {
|
||||
if r.ProtoMajor < 3 {
|
||||
err := s.h3server.SetQuicHeaders(w.Header())
|
||||
err := s.h3server.SetQUICHeaders(w.Header())
|
||||
if err != nil {
|
||||
s.logger.Error("setting HTTP/3 Alt-Svc header", zap.Error(err))
|
||||
}
|
||||
@@ -326,6 +327,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Body != nil {
|
||||
bodyReader = &lengthReader{Source: r.Body}
|
||||
r.Body = bodyReader
|
||||
|
||||
// should always be true, private interface can only be referenced in the same package
|
||||
if setReadSizer, ok := wrec.(interface{ setReadSize(*int) }); ok {
|
||||
setReadSizer.setReadSize(&bodyReader.Length)
|
||||
}
|
||||
}
|
||||
|
||||
// capture the original version of the request
|
||||
@@ -361,11 +367,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
cloneURL(origReq.URL, r.URL)
|
||||
|
||||
// prepare the error log
|
||||
logger := errLog
|
||||
errLog = errLog.With(zap.Duration("duration", duration))
|
||||
errLoggers := []*zap.Logger{errLog}
|
||||
if s.Logs != nil {
|
||||
logger = s.Logs.wrapLogger(logger, r.Host)
|
||||
errLoggers = s.Logs.wrapLogger(errLog, r)
|
||||
}
|
||||
logger = logger.With(zap.Duration("duration", duration))
|
||||
|
||||
// get the values that will be used to log the error
|
||||
errStatus, errMsg, errFields := errLogValues(err)
|
||||
@@ -379,7 +385,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err2 == nil {
|
||||
// user's error route handled the error response
|
||||
// successfully, so now just log the error
|
||||
logger.Debug(errMsg, errFields...)
|
||||
for _, logger := range errLoggers {
|
||||
logger.Debug(errMsg, errFields...)
|
||||
}
|
||||
} else {
|
||||
// well... this is awkward
|
||||
errFields = append([]zapcore.Field{
|
||||
@@ -387,7 +395,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
zap.Namespace("first_error"),
|
||||
zap.String("msg", errMsg),
|
||||
}, errFields...)
|
||||
logger.Error("error handling handler error", errFields...)
|
||||
for _, logger := range errLoggers {
|
||||
logger.Error("error handling handler error", errFields...)
|
||||
}
|
||||
if handlerErr, ok := err.(HandlerError); ok {
|
||||
w.WriteHeader(handlerErr.StatusCode)
|
||||
} else {
|
||||
@@ -395,10 +405,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if errStatus >= 500 {
|
||||
logger.Error(errMsg, errFields...)
|
||||
} else {
|
||||
logger.Debug(errMsg, errFields...)
|
||||
for _, logger := range errLoggers {
|
||||
if errStatus >= 500 {
|
||||
logger.Error(errMsg, errFields...)
|
||||
} else {
|
||||
logger.Debug(errMsg, errFields...)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(errStatus)
|
||||
}
|
||||
@@ -587,7 +599,7 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error
|
||||
TLSConfig: tlsCfg,
|
||||
MaxHeaderBytes: s.MaxHeaderBytes,
|
||||
// TODO: remove this config when draft versions are no longer supported (we have no need to support drafts)
|
||||
QuicConfig: &quic.Config{
|
||||
QUICConfig: &quic.Config{
|
||||
Versions: []quic.Version{quic.Version1, quic.Version2},
|
||||
},
|
||||
ConnContext: func(ctx context.Context, c quic.Connection) context.Context {
|
||||
@@ -706,13 +718,20 @@ func (s *Server) shouldLogRequest(r *http.Request) bool {
|
||||
// logging is disabled
|
||||
return false
|
||||
}
|
||||
if _, ok := s.Logs.LoggerNames[r.Host]; ok {
|
||||
|
||||
// strip off the port if any, logger names are host only
|
||||
hostWithoutPort, _, err := net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
hostWithoutPort = r.Host
|
||||
}
|
||||
|
||||
if _, ok := s.Logs.LoggerNames[hostWithoutPort]; ok {
|
||||
// this host is mapped to a particular logger name
|
||||
return true
|
||||
}
|
||||
for _, dh := range s.Logs.SkipHosts {
|
||||
// logging for this particular host is disabled
|
||||
if certmagic.MatchWildcard(r.Host, dh) {
|
||||
if certmagic.MatchWildcard(hostWithoutPort, dh) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -720,6 +739,15 @@ func (s *Server) shouldLogRequest(r *http.Request) bool {
|
||||
return !s.Logs.SkipUnmappedHosts
|
||||
}
|
||||
|
||||
// logTrace will log that this middleware handler is being invoked.
|
||||
// It emits at DEBUG level.
|
||||
func (s *Server) logTrace(mh MiddlewareHandler) {
|
||||
if s.Logs == nil || !s.Logs.Trace {
|
||||
return
|
||||
}
|
||||
s.traceLogger.Debug(caddy.GetModuleName(mh), zap.Any("module", mh))
|
||||
}
|
||||
|
||||
// logRequest logs the request to access logs, unless skipped.
|
||||
func (s *Server) logRequest(
|
||||
accLog *zap.Logger, r *http.Request, wrec ResponseRecorder, duration *time.Duration,
|
||||
@@ -735,16 +763,6 @@ func (s *Server) logRequest(
|
||||
repl.Set("http.response.duration", duration)
|
||||
repl.Set("http.response.duration_ms", duration.Seconds()*1e3) // multiply seconds to preserve decimal (see #4666)
|
||||
|
||||
logger := accLog
|
||||
if s.Logs != nil {
|
||||
logger = s.Logs.wrapLogger(logger, r.Host)
|
||||
}
|
||||
|
||||
log := logger.Info
|
||||
if wrec.Status() >= 400 {
|
||||
log = logger.Error
|
||||
}
|
||||
|
||||
userID, _ := repl.GetString("http.auth.user.id")
|
||||
|
||||
reqBodyLength := 0
|
||||
@@ -768,7 +786,23 @@ func (s *Server) logRequest(
|
||||
}))
|
||||
fields = append(fields, extra.fields...)
|
||||
|
||||
log("handled request", fields...)
|
||||
loggers := []*zap.Logger{accLog}
|
||||
if s.Logs != nil {
|
||||
loggers = s.Logs.wrapLogger(accLog, r)
|
||||
}
|
||||
|
||||
// wrapping may return multiple loggers, so we log to all of them
|
||||
for _, logger := range loggers {
|
||||
logAtLevel := logger.Info
|
||||
if wrec.Status() >= 500 {
|
||||
logAtLevel = logger.Error
|
||||
}
|
||||
message := "handled request"
|
||||
if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop {
|
||||
message = "NOP"
|
||||
}
|
||||
logAtLevel(message, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
// protocol returns true if the protocol proto is configured/enabled.
|
||||
@@ -811,7 +845,6 @@ func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter
|
||||
ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2))
|
||||
|
||||
ctx = context.WithValue(ctx, ExtraLogFieldsCtxKey, new(ExtraLogFields))
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
// once the pointer to the request won't change
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode/zstd"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/intercept"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/logging"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/map"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/proxyprotocol"
|
||||
|
||||
@@ -206,7 +206,7 @@ func (s StaticResponse) ServeHTTP(w http.ResponseWriter, r *http.Request, next H
|
||||
// or for clients to render JSON properly which is very common)
|
||||
body := repl.ReplaceKnown(s.Body, "")
|
||||
if body != "" && w.Header().Get("Content-Type") == "" {
|
||||
content := strings.TrimSpace(s.Body)
|
||||
content := strings.TrimSpace(body)
|
||||
if len(content) > 2 &&
|
||||
(content[0] == '{' && content[len(content)-1] == '}' ||
|
||||
(content[0] == '[' && content[len(content)-1] == ']')) &&
|
||||
|
||||
@@ -161,7 +161,7 @@ func init() {
|
||||
// Renders the given Markdown text as HTML and returns it. This uses the
|
||||
// [Goldmark](https://github.com/yuin/goldmark) library,
|
||||
// which is CommonMark compliant. It also has these extensions
|
||||
// enabled: Github Flavored Markdown, Footnote, and syntax
|
||||
// enabled: GitHub Flavored Markdown, Footnote, and syntax
|
||||
// highlighting provided by [Chroma](https://github.com/alecthomas/chroma).
|
||||
//
|
||||
// ```
|
||||
@@ -300,6 +300,18 @@ func init() {
|
||||
// find the documentation on time layouts [in Go's docs](https://pkg.go.dev/time#pkg-constants).
|
||||
// The default time layout is `RFC1123Z`, i.e. `Mon, 02 Jan 2006 15:04:05 -0700`.
|
||||
//
|
||||
// ##### `pathEscape`
|
||||
//
|
||||
// Passes a string through `url.PathEscape`, replacing characters that have
|
||||
// special meaning in URL path parameters (`?`, `&`, `%`).
|
||||
//
|
||||
// Useful e.g. to include filenames containing these characters in URL path
|
||||
// parameters, or use them as an `img` element's `src` attribute.
|
||||
//
|
||||
// ```
|
||||
// {{pathEscape "50%_valid_filename?.jpg"}}
|
||||
// ```
|
||||
//
|
||||
// ```
|
||||
// {{humanize "size" "2048000"}}
|
||||
// {{placeholder "http.response.header.Content-Length" | humanize "size"}}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
@@ -91,6 +92,7 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
|
||||
"httpError": c.funcHTTPError,
|
||||
"humanize": c.funcHumanize,
|
||||
"maybe": c.funcMaybe,
|
||||
"pathEscape": url.PathEscape,
|
||||
})
|
||||
return c.tpl
|
||||
}
|
||||
@@ -249,6 +251,12 @@ func (c *TemplateContext) executeTemplateInBuffer(tplName string, buf *bytes.Buf
|
||||
|
||||
func (c TemplateContext) funcPlaceholder(name string) string {
|
||||
repl := c.Req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
// For safety, we don't want to allow the file placeholder in
|
||||
// templates because it could be used to read arbitrary files
|
||||
// if the template contents were not trusted.
|
||||
repl = repl.WithoutFile()
|
||||
|
||||
value, _ := repl.GetString(name)
|
||||
return value
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user