mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-25 16:22:36 -04:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e38228b5f1 | |||
| 7dedd1486c | |||
| 7586e68e27 | |||
| 0c7c91a447 | |||
| 1a3e900b35 | |||
| 0722cf6fd8 | |||
| 8e2dd5079c | |||
| 5f44ea0748 | |||
| c8e4ac2c8c | |||
| 7dcc041eec | |||
| ca0ca67fbd | |||
| 92b62004eb | |||
| 6c23ec2f3c | |||
| 5de1565ff6 | |||
| d7834676aa | |||
| 4f50458866 | |||
| ea4ee3ae5d | |||
| 30b80bece8 | |||
| 7a630f2910 | |||
| 62e9c05264 | |||
| 6f6771aa1d | |||
| acf8d6a1ae |
@@ -127,10 +127,9 @@ func Load(cfgJSON []byte, forceReload bool) error {
|
||||
zap.Error(notifyErr),
|
||||
zap.String("reload_err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := notify.Ready(); err != nil {
|
||||
Log().Error("unable to notify to service manager of ready state", zap.Error(err))
|
||||
if notifyErr := notify.Ready(); notifyErr != nil {
|
||||
Log().Error("unable to notify to service manager of ready state", zap.Error(notifyErr))
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -63,8 +63,33 @@ func Format(input []byte) []byte {
|
||||
heredocClosingMarker []rune
|
||||
|
||||
nesting int // indentation level
|
||||
|
||||
currentToken strings.Builder
|
||||
currentLineFirstToken string
|
||||
previousLineWasTopLevelImport bool
|
||||
openBraceOwnLine bool
|
||||
)
|
||||
|
||||
finishToken := func() {
|
||||
if currentToken.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if currentLineFirstToken == "" {
|
||||
currentLineFirstToken = currentToken.String()
|
||||
}
|
||||
currentToken.Reset()
|
||||
}
|
||||
|
||||
finishLine := func() {
|
||||
finishToken()
|
||||
if currentLineFirstToken != "" {
|
||||
previousLineWasTopLevelImport = nesting == 0 && currentLineFirstToken == "import"
|
||||
} else if !openBrace || !openBraceOwnLine || openBraceWritten {
|
||||
previousLineWasTopLevelImport = false
|
||||
}
|
||||
currentLineFirstToken = ""
|
||||
}
|
||||
|
||||
write := func(ch rune) {
|
||||
out.WriteRune(ch)
|
||||
last = ch
|
||||
@@ -220,9 +245,11 @@ func Format(input []byte) []byte {
|
||||
}
|
||||
|
||||
if unicode.IsSpace(ch) {
|
||||
finishToken()
|
||||
space = true
|
||||
heredocEscaped = false
|
||||
if ch == '\n' {
|
||||
finishLine()
|
||||
newLines++
|
||||
}
|
||||
continue
|
||||
@@ -249,13 +276,19 @@ func Format(input []byte) []byte {
|
||||
}
|
||||
|
||||
openBrace = false
|
||||
if beginningOfLine {
|
||||
if openBraceOwnLine && previousLineWasTopLevelImport {
|
||||
if last != '\n' {
|
||||
nextLine()
|
||||
}
|
||||
indent()
|
||||
} else if beginningOfLine {
|
||||
indent()
|
||||
} else if !openBraceSpace || !unicode.IsSpace(last) {
|
||||
write(' ')
|
||||
}
|
||||
write('{')
|
||||
openBraceWritten = true
|
||||
openBraceOwnLine = false
|
||||
nextLine()
|
||||
newLines = 0
|
||||
// prevent infinite nesting from ridiculous inputs (issue #4169)
|
||||
@@ -266,8 +299,10 @@ func Format(input []byte) []byte {
|
||||
|
||||
switch {
|
||||
case ch == '{':
|
||||
finishToken()
|
||||
openBrace = true
|
||||
openBraceSpace = spacePrior && !beginningOfLine
|
||||
openBraceOwnLine = newLines > 0
|
||||
if openBraceSpace && newLines == 0 {
|
||||
write(' ')
|
||||
}
|
||||
@@ -275,11 +310,13 @@ func Format(input []byte) []byte {
|
||||
if quotes == "`" {
|
||||
write('{')
|
||||
openBraceWritten = true
|
||||
openBraceOwnLine = false
|
||||
continue
|
||||
}
|
||||
continue
|
||||
|
||||
case ch == '}' && (spacePrior || !openBrace):
|
||||
finishToken()
|
||||
if quotes == "`" {
|
||||
write('}')
|
||||
continue
|
||||
@@ -324,6 +361,7 @@ func Format(input []byte) []byte {
|
||||
space = true
|
||||
}
|
||||
|
||||
currentToken.WriteRune(ch)
|
||||
write(ch)
|
||||
|
||||
beginningOfLine = false
|
||||
|
||||
@@ -475,6 +475,21 @@ Hope this helps.` + "`" + `
|
||||
}`,
|
||||
expect: "https://localhost:8953 {\n\trespond `Here are some random numbers:\n\n{{randNumeric 16}}\n\nHope this helps.`\n}",
|
||||
},
|
||||
{
|
||||
description: "imports before global options block keep standalone brace",
|
||||
input: `import ./conf.d/matcher_my_subnet.caddy
|
||||
import ./conf.d/matcher_not_my_subnet.caddy
|
||||
{
|
||||
order crowdsec first
|
||||
order appsec after crowdsec
|
||||
}`,
|
||||
expect: `import ./conf.d/matcher_my_subnet.caddy
|
||||
import ./conf.d/matcher_not_my_subnet.caddy
|
||||
{
|
||||
order crowdsec first
|
||||
order appsec after crowdsec
|
||||
}`,
|
||||
},
|
||||
} {
|
||||
// the formatter should output a trailing newline,
|
||||
// even if the tests aren't written to expect that
|
||||
|
||||
@@ -550,7 +550,11 @@ func (p *parser) doImport(nesting int) error {
|
||||
}
|
||||
|
||||
if foundBlockDirective {
|
||||
tokensCopy = append(tokensCopy, tokensToAdd...)
|
||||
if maybeSnippet {
|
||||
tokensCopy = append(tokensCopy, token)
|
||||
} else {
|
||||
tokensCopy = append(tokensCopy, tokensToAdd...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -682,11 +686,28 @@ func (p *parser) directive() error {
|
||||
// a opening curly brace. It does NOT advance the token.
|
||||
func (p *parser) openCurlyBrace() error {
|
||||
if p.Val() != "{" {
|
||||
if p.valLooksLikeGlobalOptionsAfterImportedSnippets() {
|
||||
return p.Err("global options block must appear before import directives; move the global options block to the top of the Caddyfile")
|
||||
}
|
||||
return p.SyntaxErr("{")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) valLooksLikeGlobalOptionsAfterImportedSnippets() bool {
|
||||
if p.Val() != "import" || len(p.block.Keys) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, key := range p.block.Keys {
|
||||
if !strings.HasPrefix(key.Text, "(") || !strings.HasSuffix(key.Text, ")") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// closeCurlyBrace expects the current token to be
|
||||
// a closing curly brace. This acts like an assertion
|
||||
// because it returns an error if the token is not
|
||||
|
||||
@@ -930,6 +930,107 @@ func TestAcceptSiteImportWithBraces(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobalOptionsAfterImportedSnippetsGivesHelpfulError(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
importFile1 := filepath.Join(tempDir, "matcher_snippet_1.caddy")
|
||||
importFile2 := filepath.Join(tempDir, "matcher_snippet_2.caddy")
|
||||
|
||||
err := os.WriteFile(importFile1, []byte(`(matcher1)`), 0o644)
|
||||
if err != nil {
|
||||
t.Fatalf("writing first import file: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(importFile2, []byte(`(matcher2)`), 0o644)
|
||||
if err != nil {
|
||||
t.Fatalf("writing second import file: %v", err)
|
||||
}
|
||||
|
||||
_, err = Parse("Testfile", []byte(`import `+importFile1+`
|
||||
import `+importFile2+`
|
||||
{
|
||||
debug
|
||||
}`))
|
||||
if err == nil {
|
||||
t.Fatal("Expected an error, but got nil")
|
||||
}
|
||||
|
||||
expected := "global options block must appear before import directives; move the global options block to the top of the Caddyfile"
|
||||
if !strings.HasPrefix(err.Error(), expected) {
|
||||
t.Errorf("Expected error to start with '%s' but got '%v'", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedSnippetDefinitionRetainsBlockPlaceholder(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
importFile := filepath.Join(tempDir, "snippets.caddy")
|
||||
|
||||
err := os.WriteFile(importFile, []byte(`
|
||||
(site) {
|
||||
http://{args[0]} {
|
||||
respond "before"
|
||||
{block}
|
||||
respond "after"
|
||||
}
|
||||
}
|
||||
`), 0o644)
|
||||
if err != nil {
|
||||
t.Fatalf("writing imported snippet file: %v", err)
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
input string
|
||||
expectedDirectives []string
|
||||
}{
|
||||
{
|
||||
name: "with nested block",
|
||||
input: `
|
||||
import ` + importFile + `
|
||||
|
||||
import site example.com {
|
||||
redir https://example.net
|
||||
}
|
||||
`,
|
||||
expectedDirectives: []string{"respond", "redir", "respond"},
|
||||
},
|
||||
{
|
||||
name: "without nested block",
|
||||
input: `
|
||||
import ` + importFile + `
|
||||
|
||||
import site example.com
|
||||
`,
|
||||
expectedDirectives: []string{"respond", "respond"},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := testParser(tc.input)
|
||||
blocks, err := p.parseAll()
|
||||
if err != nil {
|
||||
t.Fatalf("parseAll: %v", err)
|
||||
}
|
||||
|
||||
if len(blocks) != 1 {
|
||||
t.Fatalf("expected exactly one server block, got %d", len(blocks))
|
||||
}
|
||||
|
||||
if actual := blocks[0].GetKeysText(); len(actual) != 1 || actual[0] != "http://example.com" {
|
||||
t.Fatalf("expected server block key http://example.com, got %v", actual)
|
||||
}
|
||||
|
||||
if len(blocks[0].Segments) != len(tc.expectedDirectives) {
|
||||
t.Fatalf("expected %d segments, got %d", len(tc.expectedDirectives), len(blocks[0].Segments))
|
||||
}
|
||||
|
||||
for i, directive := range tc.expectedDirectives {
|
||||
if actual := blocks[0].Segments[i].Directive(); actual != directive {
|
||||
t.Fatalf("segment %d: expected directive %q, got %q", i, directive, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testParser(input string) parser {
|
||||
return parser{Dispenser: NewTestDispenser(input)}
|
||||
}
|
||||
|
||||
@@ -668,6 +668,8 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
// store the unmatched root in block state so sibling directives can access it
|
||||
h.BlockState["root"] = h.Val()
|
||||
return h.NewRoute(nil, caddyhttp.VarsMiddleware{"root": h.Val()}), nil
|
||||
}
|
||||
|
||||
@@ -682,6 +684,10 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
// store the unmatched root in state so sibling/child directives can access it
|
||||
if userMatcherSet == nil {
|
||||
h.BlockState["root"] = h.Val()
|
||||
}
|
||||
// make the route with the matcher
|
||||
return h.NewRoute(userMatcherSet, caddyhttp.VarsMiddleware{"root": h.Val()}), nil
|
||||
}
|
||||
|
||||
@@ -202,7 +202,10 @@ func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) {
|
||||
type Helper struct {
|
||||
*caddyfile.Dispenser
|
||||
// State stores intermediate variables during caddyfile adaptation.
|
||||
State map[string]any
|
||||
State map[string]any
|
||||
// BlockState stores intermediate variables scoped to the current block.
|
||||
// It propagates down, but unlike state not back up from child to parent.
|
||||
BlockState map[string]any
|
||||
options map[string]any
|
||||
warnings *[]caddyconfig.Warning
|
||||
matcherDefs map[string]caddy.ModuleMap
|
||||
@@ -385,6 +388,11 @@ func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// clone BlockState once for the entire block so sibling directives
|
||||
// can share state, but changes don't leak to the parent scope
|
||||
subBlockState := make(map[string]any, len(h.BlockState))
|
||||
maps.Copy(subBlockState, h.BlockState)
|
||||
|
||||
// with matchers ready to go, evaluate each directive's segment
|
||||
for _, seg := range segments {
|
||||
dir := seg.Directive()
|
||||
@@ -396,6 +404,7 @@ func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
|
||||
subHelper := h
|
||||
subHelper.Dispenser = caddyfile.NewDispenser(seg)
|
||||
subHelper.matcherDefs = matcherDefs
|
||||
subHelper.BlockState = subBlockState
|
||||
|
||||
results, err := dirFunc(subHelper)
|
||||
if err != nil {
|
||||
|
||||
@@ -143,6 +143,7 @@ func (st ServerType) Setup(
|
||||
parentBlock: sb.block,
|
||||
groupCounter: gc,
|
||||
State: state,
|
||||
BlockState: state,
|
||||
}
|
||||
|
||||
results, err := dirFunc(h)
|
||||
@@ -504,6 +505,7 @@ func (ServerType) extractNamedRoutes(
|
||||
parentBlock: sb.block,
|
||||
groupCounter: gc,
|
||||
State: state,
|
||||
BlockState: state,
|
||||
}
|
||||
|
||||
handler, err := ParseSegmentAsSubroute(h)
|
||||
|
||||
@@ -698,14 +698,31 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
||||
emptyAPCount := 0
|
||||
origLenAPs := len(aps)
|
||||
// compute the number of empty policies (disregarding subjects) - see #4128
|
||||
// while we're at it,
|
||||
emptyAP := new(caddytls.AutomationPolicy)
|
||||
for i := 0; i < len(aps); i++ {
|
||||
emptyAP.SubjectsRaw = aps[i].SubjectsRaw
|
||||
emptyAP.ManagersRaw = nil
|
||||
if reflect.DeepEqual(aps[i], emptyAP) {
|
||||
// AP is empty
|
||||
emptyAPCount++
|
||||
if !automationPolicyHasAllPublicNames(aps[i]) {
|
||||
// if this automation policy has internal names, we might as well remove it
|
||||
// so auto-https can implicitly use the internal issuer
|
||||
|
||||
// see if this AP shadows something later
|
||||
shadowIdx := automationPolicyShadows(i, aps)
|
||||
emptyAP.SubjectsRaw = nil
|
||||
if shadowIdx >= 0 {
|
||||
emptyAP.SubjectsRaw = aps[shadowIdx].SubjectsRaw
|
||||
// allow the later policy, which is likely for a wildcard, to have cert
|
||||
// managers ("get_certificate"), since wildcards now cover specific
|
||||
// subdomains by default, when configured (see discussion in #7559)
|
||||
emptyAP.ManagersRaw = aps[shadowIdx].ManagersRaw
|
||||
}
|
||||
|
||||
// if this is the last AP, we can delete it, since auto-https should
|
||||
// pick it up; if it shadows something later that is also empty, we
|
||||
// can similarly delete this; but if it shadows something that is NOT
|
||||
// empty, we must not delete it since the shadowing has a purpose
|
||||
if i == len(aps)-1 || (shadowIdx >= 0 && reflect.DeepEqual(aps[shadowIdx], emptyAP)) {
|
||||
aps = slices.Delete(aps, i, i+1)
|
||||
i--
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ encode gzip zstd {
|
||||
|
||||
# Long way with a block for each encoding
|
||||
encode {
|
||||
zstd
|
||||
zstd {
|
||||
disable_checksum
|
||||
}
|
||||
gzip 5
|
||||
}
|
||||
|
||||
@@ -71,7 +73,9 @@ encode
|
||||
"gzip": {
|
||||
"level": 5
|
||||
},
|
||||
"zstd": {}
|
||||
"zstd": {
|
||||
"checksum": false
|
||||
}
|
||||
},
|
||||
"handler": "encode",
|
||||
"prefer": [
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
admin off
|
||||
auto_https off
|
||||
}
|
||||
|
||||
import testdata/issue_7557_invalid_subdirective_snippet.conf
|
||||
|
||||
:8080 {
|
||||
import test {
|
||||
this_is_nonsense
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
parsing caddyfile tokens for 'reverse_proxy': unrecognized subdirective this_is_nonsense
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
log {
|
||||
format journald {
|
||||
wrap console
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:80 {
|
||||
respond "Hello, World!"
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"encoder": {
|
||||
"format": "journald",
|
||||
"wrap": {
|
||||
"format": "console"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "Hello, World!",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
https://example.com {
|
||||
reverse_proxy https://localhost:54321 {
|
||||
stream_buffer_size 8KB
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"stream_buffer_size": 8000,
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"tls": {}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,11 +54,6 @@ b.com {
|
||||
"via": "http"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"subjects": [
|
||||
"b.com"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# example from https://github.com/caddyserver/caddy/issues/7559
|
||||
*.test.local {
|
||||
tls {
|
||||
get_certificate http http://cert-server:9000/certs
|
||||
}
|
||||
respond "wildcard"
|
||||
}
|
||||
|
||||
# certificate for this subdomain is covered by wildcard above
|
||||
subdomain.test.local {
|
||||
respond "subdomain"
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"subdomain.test.local"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "subdomain",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"*.test.local"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "wildcard",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"subjects": [
|
||||
"*.test.local"
|
||||
],
|
||||
"get_certificate": [
|
||||
{
|
||||
"url": "http://cert-server:9000/certs",
|
||||
"via": "http"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
localhost
|
||||
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
client_auth {
|
||||
mode request
|
||||
trust_pool combined {
|
||||
source inline {
|
||||
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||
}
|
||||
source file {
|
||||
pem_file ../caddy.ca.cer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"match": {
|
||||
"sni": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"client_authentication": {
|
||||
"ca": {
|
||||
"provider": "combined",
|
||||
"sources": [
|
||||
{
|
||||
"provider": "inline",
|
||||
"trusted_ca_certs": [
|
||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"pem_files": [
|
||||
"../caddy.ca.cer"
|
||||
],
|
||||
"provider": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mode": "request"
|
||||
}
|
||||
},
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
localhost
|
||||
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
client_auth {
|
||||
mode require_and_verify
|
||||
trust_pool combined {
|
||||
source inline {
|
||||
trust_der MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||
}
|
||||
source pki_root {
|
||||
authority local
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"match": {
|
||||
"sni": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"client_authentication": {
|
||||
"ca": {
|
||||
"provider": "combined",
|
||||
"sources": [
|
||||
{
|
||||
"provider": "inline",
|
||||
"trusted_ca_certs": [
|
||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||
]
|
||||
},
|
||||
{
|
||||
"authority": [
|
||||
"local"
|
||||
],
|
||||
"provider": "pki_root"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mode": "require_and_verify"
|
||||
}
|
||||
},
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
localhost
|
||||
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
client_auth {
|
||||
mode request
|
||||
trust_pool system
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"match": {
|
||||
"sni": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"client_authentication": {
|
||||
"ca": {
|
||||
"provider": "system"
|
||||
},
|
||||
"mode": "request"
|
||||
}
|
||||
},
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@ func TestForwardAuthCopyHeadersAuthResponseWins(t *testing.T) {
|
||||
// its own values. The backend must receive the auth service values.
|
||||
req, _ := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil)
|
||||
req.Header.Set("Authorization", "Bearer token123")
|
||||
req.Header.Set("X-User-Id", "forged-id") // must be overwritten
|
||||
req.Header.Set("X-User-Id", "forged-id") // must be overwritten
|
||||
req.Header.Set("X-User-Role", "forged-role") // must be overwritten
|
||||
tester.AssertResponse(req, http.StatusOK, "ok")
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -562,3 +567,129 @@ func TestReverseProxyHealthCheckUnixSocketWithoutPort(t *testing.T) {
|
||||
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
||||
}
|
||||
|
||||
func TestReverseProxyWebSocketUpgradeUnixSocket(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "*.sock")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary socket file: %v", err)
|
||||
}
|
||||
_ = os.Remove(f.Name())
|
||||
socketName := f.Name()
|
||||
|
||||
backend := http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path != "/ws" {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.EqualFold(req.Header.Get("Upgrade"), "websocket") ||
|
||||
!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
|
||||
http.Error(w, "missing websocket upgrade headers", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
wsKey := req.Header.Get("Sec-WebSocket-Key")
|
||||
if wsKey == "" {
|
||||
http.Error(w, "missing Sec-WebSocket-Key", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
http.Error(w, "hijacker not supported", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
conn, brw, err := hj.Hijack()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, _ = brw.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||
_, _ = brw.WriteString("Upgrade: websocket\r\n")
|
||||
_, _ = brw.WriteString("Connection: Upgrade\r\n")
|
||||
_, _ = brw.WriteString("Sec-WebSocket-Accept: " + computeWebSocketAccept(wsKey) + "\r\n")
|
||||
_, _ = brw.WriteString("\r\n")
|
||||
_ = brw.Flush()
|
||||
}),
|
||||
}
|
||||
|
||||
unixListener, err := net.Listen("unix", socketName)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to listen on unix socket: %v", err)
|
||||
}
|
||||
go backend.Serve(unixListener)
|
||||
t.Cleanup(func() {
|
||||
_ = backend.Close()
|
||||
_ = unixListener.Close()
|
||||
_ = os.Remove(socketName)
|
||||
})
|
||||
runtime.Gosched()
|
||||
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(fmt.Sprintf(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
grace_period 1ns
|
||||
}
|
||||
http://localhost:9080 {
|
||||
reverse_proxy unix/%s
|
||||
}
|
||||
`, socketName), "caddyfile")
|
||||
|
||||
conn, err := net.Dial("tcp", "127.0.0.1:9080")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial caddy listener: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
wsKey := "dGhlIHNhbXBsZSBub25jZQ=="
|
||||
request := strings.Join([]string{
|
||||
"GET /ws HTTP/1.1",
|
||||
"Host: localhost:9080",
|
||||
"Connection: Upgrade",
|
||||
"Upgrade: websocket",
|
||||
"Sec-WebSocket-Version: 13",
|
||||
"Sec-WebSocket-Key: " + wsKey,
|
||||
"",
|
||||
"",
|
||||
}, "\r\n")
|
||||
|
||||
if _, err := io.WriteString(conn, request); err != nil {
|
||||
t.Fatalf("failed to send websocket handshake request: %v", err)
|
||||
}
|
||||
|
||||
tpr := textproto.NewReader(bufio.NewReader(conn))
|
||||
statusLine, err := tpr.ReadLine()
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading handshake status line: %v", err)
|
||||
}
|
||||
if !strings.Contains(statusLine, "101") || !strings.Contains(strings.ToLower(statusLine), "switching protocols") {
|
||||
t.Fatalf("unexpected status line: %q", statusLine)
|
||||
}
|
||||
|
||||
headers, err := tpr.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading handshake headers: %v", err)
|
||||
}
|
||||
if !strings.EqualFold(headers.Get("Upgrade"), "websocket") {
|
||||
t.Fatalf("unexpected Upgrade header: %q", headers.Get("Upgrade"))
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(headers.Get("Connection")), "upgrade") {
|
||||
t.Fatalf("unexpected Connection header: %q", headers.Get("Connection"))
|
||||
}
|
||||
}
|
||||
|
||||
func computeWebSocketAccept(wsKey string) string {
|
||||
h := sha1.Sum([]byte(wsKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
|
||||
return base64.StdEncoding.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Used by import_block_snippet_invalid_subdirective.caddyfiletest
|
||||
|
||||
(test) {
|
||||
reverse_proxy {
|
||||
{block}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/DeRuina/timberjack v1.4.0
|
||||
github.com/DeRuina/timberjack v1.4.1
|
||||
github.com/KimMachineGun/automemlimit v0.7.5
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/alecthomas/chroma/v2 v2.23.1
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/cloudflare/circl v1.6.3
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/google/cel-go v0.27.0
|
||||
github.com/google/cel-go v0.28.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/klauspost/compress v1.18.5
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
@@ -30,20 +30,20 @@ require (
|
||||
github.com/tailscale/tscert v0.0.0-20251216020129-aea342f6d747
|
||||
github.com/yuin/goldmark v1.8.2
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.67.0
|
||||
go.opentelemetry.io/otel v1.42.0
|
||||
go.opentelemetry.io/otel/sdk v1.42.0
|
||||
go.step.sm/crypto v0.77.1
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.68.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.step.sm/crypto v0.77.2
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/zap v1.27.1
|
||||
go.uber.org/zap/exp v0.3.0
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20260323153451-8400f4a93807
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/term v0.41.0
|
||||
golang.org/x/term v0.42.0
|
||||
golang.org/x/time v0.15.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -62,13 +62,13 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.18.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/jackc/pgx/v5 v5.8.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
@@ -87,31 +87,31 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.18.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.43.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.43.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.43.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.65.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
google.golang.org/api v0.271.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/api v0.272.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
|
||||
)
|
||||
|
||||
@@ -163,16 +163,16 @@ require (
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/urfave/cli v1.22.17 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.42.0
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/sys v0.42.0
|
||||
golang.org/x/text v0.35.0
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/text v0.36.0
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
google.golang.org/grpc v1.80.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
)
|
||||
|
||||
@@ -28,8 +28,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DeRuina/timberjack v1.4.0 h1:Ipw9KjS/6K6A9D1xdhWebYJFqdQez5gXwfzmeKOroqE=
|
||||
github.com/DeRuina/timberjack v1.4.0/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
|
||||
github.com/DeRuina/timberjack v1.4.1 h1:JftM5HN/ITKehAXjtdbGqN5XZIS1biHm7VSjU0Qbtqg=
|
||||
github.com/DeRuina/timberjack v1.4.1/go.mod h1:RLoeQrwrCGIEF8gO5nV5b/gMD0QIy7bzQhBUgpp1EqE=
|
||||
github.com/KimMachineGun/automemlimit v0.7.5 h1:RkbaC0MwhjL1ZuBKunGDjE/ggwAX43DwZrJqVwyveTk=
|
||||
github.com/KimMachineGun/automemlimit v0.7.5/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
@@ -151,8 +151,8 @@ github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
|
||||
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -168,8 +168,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.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=
|
||||
github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw=
|
||||
github.com/google/cel-go v0.28.0 h1:KjSWstCpz/MN5t4a8gnGJNIYUsJRpdi/r97xWDphIQc=
|
||||
github.com/google/cel-go v0.28.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
|
||||
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=
|
||||
@@ -179,8 +179,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws=
|
||||
github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ=
|
||||
github.com/google/go-tpm-tools v0.4.8 h1:V4oIYyAD3BykOycwYQzO29WefDouQMTsYZqmG3HxOfM=
|
||||
github.com/google/go-tpm-tools v0.4.8/go.mod h1:4DfiOtiS1KppJjwf1+tqtW4K3PrCJjAAqFKj/TYTJKg=
|
||||
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/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
@@ -189,8 +189,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.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.18.0 h1:jxP5Uuo3bxm3M6gGtV94P4lliVetoCB4Wk2x8QA86LI=
|
||||
github.com/googleapis/gax-go/v2 v2.18.0/go.mod h1:uSzZN4a356eRG985CzJ3WfbFSpqkLTjsnhWGJR6EwrE=
|
||||
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
|
||||
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
@@ -373,66 +373,66 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 h1:w3zlHYETbDwXyWHZlyyR58ZC39XGi8rAhkBgUgJ9d5w=
|
||||
go.opentelemetry.io/contrib/bridges/prometheus v0.68.0/go.mod h1:GR/mClR2nn7vE8RLwxKjoBNg+QtgdDhRzxVa93koy5o=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0 h1:0D3GFvELGIwQGfC6agLsbrEYSGWZTRTxIXxcQUqrOuk=
|
||||
go.opentelemetry.io/contrib/exporters/autoexport v0.68.0/go.mod h1:DM2NV7Zb8CcGeVPt6glouY0FAiwZQ/iqgcWExhgWeN8=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.67.0 h1:XhcQRf4MeqwQw96FcnatDAj6gwE19SUrWZ1VwNg77iE=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.67.0/go.mod h1:7OK06SuNIBIlc5Uq3JGQEsKHuXw29t9OJemvDYyP1dk=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.42.0 h1:Kbr3xDxs6kcxp5ThXTKWK2OtwLhNoXBVtqguNYcsZL0=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.42.0/go.mod h1:Jzw9hZHtxdpCN7x8S17UH59X/EiFivp6VXLs9bdM1OQ=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.42.0 h1:B2Pew5ufEtgkjLF+tSkXjgYZXQr9m7aCm1wLKB0URbU=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.42.0/go.mod h1:iPgUcSEF5DORW6+yNbdw/YevUy+QqJ508ncjhrRSCjc=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 h1:jP8unWI6q5kcb3gpGLjKDGaUa+JW+nHKWvpS/q+YuWA=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.42.0/go.mod h1:xd89e/pUyPatUP1C4z1UknD9jHptESO99tWyvd4mWD4=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.42.0 h1:uQjD1NNqX1+DfcAoWParPt1egNg9vC9gH4xarJ9Khxo=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.42.0/go.mod h1:yw/c2TCmQLIv109HBOCn6NlJ8Dp7MNfjMcqQZRnAMmg=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0 h1:g0LRDXMX/G1SEZtK8zl8Chm4K6GBwRkjPKE36LxiTYs=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.64.0/go.mod h1:UrgcjnarfdlBDP3GjDIJWe6HTprwSazNjwsI+Ru6hro=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3HznyJlrfx9ge5oJvxxlGR+P/7s=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
|
||||
go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg=
|
||||
go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
||||
go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw=
|
||||
go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.step.sm/crypto v0.77.1 h1:4EEqfKdv0egQ1lqz2RhnU8Jv6QgXZfrgoxWMqJF9aDs=
|
||||
go.step.sm/crypto v0.77.1/go.mod h1:U/SsmEm80mNnfD5WIkbhuW/B1eFp3fgFvdXyDLpU1AQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.68.0 h1:wLGFvNBPqQhzBn0QRBZjrriH8lZ9gqtTz8ufHEjLg7k=
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.68.0/go.mod h1:evWK9nCqCzH8nhclTlpkdUzmxrmJQ2mrWCdKIvyOYec=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.43.0 h1:EwnsB3cXRLAh7/Nr/9rMuGw73nfb3z6uAvVDjRrbeUg=
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.43.0/go.mod h1:CJjTym6F87tEdm61Qvnz5xrV8vKlH4C92djiqcn62k8=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.43.0 h1:CETqV3QLLPTy5yNrqyMr41VnAOOD4lsRved7n4QG00A=
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.43.0/go.mod h1:Q4mCiCdziYzpNR0g+6UqVotAlCDZdzz6L8jwY4knOrw=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.43.0 h1:peiLMz1+aqJE+3L4mOVtR9wlmv+yh/JVYXCBjqmzJJE=
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.43.0/go.mod h1:Agvif+4A8p/3UtZzJ0MCcDEuQwgtrzM71DueU41DCs8=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.43.0 h1:Hh1HahlGc81AOE7siqi1tVOlbanY/UxMMWedpb0d5oQ=
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.43.0/go.mod h1:58MlyS7lghzYvAm5LN9gGmZpCMQEMB5vpZp9SRgOyE4=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE=
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/BUNCEgfrmfboztcS6PhvSo88HKM=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU=
|
||||
go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=
|
||||
go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=
|
||||
go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk=
|
||||
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.step.sm/crypto v0.77.2 h1:qFjjei+RHc5kP5R7NW9OUWT7SqWIuAOvOkXqg4fNWj8=
|
||||
go.step.sm/crypto v0.77.2/go.mod h1:W0YJb9onM5l78qgkXIJ2Up6grnwW8EtpCKIza/NCg0o=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -445,8 +445,8 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
|
||||
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -456,8 +456,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20260323153451-8400f4a93807 h1:sQVhWLXbNsa8CTzHOX3IHc7C4Q2JyxI5AweuMQZ/5H0=
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20260323153451-8400f4a93807/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
@@ -467,8 +467,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
@@ -477,8 +477,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -506,8 +506,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
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=
|
||||
@@ -517,8 +517,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@@ -528,8 +528,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -538,21 +538,21 @@ 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.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.271.0 h1:cIPN4qcUc61jlh7oXu6pwOQqbJW2GqYh5PS6rB2C/JY=
|
||||
google.golang.org/api v0.271.0/go.mod h1:CGT29bhwkbF+i11qkRUJb2KMKqcJ1hdFceEIRd9u64Q=
|
||||
google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d h1:vsOm753cOAMkt76efriTCDKjpCbK18XGHMJHo0JUKhc=
|
||||
google.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:0oz9d7g9QLSdv9/lgbIjowW1JoxMbxmBVNe8i6tORJI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
|
||||
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
|
||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
|
||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d h1:/aDRtSZJjyLQzm75d+a1wOJaqyKBMvIAfeQmoa3ORiI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:etfGUgejTiadZAUaEP14NP97xi1RGeawqkjDARA/UOs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
||||
@@ -41,7 +41,10 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
//
|
||||
// encode [<matcher>] <formats...> {
|
||||
// gzip [<level>]
|
||||
// zstd
|
||||
// zstd [<level>] {
|
||||
// level <level>
|
||||
// disable_checksum
|
||||
// }
|
||||
// minimum_length <length>
|
||||
// # response matcher block
|
||||
// match {
|
||||
|
||||
@@ -33,6 +33,10 @@ type Zstd struct {
|
||||
// The compression level. Accepted values: fastest, better, best, default.
|
||||
Level string `json:"level,omitempty"`
|
||||
|
||||
// Whether to include the optional 4-byte zstd frame checksum trailer.
|
||||
// If unset, the upstream zstd library default is preserved.
|
||||
Checksum *bool `json:"checksum,omitempty"`
|
||||
|
||||
// Compression level refer to type constants value from zstd.SpeedFastest to zstd.SpeedBestCompression
|
||||
level zstd.EncoderLevel
|
||||
}
|
||||
@@ -48,19 +52,48 @@ func (Zstd) CaddyModule() caddy.ModuleInfo {
|
||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
||||
func (z *Zstd) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume option name
|
||||
if !d.NextArg() {
|
||||
return nil
|
||||
args := d.RemainingArgs()
|
||||
switch len(args) {
|
||||
case 0:
|
||||
case 1:
|
||||
if _, err := parseEncoderLevel(args[0]); err != nil {
|
||||
return d.Err(err.Error())
|
||||
}
|
||||
z.Level = args[0]
|
||||
default:
|
||||
return d.ArgErr()
|
||||
}
|
||||
levelStr := d.Val()
|
||||
if ok, _ := zstd.EncoderLevelFromString(levelStr); !ok {
|
||||
return d.Errf("unexpected compression level, use one of '%s', '%s', '%s', '%s'",
|
||||
zstd.SpeedFastest,
|
||||
zstd.SpeedBetterCompression,
|
||||
zstd.SpeedBestCompression,
|
||||
zstd.SpeedDefault,
|
||||
)
|
||||
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "level":
|
||||
args := d.RemainingArgs()
|
||||
if len(args) != 1 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if z.Level != "" {
|
||||
return d.Err("compression level already specified")
|
||||
}
|
||||
if _, err := parseEncoderLevel(args[0]); err != nil {
|
||||
return d.Err(err.Error())
|
||||
}
|
||||
z.Level = args[0]
|
||||
|
||||
case "disable_checksum":
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if z.Checksum != nil {
|
||||
return d.Err("checksum already specified")
|
||||
}
|
||||
disabled := false
|
||||
z.Checksum = &disabled
|
||||
|
||||
default:
|
||||
return d.Errf("unknown subdirective '%s'", d.Val())
|
||||
}
|
||||
}
|
||||
z.Level = levelStr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -69,15 +102,11 @@ func (z *Zstd) Provision(ctx caddy.Context) error {
|
||||
if z.Level == "" {
|
||||
z.Level = zstd.SpeedDefault.String()
|
||||
}
|
||||
var ok bool
|
||||
if ok, z.level = zstd.EncoderLevelFromString(z.Level); !ok {
|
||||
return fmt.Errorf("unexpected compression level, use one of '%s', '%s', '%s', '%s'",
|
||||
zstd.SpeedFastest,
|
||||
zstd.SpeedDefault,
|
||||
zstd.SpeedBetterCompression,
|
||||
zstd.SpeedBestCompression,
|
||||
)
|
||||
level, err := parseEncoderLevel(z.Level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
z.level = level
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -90,14 +119,45 @@ func (z Zstd) NewEncoder() encode.Encoder {
|
||||
// The default of 8MB for the window is
|
||||
// too large for many clients, so we limit
|
||||
// it to 128K to lighten their load.
|
||||
writer, _ := zstd.NewWriter(
|
||||
nil,
|
||||
zstd.WithWindowSize(128<<10),
|
||||
writer, _ := zstd.NewWriter(nil, z.writerOptions(128<<10)...)
|
||||
return writer
|
||||
}
|
||||
|
||||
func (z Zstd) writerOptions(windowSize int) []zstd.EOption {
|
||||
opts := []zstd.EOption{
|
||||
zstd.WithWindowSize(windowSize),
|
||||
zstd.WithEncoderConcurrency(1),
|
||||
zstd.WithZeroFrames(true),
|
||||
zstd.WithEncoderLevel(z.level),
|
||||
zstd.WithEncoderLevel(z.encoderLevel()),
|
||||
}
|
||||
if z.Checksum != nil {
|
||||
opts = append(opts, zstd.WithEncoderCRC(*z.Checksum))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (z Zstd) encoderLevel() zstd.EncoderLevel {
|
||||
if z.level != 0 {
|
||||
return z.level
|
||||
}
|
||||
if z.Level != "" {
|
||||
if level, err := parseEncoderLevel(z.Level); err == nil {
|
||||
return level
|
||||
}
|
||||
}
|
||||
return zstd.SpeedDefault
|
||||
}
|
||||
|
||||
func parseEncoderLevel(level string) (zstd.EncoderLevel, error) {
|
||||
if ok, encLevel := zstd.EncoderLevelFromString(level); ok {
|
||||
return encLevel, nil
|
||||
}
|
||||
return 0, fmt.Errorf("unexpected compression level, use one of '%s', '%s', '%s', '%s'",
|
||||
zstd.SpeedFastest,
|
||||
zstd.SpeedBetterCompression,
|
||||
zstd.SpeedBestCompression,
|
||||
zstd.SpeedDefault,
|
||||
)
|
||||
return writer
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -100,7 +99,7 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
||||
}
|
||||
|
||||
if fsrv.Browse.RevealSymlinks {
|
||||
symLinkTarget, err := filepath.EvalSymlinks(path)
|
||||
symLinkTarget, err := os.Readlink(path)
|
||||
if err == nil {
|
||||
symlinkPath = symLinkTarget
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
// flush_interval <duration>
|
||||
// request_buffers <size>
|
||||
// response_buffers <size>
|
||||
// stream_buffer_size <size>
|
||||
// stream_timeout <duration>
|
||||
// stream_close_delay <duration>
|
||||
// verbose_logs
|
||||
@@ -646,7 +647,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
h.FlushInterval = caddy.Duration(dur)
|
||||
}
|
||||
|
||||
case "request_buffers", "response_buffers":
|
||||
case "request_buffers", "response_buffers", "stream_buffer_size":
|
||||
subdir := d.Val()
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
@@ -670,6 +671,8 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
h.RequestBuffers = size
|
||||
case "response_buffers":
|
||||
h.ResponseBuffers = size
|
||||
case "stream_buffer_size":
|
||||
h.StreamBufferSize = int(size)
|
||||
}
|
||||
|
||||
case "stream_timeout":
|
||||
@@ -725,9 +728,6 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], "", nil)
|
||||
case 2:
|
||||
// some lint checks, I guess
|
||||
if strings.EqualFold(args[0], "host") && (args[1] == "{hostport}" || args[1] == "{http.request.hostport}") {
|
||||
caddy.Log().Named("caddyfile").Warn("Unnecessary header_up Host: the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
}
|
||||
if strings.EqualFold(args[0], "x-forwarded-for") && (args[1] == "{remote}" || args[1] == "{http.request.remote}" || args[1] == "{remote_host}" || args[1] == "{http.request.remote.host}") {
|
||||
caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
}
|
||||
@@ -885,6 +885,14 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// check if the user set 'header_up host upstream_hostport' when proxying to HTTPS
|
||||
// this is unnecessary because it's the default behavior already
|
||||
if te.TLSEnabled() && h.Headers != nil && h.Headers.Request != nil {
|
||||
hostVal := h.Headers.Request.Set.Get("Host")
|
||||
if hostVal == "{upstream_hostport}" || hostVal == "{http.reverse_proxy.upstream.hostport}" {
|
||||
caddy.Log().Named("caddyfile").Warn("Unnecessary header_up Host: the reverse proxy's default behavior is to pass the configured upstream address to the upstream when proxying to HTTPS")
|
||||
}
|
||||
}
|
||||
if commonScheme == "http" && te.TLSEnabled() {
|
||||
return d.Errf("upstream address scheme is HTTP but transport is configured for HTTP+TLS (HTTPS)")
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestAddForwardedHeadersNonIP(t *testing.T) {
|
||||
|
||||
// Mock the context variables required by Caddy.
|
||||
// We need to inject the variable map manually since we aren't running the full server.
|
||||
vars := map[string]interface{}{
|
||||
vars := map[string]any{
|
||||
caddyhttp.TrustedProxyVarKey: false,
|
||||
}
|
||||
ctx := context.WithValue(req.Context(), caddyhttp.VarsCtxKey, vars)
|
||||
@@ -42,7 +42,7 @@ func TestAddForwardedHeaders_UnixSocketTrusted(t *testing.T) {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Forwarded-Host", "original.example.com")
|
||||
|
||||
vars := map[string]interface{}{
|
||||
vars := map[string]any{
|
||||
caddyhttp.TrustedProxyVarKey: true,
|
||||
caddyhttp.ClientIPVarKey: "1.2.3.4",
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func TestAddForwardedHeaders_UnixSocketUntrusted(t *testing.T) {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Forwarded-Host", "spoofed.example.com")
|
||||
|
||||
vars := map[string]interface{}{
|
||||
vars := map[string]any{
|
||||
caddyhttp.TrustedProxyVarKey: false,
|
||||
caddyhttp.ClientIPVarKey: "",
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func TestAddForwardedHeaders_UnixSocketTrustedNoExistingHeaders(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://example.com/", nil)
|
||||
req.RemoteAddr = "@"
|
||||
|
||||
vars := map[string]interface{}{
|
||||
vars := map[string]any{
|
||||
caddyhttp.TrustedProxyVarKey: true,
|
||||
caddyhttp.ClientIPVarKey: "5.6.7.8",
|
||||
}
|
||||
|
||||
@@ -129,11 +129,11 @@ func TestHTTPTransport_DialTLSContext_ProxyProtocol(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tls *TLSConfig
|
||||
proxyProtocol string
|
||||
name string
|
||||
tls *TLSConfig
|
||||
proxyProtocol string
|
||||
serverNameHasPlaceholder bool
|
||||
expectDialTLSContext bool
|
||||
expectDialTLSContext bool
|
||||
}{
|
||||
{
|
||||
name: "no TLS, no proxy protocol",
|
||||
@@ -194,4 +194,3 @@ func TestHTTPTransport_DialTLSContext_ProxyProtocol(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +171,12 @@ type Handler struct {
|
||||
// forcibly closed at the end of the timeout. Default: no timeout.
|
||||
StreamTimeout caddy.Duration `json:"stream_timeout,omitempty"`
|
||||
|
||||
// The size of the buffer used for each direction of streaming
|
||||
// requests such as WebSockets. If zero, the default size is 32 KiB.
|
||||
// This only affects upgraded bidirectional streams, not normal
|
||||
// request or response buffering.
|
||||
StreamBufferSize int `json:"stream_buffer_size,omitempty"`
|
||||
|
||||
// If nonzero, streaming requests such as WebSockets will not be
|
||||
// closed when the proxy config is unloaded, and instead the stream
|
||||
// will remain open until the delay is complete. In other words,
|
||||
|
||||
@@ -204,7 +204,12 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
||||
defer deleteFrontConn()
|
||||
defer deleteBackConn()
|
||||
|
||||
spc := switchProtocolCopier{user: conn, backend: backConn, wg: wg}
|
||||
spc := switchProtocolCopier{
|
||||
user: conn,
|
||||
backend: backConn,
|
||||
wg: wg,
|
||||
bufferSize: h.StreamBufferSize,
|
||||
}
|
||||
|
||||
// setup the timeout if requested
|
||||
var timeoutc <-chan time.Time
|
||||
@@ -636,20 +641,29 @@ func (m *maxLatencyWriter) stop() {
|
||||
type switchProtocolCopier struct {
|
||||
user, backend io.ReadWriteCloser
|
||||
wg *sync.WaitGroup
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.user, c.backend)
|
||||
_, err := io.CopyBuffer(c.user, c.backend, c.buffer())
|
||||
errc <- err
|
||||
c.wg.Done()
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.backend, c.user)
|
||||
_, err := io.CopyBuffer(c.backend, c.user, c.buffer())
|
||||
errc <- err
|
||||
c.wg.Done()
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) buffer() []byte {
|
||||
size := c.bufferSize
|
||||
if size <= 0 {
|
||||
size = defaultBufferSize
|
||||
}
|
||||
return make([]byte, size)
|
||||
}
|
||||
|
||||
var streamingBufPool = sync.Pool{
|
||||
New: func() any {
|
||||
// The Pool's New function should generally only return pointer
|
||||
|
||||
@@ -2,8 +2,10 @@ package reverseproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
@@ -34,3 +36,47 @@ func TestHandlerCopyResponse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchProtocolCopierBufferSize(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
var errc = make(chan error, 1)
|
||||
var dst bytes.Buffer
|
||||
|
||||
copier := switchProtocolCopier{
|
||||
user: nopReadWriteCloser{Reader: strings.NewReader("hello")},
|
||||
backend: nopReadWriteCloser{Writer: &dst},
|
||||
wg: &wg,
|
||||
bufferSize: 7,
|
||||
}
|
||||
|
||||
buf := copier.buffer()
|
||||
if got := len(buf); got != 7 {
|
||||
t.Fatalf("buffer len = %d, want 7", got)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go copier.copyToBackend(errc)
|
||||
wg.Wait()
|
||||
|
||||
if err := <-errc; err != nil {
|
||||
t.Fatalf("copyToBackend() error = %v", err)
|
||||
}
|
||||
if got := dst.String(); got != "hello" {
|
||||
t.Fatalf("copied data = %q, want %q", got, "hello")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSwitchProtocolCopierDefaultBufferSize(t *testing.T) {
|
||||
copier := switchProtocolCopier{}
|
||||
buf := copier.buffer()
|
||||
if got := len(buf); got != defaultBufferSize {
|
||||
t.Fatalf("buffer len = %d, want %d", got, defaultBufferSize)
|
||||
}
|
||||
}
|
||||
|
||||
type nopReadWriteCloser struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopReadWriteCloser) Close() error { return nil }
|
||||
|
||||
@@ -529,7 +529,14 @@ func (q *queryOps) do(r *http.Request, repl *caddy.Replacer) {
|
||||
if key == "" || val == "" {
|
||||
continue
|
||||
}
|
||||
query[val] = query[key]
|
||||
if key == val {
|
||||
continue
|
||||
}
|
||||
originalValues, ok := query[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
query[val] = originalValues
|
||||
delete(query, key)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ package rewrite
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
@@ -397,6 +398,55 @@ func TestRewrite(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryOpsRenameNoOpCases(t *testing.T) {
|
||||
repl := caddy.NewReplacer()
|
||||
|
||||
for i, tc := range []struct {
|
||||
input *http.Request
|
||||
expect map[string][]string
|
||||
ops *queryOps
|
||||
}{
|
||||
{
|
||||
ops: &queryOps{
|
||||
Rename: []queryOpsArguments{{Key: "ID", Val: "id"}},
|
||||
},
|
||||
input: newRequest(t, "GET", "/?page=test&id=5&test=100"),
|
||||
expect: map[string][]string{"id": {"5"}, "page": {"test"}, "test": {"100"}},
|
||||
},
|
||||
{
|
||||
ops: &queryOps{
|
||||
Rename: []queryOpsArguments{{Key: "id", Val: "id"}},
|
||||
},
|
||||
input: newRequest(t, "GET", "/?page=test&id=5&test=100"),
|
||||
expect: map[string][]string{"id": {"5"}, "page": {"test"}, "test": {"100"}},
|
||||
},
|
||||
{
|
||||
ops: &queryOps{
|
||||
Rename: []queryOpsArguments{{Key: "ID", Val: "id"}},
|
||||
},
|
||||
input: newRequest(t, "GET", "/?page=test&ID=5&test=100"),
|
||||
expect: map[string][]string{"id": {"5"}, "page": {"test"}, "test": {"100"}},
|
||||
},
|
||||
{
|
||||
ops: &queryOps{
|
||||
Rename: []queryOpsArguments{{Key: "ID", Val: "id"}},
|
||||
},
|
||||
input: newRequest(t, "GET", "/?page=test&ID=5&id=7&test=100"),
|
||||
expect: map[string][]string{"id": {"5"}, "page": {"test"}, "test": {"100"}},
|
||||
},
|
||||
} {
|
||||
repl.Set("http.request.uri", tc.input.RequestURI)
|
||||
repl.Set("http.request.uri.path", tc.input.URL.Path)
|
||||
repl.Set("http.request.uri.query", tc.input.URL.RawQuery)
|
||||
|
||||
tc.ops.do(tc.input, repl)
|
||||
|
||||
if actual := tc.input.URL.Query(); !reflect.DeepEqual(tc.expect, map[string][]string(actual)) {
|
||||
t.Errorf("Test %d: Expected query=%v but got %v", i, tc.expect, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRequest(t *testing.T, method, uri string) *http.Request {
|
||||
req, err := http.NewRequest(method, uri, nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -935,10 +935,10 @@ func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter
|
||||
ctx = context.WithValue(ctx, ServerCtxKey, s)
|
||||
|
||||
trusted, clientIP := determineTrustedProxy(r, s)
|
||||
varsMap := &sync.Map{}
|
||||
varsMap.Store(TrustedProxyVarKey, trusted)
|
||||
varsMap.Store(ClientIPVarKey, clientIP)
|
||||
ctx = context.WithValue(ctx, VarsCtxKey, varsMap)
|
||||
ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{
|
||||
TrustedProxyVarKey: trusted,
|
||||
ClientIPVarKey: clientIP,
|
||||
})
|
||||
|
||||
ctx = context.WithValue(ctx, routeGroupCtxKey, make(map[string]struct{}))
|
||||
|
||||
|
||||
+18
-30
@@ -20,7 +20,6 @@ import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
@@ -182,18 +181,15 @@ func (m VarsMatcher) MatchWithError(r *http.Request) (bool, error) {
|
||||
vars := r.Context().Value(VarsCtxKey).(map[string]any)
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
var fromPlaceholder bool
|
||||
var matcherValExpanded, valExpanded, varStr, v string
|
||||
var matcherValExpanded, varStr, v string
|
||||
var varValue any
|
||||
for key, vals := range m {
|
||||
if strings.HasPrefix(key, "{") &&
|
||||
strings.HasSuffix(key, "}") &&
|
||||
strings.Count(key, "{") == 1 {
|
||||
varValue, _ = repl.Get(strings.Trim(key, "{}"))
|
||||
fromPlaceholder = true
|
||||
} else {
|
||||
varValue = vars[key]
|
||||
fromPlaceholder = false
|
||||
}
|
||||
|
||||
switch vv := varValue.(type) {
|
||||
@@ -209,19 +205,15 @@ func (m VarsMatcher) MatchWithError(r *http.Request) (bool, error) {
|
||||
varStr = fmt.Sprintf("%v", vv)
|
||||
}
|
||||
|
||||
// Only expand placeholders in values from literal variable names
|
||||
// (e.g. map outputs). Values resolved from placeholder keys are
|
||||
// Don't expand placeholders in values from literal variable names
|
||||
// (e.g. map outputs) or other placeholders. These values are
|
||||
// already final and must not be re-expanded, as that would allow
|
||||
// user input like {env.SECRET} to be evaluated.
|
||||
valExpanded = varStr
|
||||
if !fromPlaceholder {
|
||||
valExpanded = repl.ReplaceAll(varStr, "")
|
||||
}
|
||||
|
||||
// see if any of the values given in the matcher match the actual value
|
||||
for _, v = range vals {
|
||||
matcherValExpanded = repl.ReplaceAll(v, "")
|
||||
if valExpanded == matcherValExpanded {
|
||||
if varStr == matcherValExpanded {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
@@ -325,18 +317,16 @@ func (m MatchVarsRE) MatchWithError(r *http.Request) (bool, error) {
|
||||
vars := r.Context().Value(VarsCtxKey).(map[string]any)
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
var fromPlaceholder, match bool
|
||||
var valExpanded, varStr string
|
||||
var match bool
|
||||
var varStr string
|
||||
var varValue any
|
||||
for key, val := range m {
|
||||
if strings.HasPrefix(key, "{") &&
|
||||
strings.HasSuffix(key, "}") &&
|
||||
strings.Count(key, "{") == 1 {
|
||||
varValue, _ = repl.Get(strings.Trim(key, "{}"))
|
||||
fromPlaceholder = true
|
||||
} else {
|
||||
varValue = vars[key]
|
||||
fromPlaceholder = false
|
||||
}
|
||||
|
||||
switch vv := varValue.(type) {
|
||||
@@ -352,15 +342,12 @@ func (m MatchVarsRE) MatchWithError(r *http.Request) (bool, error) {
|
||||
varStr = fmt.Sprintf("%v", vv)
|
||||
}
|
||||
|
||||
// Only expand placeholders in values from literal variable names
|
||||
// (e.g. map outputs). Values resolved from placeholder keys are
|
||||
// Don't expand placeholders in values from literal variable names
|
||||
// (e.g. map outputs) or other placeholders. These values are
|
||||
// already final and must not be re-expanded, as that would allow
|
||||
// user input like {env.SECRET} to be evaluated.
|
||||
valExpanded = varStr
|
||||
if !fromPlaceholder {
|
||||
valExpanded = repl.ReplaceAll(varStr, "")
|
||||
}
|
||||
if match = val.Match(valExpanded, repl); match {
|
||||
|
||||
if match = val.Match(varStr, repl); match {
|
||||
return match, nil
|
||||
}
|
||||
}
|
||||
@@ -444,12 +431,11 @@ func (m MatchVarsRE) Validate() error {
|
||||
// GetVar gets a value out of the context's variable table by key.
|
||||
// If the key does not exist, the return value will be nil.
|
||||
func GetVar(ctx context.Context, key string) any {
|
||||
varMap, ok := ctx.Value(VarsCtxKey).(*sync.Map)
|
||||
varMap, ok := ctx.Value(VarsCtxKey).(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
val, _ := varMap.Load(key)
|
||||
return val
|
||||
return varMap[key]
|
||||
}
|
||||
|
||||
// SetVar sets a value in the context's variable table with
|
||||
@@ -460,15 +446,17 @@ func GetVar(ctx context.Context, key string) any {
|
||||
// underlying value does not count) and the key exists in
|
||||
// the table, the key+value will be deleted from the table.
|
||||
func SetVar(ctx context.Context, key string, value any) {
|
||||
varMap, ok := ctx.Value(VarsCtxKey).(*sync.Map)
|
||||
varMap, ok := ctx.Value(VarsCtxKey).(map[string]any)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if value == nil {
|
||||
varMap.Delete(key)
|
||||
return
|
||||
if _, ok := varMap[key]; ok {
|
||||
delete(varMap, key)
|
||||
return
|
||||
}
|
||||
}
|
||||
varMap.Store(key, value)
|
||||
varMap[key] = value
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
// 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 caddyhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func newVarsTestRequest(t *testing.T, target string, headers http.Header, vars map[string]any) (*http.Request, *caddy.Replacer) {
|
||||
t.Helper()
|
||||
|
||||
if target == "" {
|
||||
target = "https://example.com/test"
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, target, nil)
|
||||
req.Header = headers
|
||||
|
||||
repl := caddy.NewReplacer()
|
||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||
if vars == nil {
|
||||
vars = make(map[string]any)
|
||||
}
|
||||
// Inject vars directly so these tests exercise matcher-side handling of
|
||||
// already-resolved values, not VarsMiddleware placeholder expansion.
|
||||
ctx = context.WithValue(ctx, VarsCtxKey, vars)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||
|
||||
return req, repl
|
||||
}
|
||||
|
||||
func TestVarsMatcherDoesNotExpandResolvedValues(t *testing.T) {
|
||||
t.Setenv("CADDY_VARS_TEST_SECRET", "topsecret")
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
target string
|
||||
match VarsMatcher
|
||||
headers http.Header
|
||||
vars map[string]any
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
name: "literal variable value containing placeholder syntax is not re-expanded",
|
||||
match: VarsMatcher{"secret": []string{"topsecret"}},
|
||||
vars: map[string]any{"secret": "{env.CADDY_VARS_TEST_SECRET}"},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "placeholder key value containing placeholder syntax is not re-expanded",
|
||||
match: VarsMatcher{"{http.request.header.X-Input}": []string{"topsecret"}},
|
||||
headers: http.Header{"X-Input": []string{"{env.CADDY_VARS_TEST_SECRET}"}},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "query placeholder value containing placeholder syntax is not re-expanded",
|
||||
target: "https://example.com/test?foo=%7Benv.CADDY_VARS_TEST_SECRET%7D",
|
||||
match: VarsMatcher{"{http.request.uri.query.foo}": []string{"topsecret"}},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "matcher values still expand placeholders",
|
||||
match: VarsMatcher{"secret": []string{"{env.CADDY_VARS_TEST_SECRET}"}},
|
||||
vars: map[string]any{"secret": "topsecret"},
|
||||
expect: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
req, _ := newVarsTestRequest(t, tc.target, tc.headers, tc.vars)
|
||||
|
||||
actual, err := tc.match.MatchWithError(req)
|
||||
if err != nil {
|
||||
t.Fatalf("MatchWithError() error = %v", err)
|
||||
}
|
||||
|
||||
if actual != tc.expect {
|
||||
t.Fatalf("MatchWithError() = %t, want %t", actual, tc.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchVarsREDoesNotExpandResolvedValues(t *testing.T) {
|
||||
t.Setenv("CADDY_VARS_TEST_SECRET", "topsecret")
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
target string
|
||||
match MatchVarsRE
|
||||
headers http.Header
|
||||
vars map[string]any
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
name: "literal variable value containing placeholder syntax is not re-expanded",
|
||||
match: MatchVarsRE{"secret": &MatchRegexp{Pattern: "^topsecret$"}},
|
||||
vars: map[string]any{"secret": "{env.CADDY_VARS_TEST_SECRET}"},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "placeholder key value containing placeholder syntax is not re-expanded",
|
||||
match: MatchVarsRE{"{http.request.header.X-Input}": &MatchRegexp{Pattern: "^topsecret$"}},
|
||||
headers: http.Header{"X-Input": []string{"{env.CADDY_VARS_TEST_SECRET}"}},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "query placeholder value containing placeholder syntax is not re-expanded",
|
||||
target: "https://example.com/test?foo=%7Benv.CADDY_VARS_TEST_SECRET%7D",
|
||||
match: MatchVarsRE{"{http.request.uri.query.foo}": &MatchRegexp{Pattern: "^topsecret$"}},
|
||||
expect: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := tc.match.Provision(caddy.Context{})
|
||||
if err != nil {
|
||||
t.Fatalf("Provision() error = %v", err)
|
||||
}
|
||||
|
||||
err = tc.match.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("Validate() error = %v", err)
|
||||
}
|
||||
|
||||
req, _ := newVarsTestRequest(t, tc.target, tc.headers, tc.vars)
|
||||
|
||||
actual, err := tc.match.MatchWithError(req)
|
||||
if err != nil {
|
||||
t.Fatalf("MatchWithError() error = %v", err)
|
||||
}
|
||||
|
||||
if actual != tc.expect {
|
||||
t.Fatalf("MatchWithError() = %t, want %t", actual, tc.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -149,6 +149,15 @@ func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
|
||||
iss.AccountKey = accountKey
|
||||
}
|
||||
|
||||
// expand DNS override domain, if non-empty
|
||||
if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.OverrideDomain != "" {
|
||||
overrideDomain, err := repl.ReplaceOrErr(iss.Challenges.DNS.OverrideDomain, true, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expanding DNS override domain '%s': %v", iss.Challenges.DNS.OverrideDomain, err)
|
||||
}
|
||||
iss.Challenges.DNS.OverrideDomain = overrideDomain
|
||||
}
|
||||
|
||||
// DNS challenge provider, if not already established
|
||||
if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.solver == nil {
|
||||
var prov certmagic.DNSProvider
|
||||
|
||||
+307
-15
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -27,6 +28,8 @@ func init() {
|
||||
caddy.RegisterModule(PKIIntermediateCAPool{})
|
||||
caddy.RegisterModule(StoragePool{})
|
||||
caddy.RegisterModule(HTTPCertPool{})
|
||||
caddy.RegisterModule(SystemCAPool{})
|
||||
caddy.RegisterModule(CombinedCAPool{})
|
||||
}
|
||||
|
||||
// The interface to be implemented by all guest modules part of
|
||||
@@ -35,6 +38,12 @@ type CA interface {
|
||||
CertPool() *x509.CertPool
|
||||
}
|
||||
|
||||
// CertificateProvider is an optional interface that CA pool sources
|
||||
// can implement to expose their underlying certificates for combining.
|
||||
type CertificateProvider interface {
|
||||
Certificates() []*x509.Certificate
|
||||
}
|
||||
|
||||
// InlineCAPool is a certificate authority pool provider coming from
|
||||
// a DER-encoded certificates in the config
|
||||
type InlineCAPool struct {
|
||||
@@ -44,7 +53,8 @@ type InlineCAPool struct {
|
||||
// these CAs will be rejected.
|
||||
TrustedCACerts []string `json:"trusted_ca_certs,omitempty"`
|
||||
|
||||
pool *x509.CertPool
|
||||
pool *x509.CertPool
|
||||
certs []*x509.Certificate
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
@@ -60,14 +70,17 @@ func (icp InlineCAPool) CaddyModule() caddy.ModuleInfo {
|
||||
// Provision implements caddy.Provisioner.
|
||||
func (icp *InlineCAPool) Provision(ctx caddy.Context) error {
|
||||
caPool := x509.NewCertPool()
|
||||
var certs []*x509.Certificate
|
||||
for i, clientCAString := range icp.TrustedCACerts {
|
||||
clientCA, err := decodeBase64DERCert(clientCAString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing certificate at index %d: %v", i, err)
|
||||
}
|
||||
caPool.AddCert(clientCA)
|
||||
certs = append(certs, clientCA)
|
||||
}
|
||||
icp.pool = caPool
|
||||
icp.certs = certs
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -103,6 +116,11 @@ func (icp InlineCAPool) CertPool() *x509.CertPool {
|
||||
return icp.pool
|
||||
}
|
||||
|
||||
// Certificates implements CertificateProvider.
|
||||
func (icp InlineCAPool) Certificates() []*x509.Certificate {
|
||||
return icp.certs
|
||||
}
|
||||
|
||||
// FileCAPool generates trusted root certificates pool from the designated DER and PEM file
|
||||
type FileCAPool struct {
|
||||
// TrustedCACertPEMFiles is a list of PEM file names
|
||||
@@ -111,7 +129,8 @@ type FileCAPool struct {
|
||||
// these CA certificates will be rejected.
|
||||
TrustedCACertPEMFiles []string `json:"pem_files,omitempty"`
|
||||
|
||||
pool *x509.CertPool
|
||||
pool *x509.CertPool
|
||||
certs []*x509.Certificate
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
@@ -127,14 +146,32 @@ func (FileCAPool) CaddyModule() caddy.ModuleInfo {
|
||||
// Loads and decodes the DER and pem files to generate the certificate pool
|
||||
func (f *FileCAPool) Provision(ctx caddy.Context) error {
|
||||
caPool := x509.NewCertPool()
|
||||
var certs []*x509.Certificate
|
||||
for _, pemFile := range f.TrustedCACertPEMFiles {
|
||||
pemContents, err := os.ReadFile(pemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading %s: %v", pemFile, err)
|
||||
}
|
||||
caPool.AppendCertsFromPEM(pemContents)
|
||||
// Parse PEM to extract certificates
|
||||
for len(pemContents) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemContents = pem.Decode(pemContents)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
continue
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing certificate in %s: %v", pemFile, err)
|
||||
}
|
||||
caPool.AddCert(cert)
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
}
|
||||
f.pool = caPool
|
||||
f.certs = certs
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -166,13 +203,19 @@ func (f FileCAPool) CertPool() *x509.CertPool {
|
||||
return f.pool
|
||||
}
|
||||
|
||||
// Certificates implements CertificateProvider.
|
||||
func (f FileCAPool) Certificates() []*x509.Certificate {
|
||||
return f.certs
|
||||
}
|
||||
|
||||
// PKIRootCAPool extracts the trusted root certificates from Caddy's native 'pki' app
|
||||
type PKIRootCAPool struct {
|
||||
// List of the Authority names that are configured in the `pki` app whose root certificates are trusted
|
||||
Authority []string `json:"authority,omitempty"`
|
||||
|
||||
ca []*caddypki.CA
|
||||
pool *x509.CertPool
|
||||
ca []*caddypki.CA
|
||||
pool *x509.CertPool
|
||||
certs []*x509.Certificate
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
@@ -201,10 +244,17 @@ func (p *PKIRootCAPool) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
var certs []*x509.Certificate
|
||||
for _, ca := range p.ca {
|
||||
caPool.AddCert(ca.RootCertificate())
|
||||
rootCert := ca.RootCertificate()
|
||||
if rootCert == nil {
|
||||
return fmt.Errorf("CA %s has no root certificate", ca.ID)
|
||||
}
|
||||
caPool.AddCert(rootCert)
|
||||
certs = append(certs, rootCert)
|
||||
}
|
||||
p.pool = caPool
|
||||
p.certs = certs
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -238,13 +288,19 @@ func (p PKIRootCAPool) CertPool() *x509.CertPool {
|
||||
return p.pool
|
||||
}
|
||||
|
||||
// Certificates implements CertificateProvider.
|
||||
func (p PKIRootCAPool) Certificates() []*x509.Certificate {
|
||||
return p.certs
|
||||
}
|
||||
|
||||
// PKIIntermediateCAPool extracts the trusted intermediate certificates from Caddy's native 'pki' app
|
||||
type PKIIntermediateCAPool struct {
|
||||
// List of the Authority names that are configured in the `pki` app whose intermediate certificates are trusted
|
||||
Authority []string `json:"authority,omitempty"`
|
||||
|
||||
ca []*caddypki.CA
|
||||
pool *x509.CertPool
|
||||
ca []*caddypki.CA
|
||||
pool *x509.CertPool
|
||||
certs []*x509.Certificate
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
@@ -273,12 +329,18 @@ func (p *PKIIntermediateCAPool) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
var certs []*x509.Certificate
|
||||
for _, ca := range p.ca {
|
||||
for _, c := range ca.IntermediateCertificateChain() {
|
||||
if c == nil {
|
||||
return fmt.Errorf("CA %s has a nil certificate in its intermediate chain", ca.ID)
|
||||
}
|
||||
caPool.AddCert(c)
|
||||
certs = append(certs, c)
|
||||
}
|
||||
}
|
||||
p.pool = caPool
|
||||
p.certs = certs
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -311,6 +373,11 @@ func (p PKIIntermediateCAPool) CertPool() *x509.CertPool {
|
||||
return p.pool
|
||||
}
|
||||
|
||||
// Certificates implements CertificateProvider.
|
||||
func (p PKIIntermediateCAPool) Certificates() []*x509.Certificate {
|
||||
return p.certs
|
||||
}
|
||||
|
||||
// StoragePool extracts the trusted certificates root from Caddy storage
|
||||
type StoragePool struct {
|
||||
// The storage module where the trusted root certificates are stored. Absent
|
||||
@@ -322,6 +389,7 @@ type StoragePool struct {
|
||||
|
||||
storage certmagic.Storage
|
||||
pool *x509.CertPool
|
||||
certs []*x509.Certificate
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
@@ -354,16 +422,33 @@ func (ca *StoragePool) Provision(ctx caddy.Context) error {
|
||||
return fmt.Errorf("no PEM keys specified")
|
||||
}
|
||||
caPool := x509.NewCertPool()
|
||||
var certs []*x509.Certificate
|
||||
for _, caID := range ca.PEMKeys {
|
||||
bs, err := ca.storage.Load(ctx, caID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading cert '%s' from storage: %s", caID, err)
|
||||
}
|
||||
if !caPool.AppendCertsFromPEM(bs) {
|
||||
return fmt.Errorf("failed to add certificate '%s' to pool", caID)
|
||||
// Parse PEM to extract certificates
|
||||
pemData := bs
|
||||
for len(pemData) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemData = pem.Decode(pemData)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
continue
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing certificate '%s': %v", caID, err)
|
||||
}
|
||||
caPool.AddCert(cert)
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
}
|
||||
ca.pool = caPool
|
||||
ca.certs = certs
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -413,9 +498,13 @@ func (p StoragePool) CertPool() *x509.CertPool {
|
||||
return p.pool
|
||||
}
|
||||
|
||||
// Certificates implements CertificateProvider.
|
||||
func (p StoragePool) Certificates() []*x509.Certificate {
|
||||
return p.certs
|
||||
}
|
||||
|
||||
// TLSConfig holds configuration related to the TLS configuration for the
|
||||
// transport/client.
|
||||
// copied from with minor modifications: modules/caddyhttp/reverseproxy/httptransport.go
|
||||
type TLSConfig 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"`
|
||||
@@ -500,7 +589,6 @@ 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) {
|
||||
repl, ok := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
if !ok || repl == nil {
|
||||
@@ -554,7 +642,8 @@ type HTTPCertPool struct {
|
||||
// Customize the TLS connection knobs to used during the HTTP call
|
||||
TLS *TLSConfig `json:"tls,omitempty"`
|
||||
|
||||
pool *x509.CertPool
|
||||
pool *x509.CertPool
|
||||
certs []*x509.Certificate
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
@@ -570,6 +659,7 @@ func (HTTPCertPool) CaddyModule() caddy.ModuleInfo {
|
||||
// Provision implements caddy.Provisioner.
|
||||
func (hcp *HTTPCertPool) Provision(ctx caddy.Context) error {
|
||||
caPool := x509.NewCertPool()
|
||||
var certs []*x509.Certificate
|
||||
|
||||
customTransport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
if hcp.TLS != nil {
|
||||
@@ -597,11 +687,30 @@ func (hcp *HTTPCertPool) Provision(ctx caddy.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !caPool.AppendCertsFromPEM(pembs) {
|
||||
return fmt.Errorf("failed to add certs from URL: %s", uri)
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return fmt.Errorf("HTTP %d fetching CA certificate bundle from %s", res.StatusCode, uri)
|
||||
}
|
||||
// Parse PEM to extract certificates
|
||||
pemData := pembs
|
||||
for len(pemData) > 0 {
|
||||
var block *pem.Block
|
||||
block, pemData = pem.Decode(pemData)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
continue
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing certificate from URL %s: %v", uri, err)
|
||||
}
|
||||
caPool.AddCert(cert)
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
}
|
||||
hcp.pool = caPool
|
||||
hcp.certs = certs
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -665,6 +774,179 @@ func (hcp HTTPCertPool) CertPool() *x509.CertPool {
|
||||
return hcp.pool
|
||||
}
|
||||
|
||||
// Certificates implements CertificateProvider.
|
||||
func (hcp HTTPCertPool) Certificates() []*x509.Certificate {
|
||||
return hcp.certs
|
||||
}
|
||||
|
||||
// SystemCAPool obtains the trusted root certificates from the system's
|
||||
// certificate pool using x509.SystemCertPool()
|
||||
type SystemCAPool struct {
|
||||
pool *x509.CertPool
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
func (SystemCAPool) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "tls.ca_pool.source.system",
|
||||
New: func() caddy.Module {
|
||||
return new(SystemCAPool)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Provision implements caddy.Provisioner.
|
||||
func (scp *SystemCAPool) Provision(ctx caddy.Context) error {
|
||||
pool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load system cert pool: %v", err)
|
||||
}
|
||||
scp.pool = pool
|
||||
return nil
|
||||
}
|
||||
|
||||
func (scp *SystemCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume module name
|
||||
if d.CountRemainingArgs() > 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if d.NextBlock(0) {
|
||||
return d.Err("system trust pool does not support any configuration")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CertPool implements CA.
|
||||
func (scp SystemCAPool) CertPool() *x509.CertPool {
|
||||
return scp.pool
|
||||
}
|
||||
|
||||
// The `combined` pool type merges multiple pools. The `sources` pools must implement the
|
||||
// `CertificateProvider` interface, which allows them to export their certificate set.
|
||||
//
|
||||
// Note: SystemCAPool does not implement CertificateProvider because
|
||||
// x509.SystemCertPool() doesn't expose its certificates, so it cannot
|
||||
// be used as a source in CombinedCAPool.
|
||||
type CombinedCAPool struct {
|
||||
// The CA pool sources to combine. Each source is a CA pool provider module.
|
||||
SourcesRaw []json.RawMessage `json:"sources,omitempty" caddy:"namespace=tls.ca_pool.source inline_key=provider"`
|
||||
|
||||
sources []CA
|
||||
pool *x509.CertPool
|
||||
certs []*x509.Certificate
|
||||
}
|
||||
|
||||
// CaddyModule implements caddy.Module.
|
||||
func (CombinedCAPool) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "tls.ca_pool.source.combined",
|
||||
New: func() caddy.Module {
|
||||
return new(CombinedCAPool)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Provision implements caddy.Provisioner.
|
||||
func (ccp *CombinedCAPool) Provision(ctx caddy.Context) error {
|
||||
if len(ccp.SourcesRaw) == 0 {
|
||||
return fmt.Errorf("no sources specified for combined CA pool")
|
||||
}
|
||||
|
||||
// Load all source modules
|
||||
sources, err := ctx.LoadModule(ccp, "SourcesRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading CA pool sources: %v", err)
|
||||
}
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
var allCerts []*x509.Certificate
|
||||
|
||||
for _, src := range sources.([]any) {
|
||||
ca, ok := src.(CA)
|
||||
if !ok {
|
||||
return fmt.Errorf("source module is not a CA pool provider")
|
||||
}
|
||||
ccp.sources = append(ccp.sources, ca)
|
||||
|
||||
certProvider, ok := ca.(CertificateProvider)
|
||||
if !ok {
|
||||
return fmt.Errorf("source %T does not implement CertificateProvider (required for combining)", ca)
|
||||
}
|
||||
|
||||
certs := certProvider.Certificates()
|
||||
if certs == nil {
|
||||
return fmt.Errorf("source %T returned nil certificates", ca)
|
||||
}
|
||||
for _, cert := range certs {
|
||||
if cert == nil {
|
||||
return fmt.Errorf("source %T returned a nil certificate", ca)
|
||||
}
|
||||
caPool.AddCert(cert)
|
||||
allCerts = append(allCerts, cert)
|
||||
}
|
||||
}
|
||||
|
||||
ccp.pool = caPool
|
||||
ccp.certs = allCerts
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Syntax:
|
||||
//
|
||||
// trust_pool combined {
|
||||
// source <module_name> {
|
||||
// <module_config>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The 'source' directive can be specified multiple times. Sources that
|
||||
// don't implement CertificateProvider (like 'system') cannot be combined.
|
||||
func (ccp *CombinedCAPool) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume module name
|
||||
if d.CountRemainingArgs() > 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "source":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
modStem := d.Val()
|
||||
modID := "tls.ca_pool.source." + modStem
|
||||
unm, err := caddyfile.UnmarshalModule(d, modID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ca, ok := unm.(CA)
|
||||
if !ok {
|
||||
return d.Errf("module %s is not a CA pool provider", modID)
|
||||
}
|
||||
ccp.SourcesRaw = append(ccp.SourcesRaw, caddyconfig.JSONModuleObject(ca, "provider", modStem, nil))
|
||||
default:
|
||||
return d.Errf("unrecognized directive: %s", d.Val())
|
||||
}
|
||||
}
|
||||
|
||||
if len(ccp.SourcesRaw) == 0 {
|
||||
return d.Err("no sources specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CertPool implements CA.
|
||||
func (ccp CombinedCAPool) CertPool() *x509.CertPool {
|
||||
return ccp.pool
|
||||
}
|
||||
|
||||
// Certificates implements CertificateProvider.
|
||||
func (ccp CombinedCAPool) Certificates() []*x509.Certificate {
|
||||
return ccp.certs
|
||||
}
|
||||
|
||||
var (
|
||||
_ caddy.Module = (*InlineCAPool)(nil)
|
||||
_ caddy.Provisioner = (*InlineCAPool)(nil)
|
||||
@@ -696,4 +978,14 @@ var (
|
||||
_ caddy.Validator = (*HTTPCertPool)(nil)
|
||||
_ CA = (*HTTPCertPool)(nil)
|
||||
_ caddyfile.Unmarshaler = (*HTTPCertPool)(nil)
|
||||
|
||||
_ caddy.Module = (*SystemCAPool)(nil)
|
||||
_ caddy.Provisioner = (*SystemCAPool)(nil)
|
||||
_ CA = (*SystemCAPool)(nil)
|
||||
_ caddyfile.Unmarshaler = (*SystemCAPool)(nil)
|
||||
|
||||
_ caddy.Module = (*CombinedCAPool)(nil)
|
||||
_ caddy.Provisioner = (*CombinedCAPool)(nil)
|
||||
_ CA = (*CombinedCAPool)(nil)
|
||||
_ caddyfile.Unmarshaler = (*CombinedCAPool)(nil)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package caddytls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -776,3 +777,219 @@ func TestHTTPCertPoolUnmarshalCaddyfile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemCAPoolUnmarshalCaddyfile(t *testing.T) {
|
||||
type args struct {
|
||||
d *caddyfile.Dispenser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "basic system pool configuration",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`system`),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "system pool with arguments produces error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`system foo`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "system pool with block produces error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`system {
|
||||
foo bar
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scp := &SystemCAPool{}
|
||||
if err := scp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
|
||||
t.Errorf("SystemCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombinedCAPoolUnmarshalCaddyfile(t *testing.T) {
|
||||
type args struct {
|
||||
d *caddyfile.Dispenser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty block produces error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`combined {
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "arguments on same line as module name produces error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`combined foo`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "single source - system",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`combined {
|
||||
source system
|
||||
}`),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "single source - inline with config",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(fmt.Sprintf(`combined {
|
||||
source inline {
|
||||
trust_der %s
|
||||
}
|
||||
}`, test_der_1)),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple sources produces error due to limitation",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(fmt.Sprintf(`combined {
|
||||
source system
|
||||
source inline {
|
||||
trust_der %s
|
||||
}
|
||||
}`, test_der_1)),
|
||||
},
|
||||
wantErr: false, // UnmarshalCaddyfile succeeds, but Provision will fail
|
||||
},
|
||||
{
|
||||
name: "source without module name produces error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`combined {
|
||||
source
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid directive produces error",
|
||||
args: args{
|
||||
d: caddyfile.NewTestDispenser(`combined {
|
||||
invalid_directive foo
|
||||
}`),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ccp := &CombinedCAPool{}
|
||||
if err := ccp.UnmarshalCaddyfile(tt.args.d); (err != nil) != tt.wantErr {
|
||||
t.Errorf("CombinedCAPool.UnmarshalCaddyfile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !tt.wantErr && len(ccp.SourcesRaw) == 0 {
|
||||
t.Errorf("CombinedCAPool.UnmarshalCaddyfile() produced no sources")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemCAPoolProvision(t *testing.T) {
|
||||
scp := &SystemCAPool{}
|
||||
ctx := caddy.Context{Context: context.Background()}
|
||||
|
||||
err := scp.Provision(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("SystemCAPool.Provision() error = %v", err)
|
||||
}
|
||||
|
||||
if scp.pool == nil {
|
||||
t.Error("SystemCAPool.Provision() did not create a cert pool")
|
||||
}
|
||||
|
||||
pool := scp.CertPool()
|
||||
if pool == nil {
|
||||
t.Error("SystemCAPool.CertPool() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombinedCAPoolProvisionWithSystemFails(t *testing.T) {
|
||||
// Test that combining system pool fails during Provision
|
||||
// because SystemCAPool doesn't implement CertificateProvider
|
||||
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
||||
defer cancel()
|
||||
|
||||
// Create a combined pool with system source
|
||||
ccp := &CombinedCAPool{
|
||||
SourcesRaw: []json.RawMessage{
|
||||
json.RawMessage(`{"provider":"system"}`),
|
||||
},
|
||||
}
|
||||
|
||||
err := ccp.Provision(ctx)
|
||||
if err == nil {
|
||||
t.Error("CombinedCAPool.Provision() with system source should fail, but succeeded")
|
||||
}
|
||||
|
||||
// Verify error message mentions CertificateProvider
|
||||
if err != nil && !contains(err.Error(), "CertificateProvider") {
|
||||
t.Errorf("Expected error to mention CertificateProvider, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombinedCAPoolProvisionWithInlineSucceeds(t *testing.T) {
|
||||
// Test that combining inline pools works
|
||||
ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
|
||||
defer cancel()
|
||||
|
||||
// Create a combined pool with inline source
|
||||
ccp := &CombinedCAPool{
|
||||
SourcesRaw: []json.RawMessage{
|
||||
json.RawMessage(fmt.Sprintf(`{"provider":"inline","trusted_ca_certs":["%s"]}`, test_der_1)),
|
||||
},
|
||||
}
|
||||
|
||||
err := ccp.Provision(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("CombinedCAPool.Provision() with inline source failed: %v", err)
|
||||
}
|
||||
|
||||
if ccp.pool == nil {
|
||||
t.Error("CombinedCAPool.Provision() did not create a cert pool")
|
||||
}
|
||||
|
||||
pool := ccp.CertPool()
|
||||
if pool == nil {
|
||||
t.Error("CombinedCAPool.CertPool() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for string contains check
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
|
||||
(len(s) > 0 && len(substr) > 0 && findSubstring(s, substr)))
|
||||
}
|
||||
|
||||
func findSubstring(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
// 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 logging
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.uber.org/zap/buffer"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(JournaldEncoder{})
|
||||
}
|
||||
|
||||
// JournaldEncoder wraps another encoder and prepends a systemd/journald
|
||||
// priority prefix to each emitted log line. This lets journald classify
|
||||
// stdout/stderr log lines by severity while leaving the underlying log
|
||||
// structure to the wrapped encoder.
|
||||
//
|
||||
// This encoder does not write directly to journald; it only changes the
|
||||
// encoded output by adding the priority marker that journald understands.
|
||||
// The wrapped encoder still controls the actual log format, such as JSON
|
||||
// or console output.
|
||||
type JournaldEncoder struct {
|
||||
zapcore.Encoder `json:"-"`
|
||||
|
||||
// The underlying encoder that actually encodes the log entries.
|
||||
// If not specified, defaults to "json", unless the output is a
|
||||
// terminal, in which case it defaults to "console".
|
||||
WrappedRaw json.RawMessage `json:"wrap,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
|
||||
|
||||
wrappedIsDefault bool
|
||||
ctx caddy.Context
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (JournaldEncoder) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "caddy.logging.encoders.journald",
|
||||
New: func() caddy.Module { return new(JournaldEncoder) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up the encoder.
|
||||
func (je *JournaldEncoder) Provision(ctx caddy.Context) error {
|
||||
je.ctx = ctx
|
||||
|
||||
if je.WrappedRaw == nil {
|
||||
je.Encoder = &JSONEncoder{}
|
||||
if p, ok := je.Encoder.(caddy.Provisioner); ok {
|
||||
if err := p.Provision(ctx); err != nil {
|
||||
return fmt.Errorf("provisioning fallback encoder module: %v", err)
|
||||
}
|
||||
}
|
||||
je.wrappedIsDefault = true
|
||||
} else {
|
||||
val, err := ctx.LoadModule(je, "WrappedRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading wrapped encoder module: %v", err)
|
||||
}
|
||||
je.Encoder = val.(zapcore.Encoder)
|
||||
}
|
||||
|
||||
suppressConsoleEncoderTimestamp(je.Encoder)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureDefaultFormat will set the default wrapped format to "console"
|
||||
// if the writer is a terminal. If already configured, it passes through
|
||||
// the writer so a deeply nested encoder can configure its own default format.
|
||||
func (je *JournaldEncoder) ConfigureDefaultFormat(wo caddy.WriterOpener) error {
|
||||
if !je.wrappedIsDefault {
|
||||
if cfd, ok := je.Encoder.(caddy.ConfiguresFormatterDefault); ok {
|
||||
return cfd.ConfigureDefaultFormat(wo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if caddy.IsWriterStandardStream(wo) && term.IsTerminal(int(os.Stderr.Fd())) {
|
||||
je.Encoder = &ConsoleEncoder{}
|
||||
if p, ok := je.Encoder.(caddy.Provisioner); ok {
|
||||
if err := p.Provision(je.ctx); err != nil {
|
||||
return fmt.Errorf("provisioning fallback encoder module: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suppressConsoleEncoderTimestamp(je.Encoder)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// journald {
|
||||
// wrap <another encoder>
|
||||
// }
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// log {
|
||||
// format journald {
|
||||
// wrap json
|
||||
// }
|
||||
// }
|
||||
func (je *JournaldEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
d.Next() // consume encoder name
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
for d.NextBlock(0) {
|
||||
if d.Val() != "wrap" {
|
||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||
}
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
moduleName := d.Val()
|
||||
moduleID := "caddy.logging.encoders." + moduleName
|
||||
unm, err := caddyfile.UnmarshalModule(d, moduleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc, ok := unm.(zapcore.Encoder)
|
||||
if !ok {
|
||||
return d.Errf("module %s (%T) is not a zapcore.Encoder", moduleID, unm)
|
||||
}
|
||||
je.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone implements zapcore.Encoder.
|
||||
func (je JournaldEncoder) Clone() zapcore.Encoder {
|
||||
return JournaldEncoder{
|
||||
Encoder: je.Encoder.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeEntry implements zapcore.Encoder.
|
||||
func (je JournaldEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
|
||||
encoded, err := je.Encoder.Clone().EncodeEntry(ent, fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := bufferpool.Get()
|
||||
out.AppendString(journaldPriorityPrefix(ent.Level))
|
||||
out.AppendBytes(encoded.Bytes())
|
||||
encoded.Free()
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func journaldPriorityPrefix(level zapcore.Level) string {
|
||||
switch level {
|
||||
case zapcore.InvalidLevel:
|
||||
return "<6>"
|
||||
case zapcore.DebugLevel:
|
||||
return "<7>"
|
||||
case zapcore.InfoLevel:
|
||||
return "<6>"
|
||||
case zapcore.WarnLevel:
|
||||
return "<4>"
|
||||
case zapcore.ErrorLevel:
|
||||
return "<3>"
|
||||
case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel:
|
||||
return "<2>"
|
||||
default:
|
||||
return "<6>"
|
||||
}
|
||||
}
|
||||
|
||||
func suppressConsoleEncoderTimestamp(enc zapcore.Encoder) {
|
||||
empty := ""
|
||||
|
||||
switch e := enc.(type) {
|
||||
case *ConsoleEncoder:
|
||||
e.TimeKey = &empty
|
||||
_ = e.Provision(caddy.Context{})
|
||||
case *AppendEncoder:
|
||||
suppressConsoleEncoderTimestamp(e.wrapped)
|
||||
case *FilterEncoder:
|
||||
suppressConsoleEncoderTimestamp(e.wrapped)
|
||||
case *JournaldEncoder:
|
||||
suppressConsoleEncoderTimestamp(e.Encoder)
|
||||
}
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ zapcore.Encoder = (*JournaldEncoder)(nil)
|
||||
_ caddyfile.Unmarshaler = (*JournaldEncoder)(nil)
|
||||
_ caddy.ConfiguresFormatterDefault = (*JournaldEncoder)(nil)
|
||||
)
|
||||
@@ -0,0 +1,155 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap/buffer"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func TestJournaldPriorityPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
level zapcore.Level
|
||||
want string
|
||||
}{
|
||||
{level: zapcore.InvalidLevel, want: "<6>"},
|
||||
{level: zapcore.DebugLevel, want: "<7>"},
|
||||
{level: zapcore.InfoLevel, want: "<6>"},
|
||||
{level: zapcore.WarnLevel, want: "<4>"},
|
||||
{level: zapcore.ErrorLevel, want: "<3>"},
|
||||
{level: zapcore.DPanicLevel, want: "<2>"},
|
||||
{level: zapcore.PanicLevel, want: "<2>"},
|
||||
{level: zapcore.FatalLevel, want: "<2>"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.level.String(), func(t *testing.T) {
|
||||
if got := journaldPriorityPrefix(tt.level); got != tt.want {
|
||||
t.Fatalf("got %s, want %s", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJournaldEncoderEncodeEntry(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
level zapcore.Level
|
||||
want string
|
||||
}{
|
||||
{name: "debug", level: zapcore.DebugLevel, want: "<7>wrapped\n"},
|
||||
{name: "info", level: zapcore.InfoLevel, want: "<6>wrapped\n"},
|
||||
{name: "warn", level: zapcore.WarnLevel, want: "<4>wrapped\n"},
|
||||
{name: "error", level: zapcore.ErrorLevel, want: "<3>wrapped\n"},
|
||||
{name: "panic", level: zapcore.PanicLevel, want: "<2>wrapped\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
enc := JournaldEncoder{Encoder: staticEncoder{output: "wrapped\n"}}
|
||||
buf, err := enc.EncodeEntry(zapcore.Entry{Level: tt.level}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeEntry() error = %v", err)
|
||||
}
|
||||
defer buf.Free()
|
||||
|
||||
if got := buf.String(); got != tt.want {
|
||||
t.Fatalf("got %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJournaldEncoderUnmarshalCaddyfile(t *testing.T) {
|
||||
d := caddyfile.NewTestDispenser(`
|
||||
journald {
|
||||
wrap console
|
||||
}
|
||||
`)
|
||||
|
||||
var enc JournaldEncoder
|
||||
if err := enc.UnmarshalCaddyfile(d); err != nil {
|
||||
t.Fatalf("UnmarshalCaddyfile() error = %v", err)
|
||||
}
|
||||
|
||||
var got map[string]any
|
||||
if err := json.Unmarshal(enc.WrappedRaw, &got); err != nil {
|
||||
t.Fatalf("unmarshal wrapped encoder: %v", err)
|
||||
}
|
||||
|
||||
if got["format"] != "console" {
|
||||
t.Fatalf("wrapped format = %v, want console", got["format"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestJournaldEncoderPreservesJSONTimestamp(t *testing.T) {
|
||||
enc := &JournaldEncoder{
|
||||
Encoder: &JSONEncoder{},
|
||||
}
|
||||
if err := enc.Provision(caddy.Context{Context: context.Background()}); err != nil {
|
||||
t.Fatalf("Provision() error = %v", err)
|
||||
}
|
||||
|
||||
buf, err := enc.EncodeEntry(zapcore.Entry{
|
||||
Level: zapcore.InfoLevel,
|
||||
Time: fixedEntryTime(),
|
||||
Message: "hello",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeEntry() error = %v", err)
|
||||
}
|
||||
defer buf.Free()
|
||||
|
||||
got := buf.String()
|
||||
if !strings.Contains(got, `"ts"`) {
|
||||
t.Fatalf("got JSON output without ts field: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJournaldEncoderSuppressesConsoleTimestamp(t *testing.T) {
|
||||
enc := &JournaldEncoder{
|
||||
Encoder: &ConsoleEncoder{},
|
||||
}
|
||||
if err := enc.Provision(caddy.Context{Context: context.Background()}); err != nil {
|
||||
t.Fatalf("Provision() error = %v", err)
|
||||
}
|
||||
|
||||
buf, err := enc.EncodeEntry(zapcore.Entry{
|
||||
Level: zapcore.InfoLevel,
|
||||
Time: fixedEntryTime(),
|
||||
Message: "hello",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("EncodeEntry() error = %v", err)
|
||||
}
|
||||
defer buf.Free()
|
||||
|
||||
got := buf.String()
|
||||
if strings.Contains(got, "2001/02/03") {
|
||||
t.Fatalf("got console output with timestamp: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
type staticEncoder struct {
|
||||
nopEncoder
|
||||
output string
|
||||
}
|
||||
|
||||
func (se staticEncoder) Clone() zapcore.Encoder { return se }
|
||||
|
||||
func (se staticEncoder) EncodeEntry(zapcore.Entry, []zapcore.Field) (*buffer.Buffer, error) {
|
||||
buf := bufferpool.Get()
|
||||
buf.AppendString(se.output)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func fixedEntryTime() (ts time.Time) {
|
||||
return time.Date(2001, 2, 3, 4, 5, 6, 0, time.UTC)
|
||||
}
|
||||
Reference in New Issue
Block a user