mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-14 06:46:27 +03:00
Merge branch 'master' into hurl-tests
This commit is contained in:
commit
eb6934f784
81 changed files with 2413 additions and 774 deletions
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
|
@ -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:
|
||||
|
||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -37,7 +37,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)
|
||||
|
|
8
.github/workflows/cross-build.yml
vendored
8
.github/workflows/cross-build.yml
vendored
|
@ -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
|
||||
|
|
9
.github/workflows/lint.yml
vendored
9
.github/workflows/lint.yml
vendored
|
@ -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
|
||||
|
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -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
|
||||
|
|
2
caddy.go
2
caddy.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
// }
|
||||
|
@ -915,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":
|
||||
|
@ -1001,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())
|
||||
}
|
||||
|
@ -1008,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
|
||||
|
@ -1059,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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -1430,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
|
||||
}
|
||||
|
@ -1591,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
|
||||
|
|
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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}"},
|
||||
}
|
||||
|
|
|
@ -456,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)
|
||||
|
@ -479,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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
@ -106,6 +116,38 @@ abort @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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
151
caddytest/integration/caddyfile_adapt/log_filter_with_header.txt
Normal file
151
caddytest/integration/caddyfile_adapt/log_filter_with_header.txt
Normal file
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
----------
|
||||
{
|
||||
|
@ -279,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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
caddytest/integration/intercept_test.go
Normal file
34
caddytest/integration/intercept_test.go
Normal file
|
@ -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")
|
||||
}
|
1
caddytest/integration/testdata/foo.txt
vendored
Normal file
1
caddytest/integration/testdata/foo.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
foo
|
48
cmd/main.go
48
cmd/main.go
|
@ -107,6 +107,40 @@ func LoadConfig(configFile, adapterName string) ([]byte, string, error) {
|
|||
return loadConfigWithLogger(caddy.Log(), configFile, adapterName)
|
||||
}
|
||||
|
||||
func isCaddyfile(configFile, adapterName string) (bool, error) {
|
||||
if adapterName == "caddyfile" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// as a special case, if a config file starts with "caddyfile" or
|
||||
// has a ".caddyfile" extension, and no adapter is specified, and
|
||||
// no adapter module name matches the extension, assume
|
||||
// caddyfile adapter for convenience
|
||||
baseConfig := strings.ToLower(filepath.Base(configFile))
|
||||
baseConfigExt := filepath.Ext(baseConfig)
|
||||
startsOrEndsInCaddyfile := strings.HasPrefix(baseConfig, "caddyfile") || strings.HasSuffix(baseConfig, ".caddyfile")
|
||||
|
||||
if baseConfigExt == ".json" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// If the adapter is not specified,
|
||||
// the config file starts with "caddyfile",
|
||||
// the config file has an extension,
|
||||
// and isn't a JSON file (e.g. Caddyfile.yaml),
|
||||
// then we don't know what the config format is.
|
||||
if adapterName == "" && startsOrEndsInCaddyfile {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// adapter is not empty,
|
||||
// adapter is not "caddyfile",
|
||||
// extension is not ".json",
|
||||
// extension is not ".caddyfile"
|
||||
// file does not start with "Caddyfile"
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([]byte, string, error) {
|
||||
// if no logger is provided, use a nop logger
|
||||
// just so we don't have to check for nil
|
||||
|
@ -157,18 +191,10 @@ func loadConfigWithLogger(logger *zap.Logger, configFile, adapterName string) ([
|
|||
}
|
||||
}
|
||||
|
||||
// as a special case, if a config file starts with "caddyfile" or
|
||||
// has a ".caddyfile" extension, and no adapter is specified, and
|
||||
// no adapter module name matches the extension, assume
|
||||
// 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) &&
|
||||
baseConfigExt != ".json" &&
|
||||
adapterName == "" {
|
||||
if yes, err := isCaddyfile(configFile, adapterName); yes {
|
||||
adapterName = "caddyfile"
|
||||
} else if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// load config adapter
|
||||
|
|
110
cmd/main_test.go
110
cmd/main_test.go
|
@ -168,3 +168,113 @@ here"
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isCaddyfile(t *testing.T) {
|
||||
type args struct {
|
||||
configFile string
|
||||
adapterName string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "bare Caddyfile without adapter",
|
||||
args: args{
|
||||
configFile: "Caddyfile",
|
||||
adapterName: "",
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "local Caddyfile without adapter",
|
||||
args: args{
|
||||
configFile: "./Caddyfile",
|
||||
adapterName: "",
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "local caddyfile with adapter",
|
||||
args: args{
|
||||
configFile: "./Caddyfile",
|
||||
adapterName: "caddyfile",
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ends with .caddyfile with adapter",
|
||||
args: args{
|
||||
configFile: "./conf.caddyfile",
|
||||
adapterName: "caddyfile",
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ends with .caddyfile without adapter",
|
||||
args: args{
|
||||
configFile: "./conf.caddyfile",
|
||||
adapterName: "",
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "config is Caddyfile.yaml with adapter",
|
||||
args: args{
|
||||
configFile: "./Caddyfile.yaml",
|
||||
adapterName: "yaml",
|
||||
},
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
|
||||
name: "json is not caddyfile but not error",
|
||||
args: args{
|
||||
configFile: "./Caddyfile.json",
|
||||
adapterName: "",
|
||||
},
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
|
||||
name: "prefix of Caddyfile and ./ with any extension is Caddyfile",
|
||||
args: args{
|
||||
configFile: "./Caddyfile.prd",
|
||||
adapterName: "",
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
|
||||
name: "prefix of Caddyfile without ./ with any extension is Caddyfile",
|
||||
args: args{
|
||||
configFile: "Caddyfile.prd",
|
||||
adapterName: "",
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := isCaddyfile(tt.args.configFile, tt.args.adapterName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("isCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("isCaddyfile() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
33
cmd/x509rootsfallback.go
Normal file
33
cmd/x509rootsfallback.go
Normal file
|
@ -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"
|
||||
)
|
34
context.go
34
context.go
|
@ -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
|
||||
|
|
65
go.mod
65
go.mod
|
@ -1,44 +1,47 @@
|
|||
module github.com/caddyserver/caddy/v2
|
||||
|
||||
go 1.22.0
|
||||
go 1.21.0
|
||||
|
||||
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.1-0.20240412214119-167015dd6570
|
||||
github.com/caddyserver/zerossl v0.1.2
|
||||
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/compress v1.17.8
|
||||
github.com/klauspost/cpuid/v2 v2.2.7
|
||||
github.com/mholt/acmez/v2 v2.0.0-beta.2
|
||||
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/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
|
||||
|
@ -70,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 (
|
||||
|
@ -116,7 +119,7 @@ require (
|
|||
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.58 // 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
|
||||
|
@ -134,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
|
||||
)
|
||||
|
|
226
go.sum
226
go.sum
|
@ -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,40 +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.1-0.20240412214119-167015dd6570 h1:SsAXjoQx2wOmLl6mEwJEwh7wwys2hb/l/mhtmxA3wts=
|
||||
github.com/caddyserver/certmagic v0.20.1-0.20240412214119-167015dd6570/go.mod h1:e1NhB1rF5KZnAuAX6oSyhE7sg1Ru5bWgggw5RtauhEY=
|
||||
github.com/caddyserver/zerossl v0.1.2 h1:tlEu1VzWGoqcCpivs9liKAKhfpJWYJkHEMmlxRbVAxE=
|
||||
github.com/caddyserver/zerossl v0.1.2/go.mod h1:wtiJEHbdvunr40ZzhXlnIkOB8Xj4eKtBKizCcZitJiQ=
|
||||
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=
|
||||
|
@ -166,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=
|
||||
|
@ -176,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=
|
||||
|
@ -190,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=
|
||||
|
@ -255,8 +260,8 @@ 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.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
|
@ -294,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/v2 v2.0.0-beta.2 h1:GIgGILx8AWN0ePyTd+bjs2WDgNiIWm0nBwDLWp59aHc=
|
||||
github.com/mholt/acmez/v2 v2.0.0-beta.2/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
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=
|
||||
|
@ -324,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=
|
||||
|
@ -334,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=
|
||||
|
@ -364,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=
|
||||
|
@ -410,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=
|
||||
|
@ -431,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=
|
||||
|
@ -449,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=
|
||||
|
@ -505,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=
|
||||
|
@ -525,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=
|
||||
|
@ -560,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=
|
||||
|
@ -569,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=
|
||||
|
@ -580,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=
|
||||
|
@ -595,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=
|
||||
|
|
16
listeners.go
16
listeners.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,40 @@ 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: "C:\\www", // inputPath fails IsLocal on Windows
|
||||
},
|
||||
{
|
||||
inputRoot: `C:\www`,
|
||||
inputPath: `/..\windows\win.ini`,
|
||||
expect: `C:\www/..\windows\win.ini`,
|
||||
expectWindows: `C:\www`,
|
||||
},
|
||||
{
|
||||
inputRoot: `C:\www`,
|
||||
inputPath: `/..\..\..\..\..\..\..\..\..\..\windows\win.ini`,
|
||||
expect: `C:\www/..\..\..\..\..\..\..\..\..\..\windows\win.ini`,
|
||||
expectWindows: `C:\www`,
|
||||
},
|
||||
{
|
||||
inputRoot: `C:\www`,
|
||||
inputPath: `/..%5cwindows%5cwin.ini`,
|
||||
expect: `C:\www/..\windows\win.ini`,
|
||||
expectWindows: `C:\www`,
|
||||
},
|
||||
{
|
||||
inputRoot: `C:\www`,
|
||||
inputPath: `/..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5c..%5cwindows%5cwin.ini`,
|
||||
expect: `C:\www/..\..\..\..\..\..\..\..\..\..\windows\win.ini`,
|
||||
expectWindows: `C:\www`,
|
||||
},
|
||||
{
|
||||
// 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 +137,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)
|
||||
|
|
|
@ -239,7 +239,7 @@ func (rw *responseWriter) WriteHeader(status int) {
|
|||
// 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 {
|
||||
if status == http.StatusNotModified && !hasVaryValue(rw.Header(), "Accept-Encoding") {
|
||||
rw.Header().Add("Vary", "Accept-Encoding")
|
||||
}
|
||||
|
||||
|
@ -349,14 +349,17 @@ 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:
|
||||
|
@ -365,13 +368,25 @@ func (rw *responseWriter) init() {
|
|||
// (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 := rw.Header().Get("Etag"); etag != "" && !strings.HasPrefix(etag, "W/") {
|
||||
if etag := hdr.Get("Etag"); etag != "" && !strings.HasPrefix(etag, "W/") {
|
||||
etag = fmt.Sprintf(`%s-%s"`, strings.TrimSuffix(etag, `"`), rw.encodingName)
|
||||
rw.Header().Set("Etag", etag)
|
||||
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
|
||||
|
|
|
@ -105,7 +105,7 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
|
|||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
w.Header().Add("Vary", "Accept")
|
||||
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 != "" {
|
||||
|
|
|
@ -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="{{.Name | replace "#" "%23" | replace "?" "%3f" | html}}">
|
||||
<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"/>
|
||||
|
|
|
@ -132,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.
|
||||
|
|
|
@ -371,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) {
|
||||
|
@ -398,9 +406,8 @@ 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 {
|
||||
|
@ -454,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)
|
||||
}
|
||||
}
|
||||
|
@ -462,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"`
|
||||
|
|
350
modules/caddyhttp/intercept/intercept.go
Normal file
350
modules/caddyhttp/intercept/intercept.go
Normal file
|
@ -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, "%") {
|
||||
|
|
|
@ -37,10 +37,16 @@ type ServerLogConfig struct {
|
|||
DefaultLoggerName string `json:"default_logger_name,omitempty"`
|
||||
|
||||
// LoggerNames maps request hostnames to one or more custom logger
|
||||
// names. For example, a mapping of "example.com" to "example" would
|
||||
// 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"`
|
||||
|
@ -59,40 +65,66 @@ 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 one or more logger named
|
||||
// according to user preferences for the given host.
|
||||
func (slc ServerLogConfig) wrapLogger(logger *zap.Logger, host string) []*zap.Logger {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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) getLoggerHosts(host string) []string {
|
||||
tryHost := func(key string) ([]string, bool) {
|
||||
// first try exact match
|
||||
if hosts, ok := slc.LoggerNames[key]; ok {
|
||||
return hosts, 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 []string{}, false
|
||||
}
|
||||
hosts, ok := slc.LoggerNames[hostOnly]
|
||||
return hosts, ok
|
||||
}
|
||||
|
||||
// try the exact hostname first
|
||||
if hosts, ok := tryHost(host); ok {
|
||||
if hosts, ok := slc.LoggerNames[host]; ok {
|
||||
return hosts
|
||||
}
|
||||
|
||||
|
@ -104,7 +136,7 @@ func (slc ServerLogConfig) getLoggerHosts(host string) []string {
|
|||
}
|
||||
labels[i] = "*"
|
||||
wildcardHost := strings.Join(labels, ".")
|
||||
if hosts, ok := tryHost(wildcardHost); ok {
|
||||
if hosts, ok := slc.LoggerNames[wildcardHost]; ok {
|
||||
return hosts
|
||||
}
|
||||
}
|
||||
|
@ -211,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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -263,6 +263,8 @@ func (rr *responseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|||
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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -75,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...>]
|
||||
// }
|
||||
|
@ -450,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()
|
||||
|
@ -607,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()
|
||||
|
@ -1139,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()
|
||||
|
@ -1383,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
|
||||
|
@ -1462,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())
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -31,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"
|
||||
|
||||
|
@ -124,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.
|
||||
|
@ -225,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
|
||||
|
@ -344,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") {
|
||||
|
@ -408,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 {
|
||||
|
@ -535,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
|
||||
|
|
|
@ -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++
|
||||
}
|
||||
|
||||
|
@ -1131,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,
|
||||
|
|
|
@ -101,6 +101,17 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
|||
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
|
||||
|
|
|
@ -326,8 +326,10 @@ func wrapMiddleware(_ caddy.Context, mh MiddlewareHandler, metrics *Metrics) Mid
|
|||
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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
@ -369,7 +370,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
errLog = errLog.With(zap.Duration("duration", duration))
|
||||
errLoggers := []*zap.Logger{errLog}
|
||||
if s.Logs != nil {
|
||||
errLoggers = s.Logs.wrapLogger(errLog, r.Host)
|
||||
errLoggers = s.Logs.wrapLogger(errLog, r)
|
||||
}
|
||||
|
||||
// get the values that will be used to log the error
|
||||
|
@ -598,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 {
|
||||
|
@ -717,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
|
||||
}
|
||||
}
|
||||
|
@ -731,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,
|
||||
|
@ -771,17 +788,20 @@ func (s *Server) logRequest(
|
|||
|
||||
loggers := []*zap.Logger{accLog}
|
||||
if s.Logs != nil {
|
||||
loggers = s.Logs.wrapLogger(accLog, r.Host)
|
||||
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() >= 400 {
|
||||
if wrec.Status() >= 500 {
|
||||
logAtLevel = logger.Error
|
||||
}
|
||||
|
||||
logAtLevel("handled request", fields...)
|
||||
message := "handled request"
|
||||
if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop {
|
||||
message = "NOP"
|
||||
}
|
||||
logAtLevel(message, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -825,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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -87,8 +87,12 @@ func (ot *openTelemetryWrapper) serveHTTP(w http.ResponseWriter, r *http.Request
|
|||
ot.propagators.Inject(ctx, propagation.HeaderCarrier(r.Header))
|
||||
spanCtx := trace.SpanContextFromContext(ctx)
|
||||
if spanCtx.IsValid() {
|
||||
traceID := spanCtx.TraceID().String()
|
||||
// Add a trace_id placeholder, accessible via `{http.vars.trace_id}`.
|
||||
caddyhttp.SetVar(ctx, "trace_id", traceID)
|
||||
// Add the trace id to the log fields for the request.
|
||||
if extra, ok := ctx.Value(caddyhttp.ExtraLogFieldsCtxKey).(*caddyhttp.ExtraLogFields); ok {
|
||||
extra.Add(zap.String("traceID", spanCtx.TraceID().String()))
|
||||
extra.Add(zap.String("traceID", traceID))
|
||||
}
|
||||
}
|
||||
next := ctx.Value(nextCallCtxKey).(*nextCall)
|
||||
|
|
|
@ -42,6 +42,7 @@ func init() {
|
|||
// domains <domains...>
|
||||
// ip_ranges <addresses...>
|
||||
// }
|
||||
// sign_with_root
|
||||
// }
|
||||
func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
||||
h.Next() // consume directive name
|
||||
|
@ -136,6 +137,11 @@ func parseACMEServer(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
|||
acmeServer.Policy = &Policy{}
|
||||
}
|
||||
acmeServer.Policy.Deny = r
|
||||
case "sign_with_root":
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
acmeServer.SignWithRoot = true
|
||||
default:
|
||||
return nil, h.Errf("unrecognized ACME server directive: %s", h.Val())
|
||||
}
|
||||
|
|
|
@ -50,8 +50,11 @@ func (a *adminAPI) Provision(ctx caddy.Context) error {
|
|||
a.ctx = ctx
|
||||
a.log = ctx.Logger(a) // TODO: passing in 'a' is a hack until the admin API is officially extensible (see #5032)
|
||||
|
||||
// Avoid initializing PKI if it wasn't configured
|
||||
if pkiApp := a.ctx.AppIfConfigured("pki"); pkiApp != nil {
|
||||
// Avoid initializing PKI if it wasn't configured.
|
||||
// We intentionally ignore the error since it's not
|
||||
// fatal if the PKI app is not explicitly configured.
|
||||
pkiApp, err := ctx.AppIfConfigured("pki")
|
||||
if err == nil {
|
||||
a.pkiApp = pkiApp.(*PKI)
|
||||
}
|
||||
|
||||
|
|
|
@ -78,18 +78,21 @@ func (kp KeyPair) Load() (*x509.Certificate, crypto.Signer, error) {
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keyData, err := os.ReadFile(kp.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cert, err := pemDecodeSingleCert(certData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
key, err := certmagic.PEMDecodePrivateKey(keyData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
var key crypto.Signer
|
||||
if kp.PrivateKey != "" {
|
||||
keyData, err := os.ReadFile(kp.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
key, err = certmagic.PEMDecodePrivateKey(keyData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
|
|
|
@ -88,6 +88,15 @@ type ACMEIssuer struct {
|
|||
// will be selected.
|
||||
PreferredChains *ChainPreference `json:"preferred_chains,omitempty"`
|
||||
|
||||
// The validity period to ask the CA to issue a certificate for.
|
||||
// Default: 0 (CA chooses lifetime).
|
||||
// This value is used to compute the "notAfter" field of the ACME order;
|
||||
// therefore the system must have a reasonably synchronized clock.
|
||||
// NOTE: Not all CAs support this. Check with your CA's ACME
|
||||
// documentation to see if this is allowed and what values may
|
||||
// be used. EXPERIMENTAL: Subject to change.
|
||||
CertificateLifetime caddy.Duration `json:"certificate_lifetime,omitempty"`
|
||||
|
||||
rootPool *x509.CertPool
|
||||
logger *zap.Logger
|
||||
|
||||
|
@ -178,6 +187,7 @@ func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) {
|
|||
CertObtainTimeout: time.Duration(iss.ACMETimeout),
|
||||
TrustedRoots: iss.rootPool,
|
||||
ExternalAccount: iss.ExternalAccount,
|
||||
NotAfter: time.Duration(iss.CertificateLifetime),
|
||||
Logger: iss.logger,
|
||||
}
|
||||
|
||||
|
@ -254,6 +264,12 @@ func (iss *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateRes
|
|||
// to be accessed and manipulated.
|
||||
func (iss *ACMEIssuer) GetACMEIssuer() *ACMEIssuer { return iss }
|
||||
|
||||
// GetRenewalInfo wraps the underlying GetRenewalInfo method and satisfies
|
||||
// the CertMagic interface for ARI support.
|
||||
func (iss *ACMEIssuer) GetRenewalInfo(ctx context.Context, cert certmagic.Certificate) (acme.RenewalInfo, error) {
|
||||
return iss.issuer.GetRenewalInfo(ctx, cert)
|
||||
}
|
||||
|
||||
// generateZeroSSLEABCredentials generates ZeroSSL EAB credentials for the primary contact email
|
||||
// on the issuer. It should only be usedif the CA endpoint is ZeroSSL. An email address is required.
|
||||
func (iss *ACMEIssuer) generateZeroSSLEABCredentials(ctx context.Context, acct acme.Account) (*acme.EAB, acme.Account, error) {
|
||||
|
@ -349,6 +365,20 @@ func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
|
||||
for d.NextBlock(0) {
|
||||
switch d.Val() {
|
||||
case "lifetime":
|
||||
var lifetimeStr string
|
||||
if !d.AllArgs(&lifetimeStr) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
lifetime, err := caddy.ParseDuration(lifetimeStr)
|
||||
if err != nil {
|
||||
return d.Errf("invalid lifetime %s: %v", lifetimeStr, err)
|
||||
}
|
||||
if lifetime < 0 {
|
||||
return d.Errf("lifetime must be >= 0: %s", lifetime)
|
||||
}
|
||||
iss.CertificateLifetime = caddy.Duration(lifetime)
|
||||
|
||||
case "dir":
|
||||
if iss.CA != "" {
|
||||
return d.Errf("directory is already specified: %s", iss.CA)
|
||||
|
@ -625,10 +655,11 @@ type ChainPreference struct {
|
|||
|
||||
// Interface guards
|
||||
var (
|
||||
_ certmagic.PreChecker = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Issuer = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Revoker = (*ACMEIssuer)(nil)
|
||||
_ caddy.Provisioner = (*ACMEIssuer)(nil)
|
||||
_ ConfigSetter = (*ACMEIssuer)(nil)
|
||||
_ caddyfile.Unmarshaler = (*ACMEIssuer)(nil)
|
||||
_ certmagic.PreChecker = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Issuer = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Revoker = (*ACMEIssuer)(nil)
|
||||
_ certmagic.RenewalInfoGetter = (*ACMEIssuer)(nil)
|
||||
_ caddy.Provisioner = (*ACMEIssuer)(nil)
|
||||
_ ConfigSetter = (*ACMEIssuer)(nil)
|
||||
_ caddyfile.Unmarshaler = (*ACMEIssuer)(nil)
|
||||
)
|
||||
|
|
|
@ -97,7 +97,7 @@ type AutomationPolicy struct {
|
|||
SubjectsRaw []string `json:"subjects,omitempty"`
|
||||
|
||||
// The modules that may issue certificates. Default: internal if all
|
||||
// subjects do not qualify for public certificates; othewise acme and
|
||||
// subjects do not qualify for public certificates; otherwise acme and
|
||||
// zerossl.
|
||||
IssuersRaw []json.RawMessage `json:"issuers,omitempty" caddy:"namespace=tls.issuance inline_key=module"`
|
||||
|
||||
|
@ -170,6 +170,9 @@ type AutomationPolicy struct {
|
|||
subjects []string
|
||||
magic *certmagic.Config
|
||||
storage certmagic.Storage
|
||||
|
||||
// Whether this policy had explicit managers configured directly on it.
|
||||
hadExplicitManagers bool
|
||||
}
|
||||
|
||||
// Provision sets up ap and builds its underlying CertMagic config.
|
||||
|
@ -201,8 +204,8 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
|
|||
// store them on the policy before putting it on the config
|
||||
|
||||
// load and provision any cert manager modules
|
||||
hadExplicitManagers := len(ap.ManagersRaw) > 0
|
||||
if ap.ManagersRaw != nil {
|
||||
ap.hadExplicitManagers = true
|
||||
vals, err := tlsApp.ctx.LoadModule(ap, "ManagersRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading external certificate manager modules: %v", err)
|
||||
|
@ -262,9 +265,9 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
|
|||
// prevent issuance from Issuers (when Managers don't provide a certificate) if there's no
|
||||
// permission module configured
|
||||
noProtections := ap.isWildcardOrDefault() && !ap.onlyInternalIssuer() && (tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil || tlsApp.Automation.OnDemand.permission == nil)
|
||||
failClosed := noProtections && hadExplicitManagers // don't allow on-demand issuance (other than implicit managers) if no managers have been explicitly configured
|
||||
failClosed := noProtections && !ap.hadExplicitManagers // don't allow on-demand issuance (other than implicit managers) if no managers have been explicitly configured
|
||||
if noProtections {
|
||||
if !hadExplicitManagers {
|
||||
if !ap.hadExplicitManagers {
|
||||
// no managers, no explicitly-configured permission module, this is a config error
|
||||
return fmt.Errorf("on-demand TLS cannot be enabled without a permission module to prevent abuse; please refer to documentation for details")
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ func init() {
|
|||
caddy.RegisterModule(PKIIntermediateCAPool{})
|
||||
caddy.RegisterModule(StoragePool{})
|
||||
caddy.RegisterModule(HTTPCertPool{})
|
||||
caddy.RegisterModule(LazyCertPool{})
|
||||
}
|
||||
|
||||
// The interface to be implemented by all guest modules part of
|
||||
|
@ -188,9 +187,9 @@ func (PKIRootCAPool) CaddyModule() caddy.ModuleInfo {
|
|||
|
||||
// Loads the PKI app and load the root certificates into the certificate pool
|
||||
func (p *PKIRootCAPool) Provision(ctx caddy.Context) error {
|
||||
pkiApp := ctx.AppIfConfigured("pki")
|
||||
if pkiApp == nil {
|
||||
return fmt.Errorf("PKI app not configured")
|
||||
pkiApp, err := ctx.AppIfConfigured("pki")
|
||||
if err != nil {
|
||||
return fmt.Errorf("pki_root CA pool requires that a PKI app is configured: %v", err)
|
||||
}
|
||||
pki := pkiApp.(*caddypki.PKI)
|
||||
for _, caID := range p.Authority {
|
||||
|
@ -260,9 +259,9 @@ func (PKIIntermediateCAPool) CaddyModule() caddy.ModuleInfo {
|
|||
|
||||
// Loads the PKI app and load the intermediate certificates into the certificate pool
|
||||
func (p *PKIIntermediateCAPool) Provision(ctx caddy.Context) error {
|
||||
pkiApp := ctx.AppIfConfigured("pki")
|
||||
if pkiApp == nil {
|
||||
return fmt.Errorf("PKI app not configured")
|
||||
pkiApp, err := ctx.AppIfConfigured("pki")
|
||||
if err != nil {
|
||||
return fmt.Errorf("pki_intermediate CA pool requires that a PKI app is configured: %v", err)
|
||||
}
|
||||
pki := pkiApp.(*caddypki.PKI)
|
||||
for _, caID := range p.Authority {
|
||||
|
@ -500,7 +499,7 @@ func (t *TLSConfig) unmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
// 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.
|
||||
// copied from with minor modifications: modules/caddyhttp/reverseproxy/httptransport.go
|
||||
func (t TLSConfig) makeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
func (t *TLSConfig) makeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
if repl == nil {
|
||||
repl = caddy.NewReplacer()
|
||||
|
@ -664,121 +663,6 @@ func (hcp HTTPCertPool) CertPool() *x509.CertPool {
|
|||
return hcp.pool
|
||||
}
|
||||
|
||||
// LazyCertPool defers the generation of the certificate pool from the
|
||||
// guest module to demand-time rather than at provisionig time. The gain of the
|
||||
// lazy load adds a risk of failure to load the certificates at demand time
|
||||
// because the validation that's typically done at provisioning is deferred.
|
||||
// The validation can be enforced to run before runtime by setting
|
||||
// `EagerValidation`/`eager_validation` to `true`. It is the operator's responsibility
|
||||
// to ensure the resources are available if `EagerValidation`/`eager_validation`
|
||||
// is set to `true`. The module also incurs performance cost at every demand.
|
||||
type LazyCertPool struct {
|
||||
// Provides the guest module that provides the trusted certificate authority (CA) certificates
|
||||
CARaw json.RawMessage `json:"ca,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
|
||||
|
||||
// Whether the validation step should try to load and provision the guest module to validate
|
||||
// the correctness of the configuration. Depeneding on the type of the guest module,
|
||||
// the resources may not be available at validation time. It is the
|
||||
// operator's responsibility to ensure the resources are available if `EagerValidation`/`eager_validation`
|
||||
// is set to `true`.
|
||||
EagerValidation bool `json:"eager_validation,omitempty"`
|
||||
|
||||
ctx caddy.Context
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
func (LazyCertPool) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "tls.ca_pool.source.lazy",
|
||||
New: func() caddy.Module {
|
||||
return new(LazyCertPool)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Provision implements caddy.Provisioner.
|
||||
func (lcp *LazyCertPool) Provision(ctx caddy.Context) error {
|
||||
if len(lcp.CARaw) == 0 {
|
||||
return fmt.Errorf("missing backing CA source")
|
||||
}
|
||||
lcp.ctx = ctx
|
||||
return nil
|
||||
}
|
||||
|
||||
// Syntax:
|
||||
//
|
||||
// trust_pool lazy {
|
||||
// backend <ca_module>
|
||||
// eager_validation
|
||||
// }
|
||||
//
|
||||
// The `backend` directive specifies the CA module to use to provision the
|
||||
// certificate pool. The `eager_validation` directive specifies that the
|
||||
// validation step should try to load and provision the guest module to validate
|
||||
// the correctness of the configuration. Depeneding on the type of the guest module,
|
||||
// the resources may not be available at validation time. It is the
|
||||
// operator's responsibility to ensure the resources are available if `EagerValidation`/`eager_validation`
|
||||
// is set to `true`.
|
||||
//
|
||||
// The `backend` directive is required.
|
||||
func (lcp *LazyCertPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume module name
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "backend":
|
||||
if lcp.CARaw != nil {
|
||||
return d.Err("backend block already defined")
|
||||
}
|
||||
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
|
||||
}
|
||||
backend, ok := unm.(CA)
|
||||
if !ok {
|
||||
return d.Errf("module %s is not a caddytls.CA", modID)
|
||||
}
|
||||
lcp.CARaw = caddyconfig.JSONModuleObject(backend, "provider", modStem, nil)
|
||||
case "eager_validation":
|
||||
lcp.EagerValidation = true
|
||||
default:
|
||||
return d.Errf("unrecognized directive: %s", d.Val())
|
||||
}
|
||||
}
|
||||
if lcp.CARaw == nil {
|
||||
return d.Err("backend block is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If EagerValidation is `true`, it attempts to load and provision the guest module
|
||||
// to ensure the guesst module's configuration is correct. Depeneding on the type of the
|
||||
// guest module, the resources may not be available at validation time. It is the
|
||||
// operator's responsibility to ensure the resources are available if `EagerValidation` is
|
||||
// set to `true`.
|
||||
func (lcp LazyCertPool) Validate() error {
|
||||
if lcp.EagerValidation {
|
||||
_, err := lcp.ctx.LoadModule(lcp, "CARaw")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CertPool loads the guest module and returns the CertPool from there
|
||||
// TODO: Cache?
|
||||
func (lcp LazyCertPool) CertPool() *x509.CertPool {
|
||||
caRaw, err := lcp.ctx.LoadModule(lcp, "CARaw")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
ca := caRaw.(CA)
|
||||
return ca.CertPool()
|
||||
}
|
||||
|
||||
var (
|
||||
_ caddy.Module = (*InlineCAPool)(nil)
|
||||
_ caddy.Provisioner = (*InlineCAPool)(nil)
|
||||
|
@ -810,10 +694,4 @@ var (
|
|||
_ caddy.Validator = (*HTTPCertPool)(nil)
|
||||
_ CA = (*HTTPCertPool)(nil)
|
||||
_ caddyfile.Unmarshaler = (*HTTPCertPool)(nil)
|
||||
|
||||
_ caddy.Module = (*LazyCertPool)(nil)
|
||||
_ caddy.Provisioner = (*LazyCertPool)(nil)
|
||||
_ caddy.Validator = (*LazyCertPool)(nil)
|
||||
_ CA = (*LazyCertPool)(nil)
|
||||
_ caddyfile.Unmarshaler = (*LazyCertPool)(nil)
|
||||
)
|
||||
|
|
|
@ -547,7 +547,7 @@ func TestTLSConfig_unmarshalCaddyfile(t *testing.T) {
|
|||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "setting 'ca' without arguemnt is an error",
|
||||
name: "setting 'ca' without argument is an error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`{
|
||||
ca
|
||||
|
@ -566,21 +566,6 @@ func TestTLSConfig_unmarshalCaddyfile(t *testing.T) {
|
|||
CARaw: []byte(`{"pem_files":["/var/caddy/ca.pem"],"provider":"file"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "setting 'ca' to 'lazy' with appropriate block is valid",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`{
|
||||
ca lazy {
|
||||
backend file {
|
||||
pem_file /var/caddy/ca.pem
|
||||
}
|
||||
}
|
||||
}`),
|
||||
},
|
||||
expected: TLSConfig{
|
||||
CARaw: []byte(`{"ca":{"pem_files":["/var/caddy/ca.pem"],"provider":"file"},"provider":"lazy"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "setting 'ca' to 'file' with appropriate block is valid",
|
||||
args: args{
|
||||
|
@ -725,7 +710,7 @@ func TestHTTPCertPoolUnmarshalCaddyfile(t *testing.T) {
|
|||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "endpoints defiend inline and in block are merged",
|
||||
name: "endpoints defined inline and in block are merged",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`http http://localhost/ca-certs {
|
||||
endpoints http://remotehost/ca-certs
|
||||
|
@ -737,7 +722,7 @@ func TestHTTPCertPoolUnmarshalCaddyfile(t *testing.T) {
|
|||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple endpoints defiend in block on the same line",
|
||||
name: "multiple endpoints defined in block on the same line",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`http {
|
||||
endpoints http://remotehost/ca-certs http://localhost/ca-certs
|
||||
|
@ -791,102 +776,3 @@ func TestHTTPCertPoolUnmarshalCaddyfile(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLazyCertPoolUnmarshalCaddyfile(t *testing.T) {
|
||||
type args struct {
|
||||
d *caddyfile.Dispenser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expected LazyCertPool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no block results in error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`lazy`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty block results in error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`lazy {
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "defining 'backend' multiple times results in error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`lazy {
|
||||
backend http {
|
||||
endpoints http://localhost/ca-certs
|
||||
}
|
||||
backend file {
|
||||
pem_file /var/caddy/certs
|
||||
}
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "defining 'backend' without argument results in error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`lazy {
|
||||
backend
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "using unrecognized directive results in error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`lazy {
|
||||
foo
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "defining single 'backend' is successful",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`lazy {
|
||||
backend http {
|
||||
endpoints http://localhost/ca-certs
|
||||
}
|
||||
}`),
|
||||
},
|
||||
expected: LazyCertPool{
|
||||
CARaw: []byte(`{"endpoints":["http://localhost/ca-certs"],"provider":"http"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "defining single 'backend' with 'eager_validation' successful",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`lazy {
|
||||
backend file {
|
||||
pem_file /var/caddy/certs
|
||||
}
|
||||
eager_validation
|
||||
}`),
|
||||
},
|
||||
expected: LazyCertPool{
|
||||
CARaw: []byte(`{"pem_files":["/var/caddy/certs"],"provider":"file"}`),
|
||||
EagerValidation: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
lcp := &LazyCertPool{}
|
||||
if err := lcp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
|
||||
t.Errorf("LazyCertPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !tt.wantErr && !reflect.DeepEqual(&tt.expected, lcp) {
|
||||
t.Errorf("LazyCertPool.UnmarshalCaddyfile() = %v, want %v", lcp, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ func (ts Tailscale) GetCertificate(ctx context.Context, hello *tls.ClientHelloIn
|
|||
if err != nil {
|
||||
ts.logger.Warn("could not get status; will try to get certificate anyway", zap.Error(err))
|
||||
}
|
||||
return tscert.GetCertificate(hello)
|
||||
return tscert.GetCertificateWithContext(ctx, hello)
|
||||
}
|
||||
|
||||
// canHazCertificate returns true if Tailscale reports it can get a certificate for the given ClientHello.
|
||||
|
|
|
@ -119,6 +119,9 @@ func (cp ConnectionPolicies) TLSConfig(_ caddy.Context) *tls.Config {
|
|||
continue policyLoop
|
||||
}
|
||||
}
|
||||
if pol.Drop {
|
||||
return nil, fmt.Errorf("dropping connection")
|
||||
}
|
||||
return pol.TLSConfig, nil
|
||||
}
|
||||
|
||||
|
@ -156,6 +159,9 @@ type ConnectionPolicy struct {
|
|||
// Maximum TLS protocol version to allow. Default: `tls1.3`
|
||||
ProtocolMax string `json:"protocol_max,omitempty"`
|
||||
|
||||
// Reject TLS connections. EXPERIMENTAL: May change.
|
||||
Drop bool `json:"drop,omitempty"`
|
||||
|
||||
// Enables and configures TLS client authentication.
|
||||
ClientAuthentication *ClientAuthentication `json:"client_authentication,omitempty"`
|
||||
|
||||
|
@ -419,6 +425,7 @@ type ClientAuthentication struct {
|
|||
// }
|
||||
// trusted_leaf_cert <base64_der>
|
||||
// trusted_leaf_cert_file <filename>
|
||||
// verifier <module>
|
||||
// }
|
||||
//
|
||||
// If `mode` is not provided, it defaults to `require_and_verify` if any of the following are provided:
|
||||
|
@ -442,6 +449,7 @@ func (ca *ClientAuthentication) UnmarshalCaddyfile(d *caddyfile.Dispenser) error
|
|||
return d.ArgErr()
|
||||
}
|
||||
case "trusted_ca_cert":
|
||||
caddy.Log().Warn("The 'trusted_ca_cert' field is deprecated. Use the 'trust_pool' field instead.")
|
||||
if len(ca.CARaw) != 0 {
|
||||
return d.Err("cannot specify both 'trust_pool' and 'trusted_ca_cert' or 'trusted_ca_cert_file'")
|
||||
}
|
||||
|
@ -455,6 +463,7 @@ func (ca *ClientAuthentication) UnmarshalCaddyfile(d *caddyfile.Dispenser) error
|
|||
}
|
||||
ca.TrustedLeafCerts = append(ca.TrustedLeafCerts, d.Val())
|
||||
case "trusted_ca_cert_file":
|
||||
caddy.Log().Warn("The 'trusted_ca_cert_file' field is deprecated. Use the 'trust_pool' field instead.")
|
||||
if len(ca.CARaw) != 0 {
|
||||
return d.Err("cannot specify both 'trust_pool' and 'trusted_ca_cert' or 'trusted_ca_cert_file'")
|
||||
}
|
||||
|
@ -508,7 +517,7 @@ func (ca *ClientAuthentication) UnmarshalCaddyfile(d *caddyfile.Dispenser) error
|
|||
|
||||
_, ok := unm.(ClientCertificateVerifier)
|
||||
if !ok {
|
||||
return d.Errf("module '%s' is not a caddytls.ClientCertificatVerifier", modID)
|
||||
return d.Errf("module '%s' is not a caddytls.ClientCertificateVerifier", modID)
|
||||
}
|
||||
ca.VerifiersRaw = append(ca.VerifiersRaw, caddyconfig.JSONModuleObject(unm, "verifier", vType, nil))
|
||||
default:
|
||||
|
|
|
@ -94,7 +94,7 @@ func (iss *InternalIssuer) Provision(ctx caddy.Context) error {
|
|||
}
|
||||
|
||||
// IssuerKey returns the unique issuer key for the
|
||||
// confgured CA endpoint.
|
||||
// configured CA endpoint.
|
||||
func (iss InternalIssuer) IssuerKey() string {
|
||||
return iss.ca.ID
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ func (iss InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateReques
|
|||
)
|
||||
}
|
||||
|
||||
certChain, err := auth.Sign(csr, provisioner.SignOptions{}, customCertLifetime(caddy.Duration(lifetime)))
|
||||
certChain, err := auth.SignWithContext(ctx, csr, provisioner.SignOptions{}, customCertLifetime(caddy.Duration(lifetime)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ func (m MatchRemoteIP) Match(hello *tls.ClientHelloInfo) bool {
|
|||
}
|
||||
ipAddr, err := netip.ParseAddr(ipStr)
|
||||
if err != nil {
|
||||
m.logger.Error("invalid client IP addresss", zap.String("ip", ipStr))
|
||||
m.logger.Error("invalid client IP address", zap.String("ip", ipStr))
|
||||
return false
|
||||
}
|
||||
return (len(m.cidrs) == 0 || m.matches(ipAddr, m.cidrs)) &&
|
||||
|
@ -185,7 +185,7 @@ func (m MatchLocalIP) Match(hello *tls.ClientHelloInfo) bool {
|
|||
}
|
||||
ipAddr, err := netip.ParseAddr(ipStr)
|
||||
if err != nil {
|
||||
m.logger.Error("invalid local IP addresss", zap.String("ip", ipStr))
|
||||
m.logger.Error("invalid local IP address", zap.String("ip", ipStr))
|
||||
return false
|
||||
}
|
||||
return (len(m.cidrs) == 0 || m.matches(ipAddr, m.cidrs))
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -117,6 +118,17 @@ func (PermissionByHTTP) CaddyModule() caddy.ModuleInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (p *PermissionByHTTP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
if !d.Next() {
|
||||
return nil
|
||||
}
|
||||
if !d.AllArgs(&p.Endpoint) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PermissionByHTTP) Provision(ctx caddy.Context) error {
|
||||
p.logger = ctx.Logger()
|
||||
p.replacer = caddy.NewReplacer()
|
||||
|
|
|
@ -81,6 +81,16 @@ type TLS struct {
|
|||
// EXPERIMENTAL. Subject to change.
|
||||
DisableOCSPStapling bool `json:"disable_ocsp_stapling,omitempty"`
|
||||
|
||||
// Disables checks in certmagic that the configured storage is ready
|
||||
// and able to handle writing new content to it. These checks are
|
||||
// intended to prevent information loss (newly issued certificates), but
|
||||
// can be expensive on the storage.
|
||||
//
|
||||
// Disabling these checks should only be done when the storage
|
||||
// can be trusted to have enough capacity and no other problems.
|
||||
// EXPERIMENTAL. Subject to change.
|
||||
DisableStorageCheck bool `json:"disable_storage_check,omitempty"`
|
||||
|
||||
certificateLoaders []CertificateLoader
|
||||
automateNames []string
|
||||
ctx caddy.Context
|
||||
|
@ -91,7 +101,8 @@ type TLS struct {
|
|||
|
||||
// set of subjects with managed certificates,
|
||||
// and hashes of manually-loaded certificates
|
||||
managing, loaded map[string]struct{}
|
||||
// (managing's value is an optional issuer key, for distinction)
|
||||
managing, loaded map[string]string
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
|
@ -112,7 +123,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
|||
t.ctx = ctx
|
||||
t.logger = ctx.Logger()
|
||||
repl := caddy.NewReplacer()
|
||||
t.managing, t.loaded = make(map[string]struct{}), make(map[string]struct{})
|
||||
t.managing, t.loaded = make(map[string]string), make(map[string]string)
|
||||
|
||||
// set up a new certificate cache; this (re)loads all certificates
|
||||
cacheOpts := certmagic.CacheOptions{
|
||||
|
@ -254,6 +265,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
|||
OCSP: certmagic.OCSPConfig{
|
||||
DisableStapling: t.DisableOCSPStapling,
|
||||
},
|
||||
DisableStorageCheck: t.DisableStorageCheck,
|
||||
})
|
||||
certCacheMu.RUnlock()
|
||||
for _, loader := range t.certificateLoaders {
|
||||
|
@ -266,7 +278,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("caching unmanaged certificate: %v", err)
|
||||
}
|
||||
t.loaded[hash] = struct{}{}
|
||||
t.loaded[hash] = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,16 +364,33 @@ func (t *TLS) Cleanup() error {
|
|||
// if a new TLS app was loaded, remove certificates from the cache that are no longer
|
||||
// being managed or loaded by the new config; if there is no more TLS app running,
|
||||
// then stop cert maintenance and let the cert cache be GC'ed
|
||||
if nextTLS := caddy.ActiveContext().AppIfConfigured("tls"); nextTLS != nil {
|
||||
if nextTLS, err := caddy.ActiveContext().AppIfConfigured("tls"); err == nil && nextTLS != nil {
|
||||
nextTLSApp := nextTLS.(*TLS)
|
||||
|
||||
// compute which certificates were managed or loaded into the cert cache by this
|
||||
// app instance (which is being stopped) that are not managed or loaded by the
|
||||
// new app instance (which just started), and remove them from the cache
|
||||
var noLongerManaged, noLongerLoaded []string
|
||||
for subj := range t.managing {
|
||||
if _, ok := nextTLSApp.managing[subj]; !ok {
|
||||
noLongerManaged = append(noLongerManaged, subj)
|
||||
var noLongerManaged []certmagic.SubjectIssuer
|
||||
var reManage, noLongerLoaded []string
|
||||
for subj, currentIssuerKey := range t.managing {
|
||||
// It's a bit nuanced: managed certs can sometimes be different enough that we have to
|
||||
// swap them out for a different one, even if they are for the same subject/domain.
|
||||
// We consider "private" certs (internal CA/locally-trusted/etc) to be significantly
|
||||
// distinct from "public" certs (production CAs/globally-trusted/etc) because of the
|
||||
// implications when it comes to actual deployments: switching between an internal CA
|
||||
// and a production CA, for example, is quite significant. Switching from one public CA
|
||||
// to another, however, is not, and for our purposes we consider those to be the same.
|
||||
// Anyway, if the next TLS app does not manage a cert for this name at all, definitely
|
||||
// remove it from the cache. But if it does, and it's not the same kind of issuer/CA
|
||||
// as we have, also remove it, so that it can swap it out for the right one.
|
||||
if nextIssuerKey, ok := nextTLSApp.managing[subj]; !ok || nextIssuerKey != currentIssuerKey {
|
||||
// next app is not managing a cert for this domain at all or is using a different issuer, so remove it
|
||||
noLongerManaged = append(noLongerManaged, certmagic.SubjectIssuer{Subject: subj, IssuerKey: currentIssuerKey})
|
||||
|
||||
// then, if the next app is managing a cert for this name, but with a different issuer, re-manage it
|
||||
if ok && nextIssuerKey != currentIssuerKey {
|
||||
reManage = append(reManage, subj)
|
||||
}
|
||||
}
|
||||
}
|
||||
for hash := range t.loaded {
|
||||
|
@ -370,10 +399,19 @@ func (t *TLS) Cleanup() error {
|
|||
}
|
||||
}
|
||||
|
||||
// remove the certs
|
||||
certCacheMu.RLock()
|
||||
certCache.RemoveManaged(noLongerManaged)
|
||||
certCache.Remove(noLongerLoaded)
|
||||
certCacheMu.RUnlock()
|
||||
|
||||
// give the new TLS app a "kick" to manage certs that it is configured for
|
||||
// with its own configuration instead of the one we just evicted
|
||||
if err := nextTLSApp.Manage(reManage); err != nil {
|
||||
t.logger.Error("re-managing unloaded certificates with new config",
|
||||
zap.Strings("subjects", reManage),
|
||||
zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
// no more TLS app running, so delete in-memory cert cache
|
||||
certCache.Stop()
|
||||
|
@ -407,7 +445,20 @@ func (t *TLS) Manage(names []string) error {
|
|||
return fmt.Errorf("automate: manage %v: %v", names, err)
|
||||
}
|
||||
for _, name := range names {
|
||||
t.managing[name] = struct{}{}
|
||||
// certs that are issued solely by our internal issuer are a little bit of
|
||||
// a special case: if you have an initial config that manages example.com
|
||||
// using internal CA, then after testing it you switch to a production CA,
|
||||
// you wouldn't want to keep using the same self-signed cert, obviously;
|
||||
// so we differentiate these by associating the subject with its issuer key;
|
||||
// we do this because CertMagic has no notion of "InternalIssuer" like we
|
||||
// do, so we have to do this logic ourselves
|
||||
var issuerKey string
|
||||
if len(ap.Issuers) == 1 {
|
||||
if intIss, ok := ap.Issuers[0].(*InternalIssuer); ok && intIss != nil {
|
||||
issuerKey = intIss.IssuerKey()
|
||||
}
|
||||
}
|
||||
t.managing[name] = issuerKey
|
||||
}
|
||||
}
|
||||
|
||||
|
|
107
replacer.go
107
replacer.go
|
@ -16,6 +16,7 @@ package caddy
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -24,6 +25,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// NewReplacer returns a new Replacer.
|
||||
|
@ -32,9 +35,10 @@ func NewReplacer() *Replacer {
|
|||
static: make(map[string]any),
|
||||
mapMutex: &sync.RWMutex{},
|
||||
}
|
||||
rep.providers = []ReplacerFunc{
|
||||
globalDefaultReplacements,
|
||||
rep.fromStatic,
|
||||
rep.providers = []replacementProvider{
|
||||
globalDefaultReplacementProvider{},
|
||||
fileReplacementProvider{},
|
||||
ReplacerFunc(rep.fromStatic),
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
@ -46,8 +50,8 @@ func NewEmptyReplacer() *Replacer {
|
|||
static: make(map[string]any),
|
||||
mapMutex: &sync.RWMutex{},
|
||||
}
|
||||
rep.providers = []ReplacerFunc{
|
||||
rep.fromStatic,
|
||||
rep.providers = []replacementProvider{
|
||||
ReplacerFunc(rep.fromStatic),
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
@ -56,10 +60,25 @@ func NewEmptyReplacer() *Replacer {
|
|||
// A default/empty Replacer is not valid;
|
||||
// use NewReplacer to make one.
|
||||
type Replacer struct {
|
||||
providers []ReplacerFunc
|
||||
providers []replacementProvider
|
||||
static map[string]any
|
||||
mapMutex *sync.RWMutex
|
||||
}
|
||||
|
||||
static map[string]any
|
||||
mapMutex *sync.RWMutex
|
||||
// WithoutFile returns a copy of the current Replacer
|
||||
// without support for the {file.*} placeholder, which
|
||||
// may be unsafe in some contexts.
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
func (r *Replacer) WithoutFile() *Replacer {
|
||||
rep := &Replacer{static: r.static}
|
||||
for _, v := range r.providers {
|
||||
if _, ok := v.(fileReplacementProvider); ok {
|
||||
continue
|
||||
}
|
||||
rep.providers = append(rep.providers, v)
|
||||
}
|
||||
return rep
|
||||
}
|
||||
|
||||
// Map adds mapFunc to the list of value providers.
|
||||
|
@ -79,7 +98,7 @@ func (r *Replacer) Set(variable string, value any) {
|
|||
// the value and whether the variable was known.
|
||||
func (r *Replacer) Get(variable string) (any, bool) {
|
||||
for _, mapFunc := range r.providers {
|
||||
if val, ok := mapFunc(variable); ok {
|
||||
if val, ok := mapFunc.replace(variable); ok {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
|
@ -298,14 +317,52 @@ func ToString(val any) string {
|
|||
}
|
||||
}
|
||||
|
||||
// ReplacerFunc is a function that returns a replacement
|
||||
// for the given key along with true if the function is able
|
||||
// to service that key (even if the value is blank). If the
|
||||
// function does not recognize the key, false should be
|
||||
// returned.
|
||||
// ReplacerFunc is a function that returns a replacement for the
|
||||
// given key along with true if the function is able to service
|
||||
// that key (even if the value is blank). If the function does
|
||||
// not recognize the key, false should be returned.
|
||||
type ReplacerFunc func(key string) (any, bool)
|
||||
|
||||
func globalDefaultReplacements(key string) (any, bool) {
|
||||
func (f ReplacerFunc) replace(key string) (any, bool) {
|
||||
return f(key)
|
||||
}
|
||||
|
||||
// replacementProvider is a type that can provide replacements
|
||||
// for placeholders. Allows for type assertion to determine
|
||||
// which type of provider it is.
|
||||
type replacementProvider interface {
|
||||
replace(key string) (any, bool)
|
||||
}
|
||||
|
||||
// fileReplacementsProvider handles {file.*} replacements,
|
||||
// reading a file from disk and replacing with its contents.
|
||||
type fileReplacementProvider struct{}
|
||||
|
||||
func (f fileReplacementProvider) replace(key string) (any, bool) {
|
||||
if !strings.HasPrefix(key, filePrefix) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
filename := key[len(filePrefix):]
|
||||
maxSize := 1024 * 1024
|
||||
body, err := readFileIntoBuffer(filename, maxSize)
|
||||
if err != nil {
|
||||
wd, _ := os.Getwd()
|
||||
Log().Error("placeholder: failed to read file",
|
||||
zap.String("file", filename),
|
||||
zap.String("working_dir", wd),
|
||||
zap.Error(err))
|
||||
return nil, true
|
||||
}
|
||||
return string(body), true
|
||||
}
|
||||
|
||||
// globalDefaultReplacementsProvider handles replacements
|
||||
// that can be used in any context, such as system variables,
|
||||
// time, or environment variables.
|
||||
type globalDefaultReplacementProvider struct{}
|
||||
|
||||
func (f globalDefaultReplacementProvider) replace(key string) (any, bool) {
|
||||
// check environment variable
|
||||
const envPrefix = "env."
|
||||
if strings.HasPrefix(key, envPrefix) {
|
||||
|
@ -347,6 +404,24 @@ func globalDefaultReplacements(key string) (any, bool) {
|
|||
return nil, false
|
||||
}
|
||||
|
||||
// readFileIntoBuffer reads the file at filePath into a size limited buffer.
|
||||
func readFileIntoBuffer(filename string, size int) ([]byte, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buffer := make([]byte, size)
|
||||
n, err := file.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// slice the buffer to the actual size
|
||||
return buffer[:n], nil
|
||||
}
|
||||
|
||||
// ReplacementFunc is a function that is called when a
|
||||
// replacement is being performed. It receives the
|
||||
// variable (i.e. placeholder name) and the value that
|
||||
|
@ -363,3 +438,5 @@ var nowFunc = time.Now
|
|||
const ReplacerCtxKey CtxKey = "replacer"
|
||||
|
||||
const phOpen, phClose, phEscape = '{', '}', '\\'
|
||||
|
||||
const filePrefix = "file."
|
||||
|
|
146
replacer_test.go
146
replacer_test.go
|
@ -240,9 +240,9 @@ func TestReplacerSet(t *testing.T) {
|
|||
func TestReplacerReplaceKnown(t *testing.T) {
|
||||
rep := Replacer{
|
||||
mapMutex: &sync.RWMutex{},
|
||||
providers: []ReplacerFunc{
|
||||
providers: []replacementProvider{
|
||||
// split our possible vars to two functions (to test if both functions are called)
|
||||
func(key string) (val any, ok bool) {
|
||||
ReplacerFunc(func(key string) (val any, ok bool) {
|
||||
switch key {
|
||||
case "test1":
|
||||
return "val1", true
|
||||
|
@ -255,8 +255,8 @@ func TestReplacerReplaceKnown(t *testing.T) {
|
|||
default:
|
||||
return "NOOO", false
|
||||
}
|
||||
},
|
||||
func(key string) (val any, ok bool) {
|
||||
}),
|
||||
ReplacerFunc(func(key string) (val any, ok bool) {
|
||||
switch key {
|
||||
case "1":
|
||||
return "test-123", true
|
||||
|
@ -267,7 +267,7 @@ func TestReplacerReplaceKnown(t *testing.T) {
|
|||
default:
|
||||
return "NOOO", false
|
||||
}
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -372,53 +372,99 @@ func TestReplacerMap(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestReplacerNew(t *testing.T) {
|
||||
rep := NewReplacer()
|
||||
repl := NewReplacer()
|
||||
|
||||
if len(rep.providers) != 2 {
|
||||
t.Errorf("Expected providers length '%v' got length '%v'", 2, len(rep.providers))
|
||||
} else {
|
||||
// test if default global replacements are added as the first provider
|
||||
hostname, _ := os.Hostname()
|
||||
wd, _ := os.Getwd()
|
||||
os.Setenv("CADDY_REPLACER_TEST", "envtest")
|
||||
defer os.Setenv("CADDY_REPLACER_TEST", "")
|
||||
if len(repl.providers) != 3 {
|
||||
t.Errorf("Expected providers length '%v' got length '%v'", 3, len(repl.providers))
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
variable string
|
||||
value string
|
||||
}{
|
||||
{
|
||||
variable: "system.hostname",
|
||||
value: hostname,
|
||||
},
|
||||
{
|
||||
variable: "system.slash",
|
||||
value: string(filepath.Separator),
|
||||
},
|
||||
{
|
||||
variable: "system.os",
|
||||
value: runtime.GOOS,
|
||||
},
|
||||
{
|
||||
variable: "system.arch",
|
||||
value: runtime.GOARCH,
|
||||
},
|
||||
{
|
||||
variable: "system.wd",
|
||||
value: wd,
|
||||
},
|
||||
{
|
||||
variable: "env.CADDY_REPLACER_TEST",
|
||||
value: "envtest",
|
||||
},
|
||||
} {
|
||||
if val, ok := rep.providers[0](tc.variable); ok {
|
||||
if val != tc.value {
|
||||
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Expected key '%s' to be recognized by first provider", tc.variable)
|
||||
// test if default global replacements are added as the first provider
|
||||
hostname, _ := os.Hostname()
|
||||
wd, _ := os.Getwd()
|
||||
os.Setenv("CADDY_REPLACER_TEST", "envtest")
|
||||
defer os.Setenv("CADDY_REPLACER_TEST", "")
|
||||
|
||||
for _, tc := range []struct {
|
||||
variable string
|
||||
value string
|
||||
}{
|
||||
{
|
||||
variable: "system.hostname",
|
||||
value: hostname,
|
||||
},
|
||||
{
|
||||
variable: "system.slash",
|
||||
value: string(filepath.Separator),
|
||||
},
|
||||
{
|
||||
variable: "system.os",
|
||||
value: runtime.GOOS,
|
||||
},
|
||||
{
|
||||
variable: "system.arch",
|
||||
value: runtime.GOARCH,
|
||||
},
|
||||
{
|
||||
variable: "system.wd",
|
||||
value: wd,
|
||||
},
|
||||
{
|
||||
variable: "env.CADDY_REPLACER_TEST",
|
||||
value: "envtest",
|
||||
},
|
||||
} {
|
||||
if val, ok := repl.providers[0].replace(tc.variable); ok {
|
||||
if val != tc.value {
|
||||
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Expected key '%s' to be recognized by first provider", tc.variable)
|
||||
}
|
||||
}
|
||||
|
||||
// test if file provider is added as the second provider
|
||||
for _, tc := range []struct {
|
||||
variable string
|
||||
value string
|
||||
}{
|
||||
{
|
||||
variable: "file.caddytest/integration/testdata/foo.txt",
|
||||
value: "foo",
|
||||
},
|
||||
} {
|
||||
if val, ok := repl.providers[1].replace(tc.variable); ok {
|
||||
if val != tc.value {
|
||||
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Expected key '%s' to be recognized by second provider", tc.variable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplacerNewWithoutFile(t *testing.T) {
|
||||
repl := NewReplacer().WithoutFile()
|
||||
|
||||
for _, tc := range []struct {
|
||||
variable string
|
||||
value string
|
||||
notFound bool
|
||||
}{
|
||||
{
|
||||
variable: "file.caddytest/integration/testdata/foo.txt",
|
||||
notFound: true,
|
||||
},
|
||||
{
|
||||
variable: "system.os",
|
||||
value: runtime.GOOS,
|
||||
},
|
||||
} {
|
||||
if val, ok := repl.Get(tc.variable); ok && !tc.notFound {
|
||||
if val != tc.value {
|
||||
t.Errorf("Expected value '%s' for key '%s' got '%s'", tc.value, tc.variable, val)
|
||||
}
|
||||
} else if !tc.notFound {
|
||||
t.Errorf("Expected key '%s' to be recognized", tc.variable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -464,7 +510,7 @@ func BenchmarkReplacer(b *testing.B) {
|
|||
|
||||
func testReplacer() Replacer {
|
||||
return Replacer{
|
||||
providers: make([]ReplacerFunc, 0),
|
||||
providers: make([]replacementProvider, 0),
|
||||
static: make(map[string]any),
|
||||
mapMutex: &sync.RWMutex{},
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue