mirror of
https://github.com/caddyserver/caddy.git
synced 2025-11-11 17:16:58 -05:00
Merge branch 'master' into trace-servicegraph
This commit is contained in:
commit
a0b1a8de3d
39
.github/workflows/ci.yml
vendored
39
.github/workflows/ci.yml
vendored
@ -23,18 +23,18 @@ jobs:
|
|||||||
- mac
|
- mac
|
||||||
- windows
|
- windows
|
||||||
go:
|
go:
|
||||||
- '1.21'
|
|
||||||
- '1.22'
|
- '1.22'
|
||||||
|
- '1.23'
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.21'
|
|
||||||
GO_SEMVER: '~1.21.0'
|
|
||||||
|
|
||||||
- go: '1.22'
|
- go: '1.22'
|
||||||
GO_SEMVER: '~1.22.3'
|
GO_SEMVER: '~1.22.3'
|
||||||
|
|
||||||
|
- go: '1.23'
|
||||||
|
GO_SEMVER: '~1.23.0'
|
||||||
|
|
||||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||||
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
|
# OS_LABEL: the VM label from GitHub Actions (see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)
|
||||||
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
||||||
@ -99,7 +99,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
run: |
|
run: |
|
||||||
go build -tags nobdger -trimpath -ldflags="-w -s" -v
|
go build -tags nobadger -trimpath -ldflags="-w -s" -v
|
||||||
|
|
||||||
- name: Smoke test Caddy
|
- name: Smoke test Caddy
|
||||||
working-directory: ./cmd/caddy
|
working-directory: ./cmd/caddy
|
||||||
@ -150,18 +150,41 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
|
set +e
|
||||||
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
|
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
|
||||||
|
|
||||||
# short sha is enough?
|
# short sha is enough?
|
||||||
short_sha=$(git rev-parse --short HEAD)
|
short_sha=$(git rev-parse --short HEAD)
|
||||||
|
|
||||||
|
# To shorten the following lines
|
||||||
|
ssh_opts="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
|
||||||
|
ssh_host="$CI_USER@ci-s390x.caddyserver.com"
|
||||||
|
|
||||||
# The environment is fresh, so there's no point in keeping accepting and adding the key.
|
# The environment is fresh, so there's no point in keeping accepting and adding the key.
|
||||||
rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . "$CI_USER"@ci-s390x.caddyserver.com:/var/tmp/"$short_sha"
|
rsync -arz -e "ssh $ssh_opts" --progress --delete --exclude '.git' . "$ssh_host":/var/tmp/"$short_sha"
|
||||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t "$CI_USER"@ci-s390x.caddyserver.com "cd /var/tmp/$short_sha; go version; go env; printf "\n\n";CGO_ENABLED=0 go test -tags nobadger -v ./..."
|
ssh $ssh_opts -t "$ssh_host" bash <<EOF
|
||||||
|
cd /var/tmp/$short_sha
|
||||||
|
go version
|
||||||
|
go env
|
||||||
|
printf "\n\n"
|
||||||
|
retries=3
|
||||||
|
exit_code=0
|
||||||
|
while ((retries > 0)); do
|
||||||
|
CGO_ENABLED=0 go test -p 1 -tags nobadger -v ./...
|
||||||
|
exit_code=$?
|
||||||
|
if ((exit_code == 0)); then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "\n\nTest failed: \$exit_code, retrying..."
|
||||||
|
((retries--))
|
||||||
|
done
|
||||||
|
echo "Remote exit code: \$exit_code"
|
||||||
|
exit \$exit_code
|
||||||
|
EOF
|
||||||
test_result=$?
|
test_result=$?
|
||||||
|
|
||||||
# There's no need leaving the files around
|
# There's no need leaving the files around
|
||||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "$CI_USER"@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'"
|
ssh $ssh_opts "$ssh_host" "rm -rf /var/tmp/'$short_sha'"
|
||||||
|
|
||||||
echo "Test exit code: $test_result"
|
echo "Test exit code: $test_result"
|
||||||
exit $test_result
|
exit $test_result
|
||||||
|
|||||||
4
.github/workflows/cross-build.yml
vendored
4
.github/workflows/cross-build.yml
vendored
@ -28,6 +28,7 @@ jobs:
|
|||||||
- 'netbsd'
|
- 'netbsd'
|
||||||
go:
|
go:
|
||||||
- '1.22'
|
- '1.22'
|
||||||
|
- '1.23'
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
@ -35,6 +36,9 @@ jobs:
|
|||||||
- go: '1.22'
|
- go: '1.22'
|
||||||
GO_SEMVER: '~1.22.3'
|
GO_SEMVER: '~1.22.3'
|
||||||
|
|
||||||
|
- go: '1.23'
|
||||||
|
GO_SEMVER: '~1.23.0'
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@ -43,13 +43,13 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '~1.22.3'
|
go-version: '~1.23'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: v1.55
|
version: latest
|
||||||
|
|
||||||
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
# Windows times out frequently after about 5m50s if we don't set a longer timeout.
|
||||||
args: --timeout 10m
|
args: --timeout 10m
|
||||||
@ -63,5 +63,5 @@ jobs:
|
|||||||
- name: govulncheck
|
- name: govulncheck
|
||||||
uses: golang/govulncheck-action@v1
|
uses: golang/govulncheck-action@v1
|
||||||
with:
|
with:
|
||||||
go-version-input: '~1.22.3'
|
go-version-input: '~1.23.0'
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -13,13 +13,13 @@ jobs:
|
|||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
go:
|
go:
|
||||||
- '1.22'
|
- '1.23'
|
||||||
|
|
||||||
include:
|
include:
|
||||||
# Set the minimum Go patch version for the given Go minor
|
# Set the minimum Go patch version for the given Go minor
|
||||||
# Usable via ${{ matrix.GO_SEMVER }}
|
# Usable via ${{ matrix.GO_SEMVER }}
|
||||||
- go: '1.22'
|
- go: '1.23'
|
||||||
GO_SEMVER: '~1.22.3'
|
GO_SEMVER: '~1.23.0'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
errcheck:
|
errcheck:
|
||||||
ignore: fmt:.*,go.uber.org/zap/zapcore:^Add.*
|
exclude-functions:
|
||||||
ignoretests: true
|
- fmt.*
|
||||||
|
- (go.uber.org/zap/zapcore.ObjectEncoder).AddObject
|
||||||
|
- (go.uber.org/zap/zapcore.ObjectEncoder).AddArray
|
||||||
gci:
|
gci:
|
||||||
sections:
|
sections:
|
||||||
- standard # Standard section: captures all standard packages.
|
- standard # Standard section: captures all standard packages.
|
||||||
@ -33,7 +35,6 @@ linters:
|
|||||||
- errcheck
|
- errcheck
|
||||||
- errname
|
- errname
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- exportloopref
|
|
||||||
- gci
|
- gci
|
||||||
- gofmt
|
- gofmt
|
||||||
- goimports
|
- goimports
|
||||||
@ -130,18 +131,22 @@ linters:
|
|||||||
run:
|
run:
|
||||||
# default concurrency is a available CPU number.
|
# default concurrency is a available CPU number.
|
||||||
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
|
# concurrency: 4 # explicitly omit this value to fully utilize available resources.
|
||||||
deadline: 5m
|
timeout: 5m
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
tests: false
|
tests: false
|
||||||
|
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
format: 'colored-line-number'
|
formats:
|
||||||
|
- format: 'colored-line-number'
|
||||||
print-issued-lines: true
|
print-issued-lines: true
|
||||||
print-linter-name: true
|
print-linter-name: true
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
- text: 'G115' # TODO: Either we should fix the issues or nuke the linter if it's bad
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
# we aren't calling unknown URL
|
# we aren't calling unknown URL
|
||||||
- text: 'G107' # G107: Url provided to HTTP request as taint input
|
- text: 'G107' # G107: Url provided to HTTP request as taint input
|
||||||
linters:
|
linters:
|
||||||
@ -166,3 +171,12 @@ issues:
|
|||||||
- path: modules/logging/filters.go
|
- path: modules/logging/filters.go
|
||||||
linters:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
|
- path: modules/caddyhttp/matchers.go
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- path: modules/caddyhttp/vars.go
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
|||||||
@ -12,6 +12,10 @@ before:
|
|||||||
- mkdir -p caddy-build
|
- mkdir -p caddy-build
|
||||||
- cp cmd/caddy/main.go caddy-build/main.go
|
- cp cmd/caddy/main.go caddy-build/main.go
|
||||||
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
- /bin/sh -c 'cd ./caddy-build && go mod init caddy'
|
||||||
|
# prepare syso files for windows embedding
|
||||||
|
- go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
|
||||||
|
- /bin/sh -c 'for a in amd64 arm arm64; do XCADDY_SKIP_BUILD=1 GOOS=windows GOARCH=$a $GOPATH/bin/xcaddy build {{.Env.TAG}}; done'
|
||||||
|
- /bin/sh -c 'mv /tmp/buildenv_*/*.syso caddy-build'
|
||||||
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
|
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
|
||||||
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
|
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
|
||||||
- go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod
|
- go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod
|
||||||
@ -31,7 +35,6 @@ builds:
|
|||||||
- env:
|
- env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GO111MODULE=on
|
- GO111MODULE=on
|
||||||
main: main.go
|
|
||||||
dir: ./caddy-build
|
dir: ./caddy-build
|
||||||
binary: caddy
|
binary: caddy
|
||||||
goos:
|
goos:
|
||||||
|
|||||||
@ -87,7 +87,7 @@ See [our online documentation](https://caddyserver.com/docs/install) for other i
|
|||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- [Go 1.21 or newer](https://golang.org/dl/)
|
- [Go 1.22.3 or newer](https://golang.org/dl/)
|
||||||
|
|
||||||
### For development
|
### For development
|
||||||
|
|
||||||
|
|||||||
23
admin.go
23
admin.go
@ -34,6 +34,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -312,7 +313,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
|||||||
}
|
}
|
||||||
if admin.Origins == nil {
|
if admin.Origins == nil {
|
||||||
if addr.isLoopback() {
|
if addr.isLoopback() {
|
||||||
if addr.IsUnixNetwork() {
|
if addr.IsUnixNetwork() || addr.IsFdNetwork() {
|
||||||
// RFC 2616, Section 14.26:
|
// RFC 2616, Section 14.26:
|
||||||
// "A client MUST include a Host header field in all HTTP/1.1 request
|
// "A client MUST include a Host header field in all HTTP/1.1 request
|
||||||
// messages. If the requested URI does not include an Internet host
|
// messages. If the requested URI does not include an Internet host
|
||||||
@ -350,7 +351,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
|
|||||||
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !addr.IsUnixNetwork() {
|
if !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
|
||||||
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -675,13 +676,7 @@ func (remote RemoteAdmin) enforceAccessControls(r *http.Request) error {
|
|||||||
// key recognized; make sure its HTTP request is permitted
|
// key recognized; make sure its HTTP request is permitted
|
||||||
for _, accessPerm := range adminAccess.Permissions {
|
for _, accessPerm := range adminAccess.Permissions {
|
||||||
// verify method
|
// verify method
|
||||||
methodFound := accessPerm.Methods == nil
|
methodFound := accessPerm.Methods == nil || slices.Contains(accessPerm.Methods, r.Method)
|
||||||
for _, method := range accessPerm.Methods {
|
|
||||||
if method == r.Method {
|
|
||||||
methodFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !methodFound {
|
if !methodFound {
|
||||||
return APIError{
|
return APIError{
|
||||||
HTTPStatus: http.StatusForbidden,
|
HTTPStatus: http.StatusForbidden,
|
||||||
@ -877,13 +872,9 @@ func (h adminHandler) handleError(w http.ResponseWriter, r *http.Request, err er
|
|||||||
// a trustworthy/expected value. This helps to mitigate DNS
|
// a trustworthy/expected value. This helps to mitigate DNS
|
||||||
// rebinding attacks.
|
// rebinding attacks.
|
||||||
func (h adminHandler) checkHost(r *http.Request) error {
|
func (h adminHandler) checkHost(r *http.Request) error {
|
||||||
var allowed bool
|
allowed := slices.ContainsFunc(h.allowedOrigins, func(u *url.URL) bool {
|
||||||
for _, allowedOrigin := range h.allowedOrigins {
|
return r.Host == u.Host
|
||||||
if r.Host == allowedOrigin.Host {
|
})
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !allowed {
|
if !allowed {
|
||||||
return APIError{
|
return APIError{
|
||||||
HTTPStatus: http.StatusForbidden,
|
HTTPStatus: http.StatusForbidden,
|
||||||
|
|||||||
@ -415,7 +415,7 @@ func (d *Dispenser) EOFErr() error {
|
|||||||
|
|
||||||
// Err generates a custom parse-time error with a message of msg.
|
// Err generates a custom parse-time error with a message of msg.
|
||||||
func (d *Dispenser) Err(msg string) error {
|
func (d *Dispenser) Err(msg string) error {
|
||||||
return d.Errf(msg)
|
return d.WrapErr(errors.New(msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errf is like Err, but for formatted error messages
|
// Errf is like Err, but for formatted error messages
|
||||||
|
|||||||
@ -16,6 +16,7 @@ package caddyfile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type adjacency map[string][]string
|
type adjacency map[string][]string
|
||||||
@ -91,12 +92,7 @@ func (i *importGraph) areConnected(from, to string) bool {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, v := range al {
|
return slices.Contains(al, to)
|
||||||
if v == to {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *importGraph) willCycle(from, to string) bool {
|
func (i *importGraph) willCycle(from, to string) bool {
|
||||||
|
|||||||
@ -77,10 +77,15 @@ import (
|
|||||||
// repetition may be undesirable, so call consolidateAddrMappings() to map
|
// repetition may be undesirable, so call consolidateAddrMappings() to map
|
||||||
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
// multiple addresses to the same lists of server blocks (a many:many mapping).
|
||||||
// (Doing this is essentially a map-reduce technique.)
|
// (Doing this is essentially a map-reduce technique.)
|
||||||
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
|
func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock,
|
||||||
options map[string]any,
|
options map[string]any,
|
||||||
) (map[string][]serverBlock, error) {
|
) (map[string]map[string][]serverBlock, error) {
|
||||||
sbmap := make(map[string][]serverBlock)
|
addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{}
|
||||||
|
|
||||||
|
type keyWithParsedKey struct {
|
||||||
|
key caddyfile.Token
|
||||||
|
parsedKey Address
|
||||||
|
}
|
||||||
|
|
||||||
for i, sblock := range originalServerBlocks {
|
for i, sblock := range originalServerBlocks {
|
||||||
// within a server block, we need to map all the listener addresses
|
// within a server block, we need to map all the listener addresses
|
||||||
@ -88,27 +93,48 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
|
|||||||
// will be served by them; this has the effect of treating each
|
// will be served by them; this has the effect of treating each
|
||||||
// key of a server block as its own, but without having to repeat its
|
// key of a server block as its own, but without having to repeat its
|
||||||
// contents in cases where multiple keys really can be served together
|
// contents in cases where multiple keys really can be served together
|
||||||
addrToKeys := make(map[string][]caddyfile.Token)
|
addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{}
|
||||||
for j, key := range sblock.block.Keys {
|
for j, key := range sblock.block.Keys {
|
||||||
|
parsedKey, err := ParseAddress(key.Text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing key: %v", err)
|
||||||
|
}
|
||||||
|
parsedKey = parsedKey.Normalize()
|
||||||
|
|
||||||
// a key can have multiple listener addresses if there are multiple
|
// a key can have multiple listener addresses if there are multiple
|
||||||
// arguments to the 'bind' directive (although they will all have
|
// arguments to the 'bind' directive (although they will all have
|
||||||
// the same port, since the port is defined by the key or is implicit
|
// the same port, since the port is defined by the key or is implicit
|
||||||
// through automatic HTTPS)
|
// through automatic HTTPS)
|
||||||
addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, options)
|
listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
|
return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// associate this key with each listener address it is served on
|
// associate this key with its protocols and each listener address served with them
|
||||||
for _, addr := range addrs {
|
kwpk := keyWithParsedKey{key, parsedKey}
|
||||||
addrToKeys[addr] = append(addrToKeys[addr], key)
|
for addr, protocols := range listeners {
|
||||||
|
protocolToKeyWithParsedKeys, ok := addrToProtocolToKeyWithParsedKeys[addr]
|
||||||
|
if !ok {
|
||||||
|
protocolToKeyWithParsedKeys = map[string][]keyWithParsedKey{}
|
||||||
|
addrToProtocolToKeyWithParsedKeys[addr] = protocolToKeyWithParsedKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// an empty protocol indicates the default, a nil or empty value in the ListenProtocols array
|
||||||
|
if len(protocols) == 0 {
|
||||||
|
protocols[""] = struct{}{}
|
||||||
|
}
|
||||||
|
for prot := range protocols {
|
||||||
|
protocolToKeyWithParsedKeys[prot] = append(
|
||||||
|
protocolToKeyWithParsedKeys[prot],
|
||||||
|
kwpk)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a slice of the map keys so we can iterate in sorted order
|
// make a slice of the map keys so we can iterate in sorted order
|
||||||
addrs := make([]string, 0, len(addrToKeys))
|
addrs := make([]string, 0, len(addrToProtocolToKeyWithParsedKeys))
|
||||||
for k := range addrToKeys {
|
for addr := range addrToProtocolToKeyWithParsedKeys {
|
||||||
addrs = append(addrs, k)
|
addrs = append(addrs, addr)
|
||||||
}
|
}
|
||||||
sort.Strings(addrs)
|
sort.Strings(addrs)
|
||||||
|
|
||||||
@ -118,85 +144,132 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
|
|||||||
// server block are only the ones which use the address; but
|
// server block are only the ones which use the address; but
|
||||||
// the contents (tokens) are of course the same
|
// the contents (tokens) are of course the same
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
keys := addrToKeys[addr]
|
protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr]
|
||||||
// parse keys so that we only have to do it once
|
|
||||||
parsedKeys := make([]Address, 0, len(keys))
|
prots := make([]string, 0, len(protocolToKeyWithParsedKeys))
|
||||||
for _, key := range keys {
|
for prot := range protocolToKeyWithParsedKeys {
|
||||||
addr, err := ParseAddress(key.Text)
|
prots = append(prots, prot)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err)
|
|
||||||
}
|
}
|
||||||
parsedKeys = append(parsedKeys, addr.Normalize())
|
sort.Strings(prots)
|
||||||
|
|
||||||
|
protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr]
|
||||||
|
if !ok {
|
||||||
|
protocolToServerBlocks = map[string][]serverBlock{}
|
||||||
|
addrToProtocolToServerBlocks[addr] = protocolToServerBlocks
|
||||||
}
|
}
|
||||||
sbmap[addr] = append(sbmap[addr], serverBlock{
|
|
||||||
|
for _, prot := range prots {
|
||||||
|
keyWithParsedKeys := protocolToKeyWithParsedKeys[prot]
|
||||||
|
|
||||||
|
keys := make([]caddyfile.Token, len(keyWithParsedKeys))
|
||||||
|
parsedKeys := make([]Address, len(keyWithParsedKeys))
|
||||||
|
|
||||||
|
for k, keyWithParsedKey := range keyWithParsedKeys {
|
||||||
|
keys[k] = keyWithParsedKey.key
|
||||||
|
parsedKeys[k] = keyWithParsedKey.parsedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{
|
||||||
block: caddyfile.ServerBlock{
|
block: caddyfile.ServerBlock{
|
||||||
Keys: keys,
|
Keys: keys,
|
||||||
Segments: sblock.block.Segments,
|
Segments: sblock.block.Segments,
|
||||||
},
|
},
|
||||||
pile: sblock.pile,
|
pile: sblock.pile,
|
||||||
keys: parsedKeys,
|
parsedKeys: parsedKeys,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sbmap, nil
|
return addrToProtocolToServerBlocks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
|
// consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
|
||||||
// single listener addresses to lists of server blocks. Since multiple addresses may serve
|
// single listener addresses to protocols to lists of server blocks. Since multiple addresses
|
||||||
// identical sites (server block contents), this function turns a 1:many mapping into a
|
// may serve multiple protocols to identical sites (server block contents), this function turns
|
||||||
// many:many mapping. Server block contents (tokens) must be exactly identical so that
|
// a 1:many mapping into a many:many mapping. Server block contents (tokens) must be
|
||||||
// reflect.DeepEqual returns true in order for the addresses to be combined. Identical
|
// exactly identical so that reflect.DeepEqual returns true in order for the addresses to be combined.
|
||||||
// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
|
// Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
|
||||||
// association from multiple addresses to multiple server blocks; i.e. each element of
|
// association from multiple addresses to multiple server blocks; i.e. each element of
|
||||||
// the returned slice) becomes a server definition in the output JSON.
|
// the returned slice) becomes a server definition in the output JSON.
|
||||||
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
|
func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation {
|
||||||
sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
|
sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks))
|
||||||
for addr, sblocks := range addrToServerBlocks {
|
|
||||||
// we start with knowing that at least this address
|
addrs := make([]string, 0, len(addrToProtocolToServerBlocks))
|
||||||
// maps to these server blocks
|
for addr := range addrToProtocolToServerBlocks {
|
||||||
a := sbAddrAssociation{
|
addrs = append(addrs, addr)
|
||||||
addresses: []string{addr},
|
|
||||||
serverBlocks: sblocks,
|
|
||||||
}
|
}
|
||||||
|
sort.Strings(addrs)
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
protocolToServerBlocks := addrToProtocolToServerBlocks[addr]
|
||||||
|
|
||||||
|
prots := make([]string, 0, len(protocolToServerBlocks))
|
||||||
|
for prot := range protocolToServerBlocks {
|
||||||
|
prots = append(prots, prot)
|
||||||
|
}
|
||||||
|
sort.Strings(prots)
|
||||||
|
|
||||||
|
for _, prot := range prots {
|
||||||
|
serverBlocks := protocolToServerBlocks[prot]
|
||||||
|
|
||||||
// now find other addresses that map to identical
|
// now find other addresses that map to identical
|
||||||
// server blocks and add them to our list of
|
// server blocks and add them to our map of listener
|
||||||
// addresses, while removing them from the map
|
// addresses and protocols, while removing them from
|
||||||
for otherAddr, otherSblocks := range addrToServerBlocks {
|
// the original map
|
||||||
if addr == otherAddr {
|
listeners := map[string]map[string]struct{}{}
|
||||||
continue
|
|
||||||
}
|
|
||||||
if reflect.DeepEqual(sblocks, otherSblocks) {
|
|
||||||
a.addresses = append(a.addresses, otherAddr)
|
|
||||||
delete(addrToServerBlocks, otherAddr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(a.addresses)
|
|
||||||
|
|
||||||
sbaddrs = append(sbaddrs, a)
|
for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks {
|
||||||
|
for otherProt, otherServerBlocks := range otherProtocolToServerBlocks {
|
||||||
|
if addr == otherAddr && prot == otherProt || reflect.DeepEqual(serverBlocks, otherServerBlocks) {
|
||||||
|
listener, ok := listeners[otherAddr]
|
||||||
|
if !ok {
|
||||||
|
listener = map[string]struct{}{}
|
||||||
|
listeners[otherAddr] = listener
|
||||||
|
}
|
||||||
|
listener[otherProt] = struct{}{}
|
||||||
|
delete(otherProtocolToServerBlocks, otherProt)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort them by their first address (we know there will always be at least one)
|
addresses := make([]string, 0, len(listeners))
|
||||||
// to avoid problems with non-deterministic ordering (makes tests flaky)
|
for lnAddr := range listeners {
|
||||||
sort.Slice(sbaddrs, func(i, j int) bool {
|
addresses = append(addresses, lnAddr)
|
||||||
return sbaddrs[i].addresses[0] < sbaddrs[j].addresses[0]
|
}
|
||||||
|
sort.Strings(addresses)
|
||||||
|
|
||||||
|
addressesWithProtocols := make([]addressWithProtocols, 0, len(listeners))
|
||||||
|
|
||||||
|
for _, lnAddr := range addresses {
|
||||||
|
lnProts := listeners[lnAddr]
|
||||||
|
prots := make([]string, 0, len(lnProts))
|
||||||
|
for prot := range lnProts {
|
||||||
|
prots = append(prots, prot)
|
||||||
|
}
|
||||||
|
sort.Strings(prots)
|
||||||
|
|
||||||
|
addressesWithProtocols = append(addressesWithProtocols, addressWithProtocols{
|
||||||
|
address: lnAddr,
|
||||||
|
protocols: prots,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sbaddrs = append(sbaddrs, sbAddrAssociation{
|
||||||
|
addressesWithProtocols: addressesWithProtocols,
|
||||||
|
serverBlocks: serverBlocks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sbaddrs
|
return sbaddrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// listenerAddrsForServerBlockKey essentially converts the Caddyfile
|
// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from
|
||||||
// site addresses to Caddy listener addresses for each server block.
|
// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block.
|
||||||
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
|
func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address,
|
||||||
options map[string]any,
|
options map[string]any,
|
||||||
) ([]string, error) {
|
) (map[string]map[string]struct{}, error) {
|
||||||
addr, err := ParseAddress(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing key: %v", err)
|
|
||||||
}
|
|
||||||
addr = addr.Normalize()
|
|
||||||
|
|
||||||
switch addr.Scheme {
|
switch addr.Scheme {
|
||||||
case "wss":
|
case "wss":
|
||||||
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
|
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
|
||||||
@ -230,55 +303,54 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
|||||||
|
|
||||||
// error if scheme and port combination violate convention
|
// error if scheme and port combination violate convention
|
||||||
if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
|
if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
|
||||||
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
|
return nil, fmt.Errorf("[%s] scheme and port violate convention", addr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// the bind directive specifies hosts (and potentially network), but is optional
|
// the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional
|
||||||
lnHosts := make([]string, 0, len(sblock.pile["bind"]))
|
lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"]))
|
||||||
for _, cfgVal := range sblock.pile["bind"] {
|
for _, cfgVal := range sblock.pile["bind"] {
|
||||||
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
|
if val, ok := cfgVal.Value.(addressesWithProtocols); ok {
|
||||||
|
lnCfgVals = append(lnCfgVals, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(lnCfgVals) == 0 {
|
||||||
|
if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok {
|
||||||
|
for _, defaultBindValue := range defaultBindValues {
|
||||||
|
lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols))
|
||||||
}
|
}
|
||||||
if len(lnHosts) == 0 {
|
|
||||||
if defaultBind, ok := options["default_bind"].([]string); ok {
|
|
||||||
lnHosts = defaultBind
|
|
||||||
} else {
|
} else {
|
||||||
lnHosts = []string{""}
|
lnCfgVals = []addressesWithProtocols{{
|
||||||
|
addresses: []string{""},
|
||||||
|
protocols: nil,
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// use a map to prevent duplication
|
// use a map to prevent duplication
|
||||||
listeners := make(map[string]struct{})
|
listeners := map[string]map[string]struct{}{}
|
||||||
for _, lnHost := range lnHosts {
|
for _, lnCfgVal := range lnCfgVals {
|
||||||
// normally we would simply append the port,
|
for _, lnHost := range lnCfgVal.addresses {
|
||||||
// but if lnHost is IPv6, we need to ensure it
|
networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort)
|
||||||
// is enclosed in [ ]; net.JoinHostPort does
|
|
||||||
// this for us, but lnHost might also have a
|
|
||||||
// network type in front (e.g. "tcp/") leading
|
|
||||||
// to "[tcp/::1]" which causes parsing failures
|
|
||||||
// later; what we need is "tcp/[::1]", so we have
|
|
||||||
// to split the network and host, then re-combine
|
|
||||||
network, host, ok := strings.Cut(lnHost, "/")
|
|
||||||
if !ok {
|
|
||||||
host = network
|
|
||||||
network = ""
|
|
||||||
}
|
|
||||||
host = strings.Trim(host, "[]") // IPv6
|
|
||||||
networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
|
|
||||||
addr, err := caddy.ParseNetworkAddress(networkAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing network address: %v", err)
|
return nil, fmt.Errorf("parsing network address: %v", err)
|
||||||
}
|
}
|
||||||
listeners[addr.String()] = struct{}{}
|
if _, ok := listeners[addr.String()]; !ok {
|
||||||
|
listeners[networkAddr.String()] = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
for _, protocol := range lnCfgVal.protocols {
|
||||||
|
listeners[networkAddr.String()][protocol] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// now turn map into list
|
return listeners, nil
|
||||||
listenersList := make([]string, 0, len(listeners))
|
|
||||||
for lnStr := range listeners {
|
|
||||||
listenersList = append(listenersList, lnStr)
|
|
||||||
}
|
}
|
||||||
sort.Strings(listenersList)
|
|
||||||
|
|
||||||
return listenersList, nil
|
// addressesWithProtocols associates a list of listen addresses
|
||||||
|
// with a list of protocols to serve them with
|
||||||
|
type addressesWithProtocols struct {
|
||||||
|
addresses []string
|
||||||
|
protocols []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address represents a site address. It contains
|
// Address represents a site address. It contains
|
||||||
|
|||||||
@ -56,10 +56,30 @@ func init() {
|
|||||||
|
|
||||||
// parseBind parses the bind directive. Syntax:
|
// parseBind parses the bind directive. Syntax:
|
||||||
//
|
//
|
||||||
// bind <addresses...>
|
// bind <addresses...> [{
|
||||||
|
// protocols [h1|h2|h2c|h3] [...]
|
||||||
|
// }]
|
||||||
func parseBind(h Helper) ([]ConfigValue, error) {
|
func parseBind(h Helper) ([]ConfigValue, error) {
|
||||||
h.Next() // consume directive name
|
h.Next() // consume directive name
|
||||||
return []ConfigValue{{Class: "bind", Value: h.RemainingArgs()}}, nil
|
var addresses, protocols []string
|
||||||
|
addresses = h.RemainingArgs()
|
||||||
|
|
||||||
|
for h.NextBlock(0) {
|
||||||
|
switch h.Val() {
|
||||||
|
case "protocols":
|
||||||
|
protocols = h.RemainingArgs()
|
||||||
|
if len(protocols) == 0 {
|
||||||
|
return nil, h.Errf("protocols requires one or more arguments")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, h.Errf("unknown subdirective: %s", h.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
|
||||||
|
addresses: addresses,
|
||||||
|
protocols: protocols,
|
||||||
|
}}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseTLS parses the tls directive. Syntax:
|
// parseTLS parses the tls directive. Syntax:
|
||||||
|
|||||||
@ -17,6 +17,7 @@ package httpcaddyfile
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -100,17 +101,6 @@ var defaultDirectiveOrder = []string{
|
|||||||
// plugins or by the user via the "order" global option.
|
// plugins or by the user via the "order" global option.
|
||||||
var directiveOrder = defaultDirectiveOrder
|
var directiveOrder = defaultDirectiveOrder
|
||||||
|
|
||||||
// directiveIsOrdered returns true if dir is
|
|
||||||
// a known, ordered (sorted) directive.
|
|
||||||
func directiveIsOrdered(dir string) bool {
|
|
||||||
for _, d := range directiveOrder {
|
|
||||||
if d == dir {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterDirective registers a unique directive dir with an
|
// RegisterDirective registers a unique directive dir with an
|
||||||
// associated unmarshaling (setup) function. When directive dir
|
// associated unmarshaling (setup) function. When directive dir
|
||||||
// is encountered in a Caddyfile, setupFunc will be called to
|
// is encountered in a Caddyfile, setupFunc will be called to
|
||||||
@ -161,7 +151,7 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
|
|||||||
// EXPERIMENTAL: This API may change or be removed.
|
// EXPERIMENTAL: This API may change or be removed.
|
||||||
func RegisterDirectiveOrder(dir string, position Positional, standardDir string) {
|
func RegisterDirectiveOrder(dir string, position Positional, standardDir string) {
|
||||||
// check if directive was already ordered
|
// check if directive was already ordered
|
||||||
if directiveIsOrdered(dir) {
|
if slices.Contains(directiveOrder, dir) {
|
||||||
panic("directive '" + dir + "' already ordered")
|
panic("directive '" + dir + "' already ordered")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,12 +162,7 @@ func RegisterDirectiveOrder(dir string, position Positional, standardDir string)
|
|||||||
// check if directive exists in standard distribution, since
|
// check if directive exists in standard distribution, since
|
||||||
// we can't allow plugins to depend on one another; we can't
|
// we can't allow plugins to depend on one another; we can't
|
||||||
// guarantee the order that plugins are loaded in.
|
// guarantee the order that plugins are loaded in.
|
||||||
foundStandardDir := false
|
foundStandardDir := slices.Contains(defaultDirectiveOrder, standardDir)
|
||||||
for _, d := range defaultDirectiveOrder {
|
|
||||||
if d == standardDir {
|
|
||||||
foundStandardDir = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !foundStandardDir {
|
if !foundStandardDir {
|
||||||
panic("the 3rd argument '" + standardDir + "' must be a directive that exists in the standard distribution of Caddy")
|
panic("the 3rd argument '" + standardDir + "' must be a directive that exists in the standard distribution of Caddy")
|
||||||
}
|
}
|
||||||
@ -533,7 +518,7 @@ func sortRoutes(routes []ConfigValue) {
|
|||||||
type serverBlock struct {
|
type serverBlock struct {
|
||||||
block caddyfile.ServerBlock
|
block caddyfile.ServerBlock
|
||||||
pile map[string][]ConfigValue // config values obtained from directives
|
pile map[string][]ConfigValue // config values obtained from directives
|
||||||
keys []Address
|
parsedKeys []Address
|
||||||
}
|
}
|
||||||
|
|
||||||
// hostsFromKeys returns a list of all the non-empty hostnames found in
|
// hostsFromKeys returns a list of all the non-empty hostnames found in
|
||||||
@ -550,7 +535,7 @@ type serverBlock struct {
|
|||||||
func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
||||||
// ensure each entry in our list is unique
|
// ensure each entry in our list is unique
|
||||||
hostMap := make(map[string]struct{})
|
hostMap := make(map[string]struct{})
|
||||||
for _, addr := range sb.keys {
|
for _, addr := range sb.parsedKeys {
|
||||||
if addr.Host == "" {
|
if addr.Host == "" {
|
||||||
if !loggerMode {
|
if !loggerMode {
|
||||||
// server block contains a key like ":443", i.e. the host portion
|
// server block contains a key like ":443", i.e. the host portion
|
||||||
@ -582,7 +567,7 @@ func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
|||||||
func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
||||||
// ensure each entry in our list is unique
|
// ensure each entry in our list is unique
|
||||||
hostMap := make(map[string]struct{})
|
hostMap := make(map[string]struct{})
|
||||||
for _, addr := range sb.keys {
|
for _, addr := range sb.parsedKeys {
|
||||||
if addr.Host == "" {
|
if addr.Host == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -603,23 +588,17 @@ func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
|
|||||||
// hasHostCatchAllKey returns true if sb has a key that
|
// hasHostCatchAllKey returns true if sb has a key that
|
||||||
// omits a host portion, i.e. it "catches all" hosts.
|
// omits a host portion, i.e. it "catches all" hosts.
|
||||||
func (sb serverBlock) hasHostCatchAllKey() bool {
|
func (sb serverBlock) hasHostCatchAllKey() bool {
|
||||||
for _, addr := range sb.keys {
|
return slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool {
|
||||||
if addr.Host == "" {
|
return addr.Host == ""
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isAllHTTP returns true if all sb keys explicitly specify
|
// isAllHTTP returns true if all sb keys explicitly specify
|
||||||
// the http:// scheme
|
// the http:// scheme
|
||||||
func (sb serverBlock) isAllHTTP() bool {
|
func (sb serverBlock) isAllHTTP() bool {
|
||||||
for _, addr := range sb.keys {
|
return !slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool {
|
||||||
if addr.Scheme != "http" {
|
return addr.Scheme != "http"
|
||||||
return false
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Positional are the supported modes for ordering directives.
|
// Positional are the supported modes for ordering directives.
|
||||||
|
|||||||
@ -78,7 +78,7 @@ func TestHostsFromKeys(t *testing.T) {
|
|||||||
[]string{"example.com:2015"},
|
[]string{"example.com:2015"},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
sb := serverBlock{keys: tc.keys}
|
sb := serverBlock{parsedKeys: tc.keys}
|
||||||
|
|
||||||
// test in normal mode
|
// test in normal mode
|
||||||
actual := sb.hostsFromKeys(false)
|
actual := sb.hostsFromKeys(false)
|
||||||
|
|||||||
@ -171,7 +171,7 @@ func (st ServerType) Setup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// map
|
// map
|
||||||
sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks, options)
|
sbmap, err := st.mapAddressToProtocolToServerBlocks(originalServerBlocks, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, warnings, err
|
return nil, warnings, err
|
||||||
}
|
}
|
||||||
@ -402,6 +402,20 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
|
|||||||
options[opt] = append(existingOpts, logOpts...)
|
options[opt] = append(existingOpts, logOpts...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Also fold multiple "default_bind" options together into an
|
||||||
|
// array so that server blocks can have multiple binds by default.
|
||||||
|
if opt == "default_bind" {
|
||||||
|
existingOpts, ok := options[opt].([]ConfigValue)
|
||||||
|
if !ok {
|
||||||
|
existingOpts = []ConfigValue{}
|
||||||
|
}
|
||||||
|
defaultBindOpts, ok := val.([]ConfigValue)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected type from 'default_bind' global options: %T", val)
|
||||||
|
}
|
||||||
|
options[opt] = append(existingOpts, defaultBindOpts...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
options[opt] = val
|
options[opt] = val
|
||||||
}
|
}
|
||||||
@ -520,8 +534,8 @@ func (st *ServerType) serversFromPairings(
|
|||||||
if hsp, ok := options["https_port"].(int); ok {
|
if hsp, ok := options["https_port"].(int); ok {
|
||||||
httpsPort = strconv.Itoa(hsp)
|
httpsPort = strconv.Itoa(hsp)
|
||||||
}
|
}
|
||||||
autoHTTPS := "on"
|
autoHTTPS := []string{}
|
||||||
if ah, ok := options["auto_https"].(string); ok {
|
if ah, ok := options["auto_https"].([]string); ok {
|
||||||
autoHTTPS = ah
|
autoHTTPS = ah
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -536,29 +550,81 @@ func (st *ServerType) serversFromPairings(
|
|||||||
if k == j {
|
if k == j {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if sliceContains(sblock2.block.GetKeysText(), key) {
|
if slices.Contains(sblock2.block.GetKeysText(), key) {
|
||||||
return nil, fmt.Errorf("ambiguous site definition: %s", key)
|
return nil, fmt.Errorf("ambiguous site definition: %s", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
addresses []string
|
||||||
|
protocols [][]string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, addressWithProtocols := range p.addressesWithProtocols {
|
||||||
|
addresses = append(addresses, addressWithProtocols.address)
|
||||||
|
protocols = append(protocols, addressWithProtocols.protocols)
|
||||||
|
}
|
||||||
|
|
||||||
srv := &caddyhttp.Server{
|
srv := &caddyhttp.Server{
|
||||||
Listen: p.addresses,
|
Listen: addresses,
|
||||||
|
ListenProtocols: protocols,
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove srv.ListenProtocols[j] if it only contains the default protocols
|
||||||
|
for j, lnProtocols := range srv.ListenProtocols {
|
||||||
|
srv.ListenProtocols[j] = nil
|
||||||
|
for _, lnProtocol := range lnProtocols {
|
||||||
|
if lnProtocol != "" {
|
||||||
|
srv.ListenProtocols[j] = lnProtocols
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove srv.ListenProtocols if it only contains the default protocols for all listen addresses
|
||||||
|
listenProtocols := srv.ListenProtocols
|
||||||
|
srv.ListenProtocols = nil
|
||||||
|
for _, lnProtocols := range listenProtocols {
|
||||||
|
if lnProtocols != nil {
|
||||||
|
srv.ListenProtocols = listenProtocols
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle the auto_https global option
|
// handle the auto_https global option
|
||||||
if autoHTTPS != "on" {
|
for _, val := range autoHTTPS {
|
||||||
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
switch val {
|
||||||
switch autoHTTPS {
|
|
||||||
case "off":
|
case "off":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
srv.AutoHTTPS.Disabled = true
|
srv.AutoHTTPS.Disabled = true
|
||||||
|
|
||||||
case "disable_redirects":
|
case "disable_redirects":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
srv.AutoHTTPS.DisableRedir = true
|
srv.AutoHTTPS.DisableRedir = true
|
||||||
|
|
||||||
case "disable_certs":
|
case "disable_certs":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
srv.AutoHTTPS.DisableCerts = true
|
srv.AutoHTTPS.DisableCerts = true
|
||||||
|
|
||||||
case "ignore_loaded_certs":
|
case "ignore_loaded_certs":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
srv.AutoHTTPS.IgnoreLoadedCerts = true
|
srv.AutoHTTPS.IgnoreLoadedCerts = true
|
||||||
|
|
||||||
|
case "prefer_wildcard":
|
||||||
|
if srv.AutoHTTPS == nil {
|
||||||
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
|
}
|
||||||
|
srv.AutoHTTPS.PreferWildcard = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,7 +632,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
// See ParseAddress() where parsing should later reject paths
|
// See ParseAddress() where parsing should later reject paths
|
||||||
// See https://github.com/caddyserver/caddy/pull/4728 for a full explanation
|
// See https://github.com/caddyserver/caddy/pull/4728 for a full explanation
|
||||||
for _, sblock := range p.serverBlocks {
|
for _, sblock := range p.serverBlocks {
|
||||||
for _, addr := range sblock.keys {
|
for _, addr := range sblock.parsedKeys {
|
||||||
if addr.Path != "" {
|
if addr.Path != "" {
|
||||||
caddy.Log().Named("caddyfile").Warn("Using a path in a site address is deprecated; please use the 'handle' directive instead", zap.String("address", addr.String()))
|
caddy.Log().Named("caddyfile").Warn("Using a path in a site address is deprecated; please use the 'handle' directive instead", zap.String("address", addr.String()))
|
||||||
}
|
}
|
||||||
@ -584,7 +650,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
var iLongestPath, jLongestPath string
|
var iLongestPath, jLongestPath string
|
||||||
var iLongestHost, jLongestHost string
|
var iLongestHost, jLongestHost string
|
||||||
var iWildcardHost, jWildcardHost bool
|
var iWildcardHost, jWildcardHost bool
|
||||||
for _, addr := range p.serverBlocks[i].keys {
|
for _, addr := range p.serverBlocks[i].parsedKeys {
|
||||||
if strings.Contains(addr.Host, "*") || addr.Host == "" {
|
if strings.Contains(addr.Host, "*") || addr.Host == "" {
|
||||||
iWildcardHost = true
|
iWildcardHost = true
|
||||||
}
|
}
|
||||||
@ -595,7 +661,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
iLongestPath = addr.Path
|
iLongestPath = addr.Path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, addr := range p.serverBlocks[j].keys {
|
for _, addr := range p.serverBlocks[j].parsedKeys {
|
||||||
if strings.Contains(addr.Host, "*") || addr.Host == "" {
|
if strings.Contains(addr.Host, "*") || addr.Host == "" {
|
||||||
jWildcardHost = true
|
jWildcardHost = true
|
||||||
}
|
}
|
||||||
@ -627,7 +693,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
})
|
})
|
||||||
|
|
||||||
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
|
var hasCatchAllTLSConnPolicy, addressQualifiesForTLS bool
|
||||||
autoHTTPSWillAddConnPolicy := autoHTTPS != "off"
|
autoHTTPSWillAddConnPolicy := srv.AutoHTTPS == nil || !srv.AutoHTTPS.Disabled
|
||||||
|
|
||||||
// if needed, the ServerLogConfig is initialized beforehand so
|
// if needed, the ServerLogConfig is initialized beforehand so
|
||||||
// that all server blocks can populate it with data, even when not
|
// that all server blocks can populate it with data, even when not
|
||||||
@ -711,7 +777,14 @@ func (st *ServerType) serversFromPairings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range sblock.keys {
|
wildcardHosts := []string{}
|
||||||
|
for _, addr := range sblock.parsedKeys {
|
||||||
|
if strings.HasPrefix(addr.Host, "*.") {
|
||||||
|
wildcardHosts = append(wildcardHosts, addr.Host[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range sblock.parsedKeys {
|
||||||
// if server only uses HTTP port, auto-HTTPS will not apply
|
// if server only uses HTTP port, auto-HTTPS will not apply
|
||||||
if listenersUseAnyPortOtherThan(srv.Listen, httpPort) {
|
if listenersUseAnyPortOtherThan(srv.Listen, httpPort) {
|
||||||
// exclude any hosts that were defined explicitly with "http://"
|
// exclude any hosts that were defined explicitly with "http://"
|
||||||
@ -720,12 +793,24 @@ func (st *ServerType) serversFromPairings(
|
|||||||
if srv.AutoHTTPS == nil {
|
if srv.AutoHTTPS == nil {
|
||||||
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
|
||||||
}
|
}
|
||||||
if !sliceContains(srv.AutoHTTPS.Skip, addr.Host) {
|
if !slices.Contains(srv.AutoHTTPS.Skip, addr.Host) {
|
||||||
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
|
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If prefer wildcard is enabled, then we add hosts that are
|
||||||
|
// already covered by the wildcard to the skip list
|
||||||
|
if srv.AutoHTTPS != nil && srv.AutoHTTPS.PreferWildcard && addr.Scheme == "https" {
|
||||||
|
baseDomain := addr.Host
|
||||||
|
if idx := strings.Index(baseDomain, "."); idx != -1 {
|
||||||
|
baseDomain = baseDomain[idx+1:]
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(addr.Host, "*.") && slices.Contains(wildcardHosts, baseDomain) {
|
||||||
|
srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, addr.Host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If TLS is specified as directive, it will also result in 1 or more connection policy being created
|
// If TLS is specified as directive, it will also result in 1 or more connection policy being created
|
||||||
// Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without
|
// Thus, catch-all address with non-standard port, e.g. :8443, can have TLS enabled without
|
||||||
// specifying prefix "https://"
|
// specifying prefix "https://"
|
||||||
@ -734,7 +819,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
// https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761
|
// https://caddy.community/t/making-sense-of-auto-https-and-why-disabling-it-still-serves-https-instead-of-http/9761
|
||||||
createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"]
|
createdTLSConnPolicies, ok := sblock.pile["tls.connection_policy"]
|
||||||
hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) ||
|
hasTLSEnabled := (ok && len(createdTLSConnPolicies) > 0) ||
|
||||||
(addr.Host != "" && srv.AutoHTTPS != nil && !sliceContains(srv.AutoHTTPS.Skip, addr.Host))
|
(addr.Host != "" && srv.AutoHTTPS != nil && !slices.Contains(srv.AutoHTTPS.Skip, addr.Host))
|
||||||
|
|
||||||
// we'll need to remember if the address qualifies for auto-HTTPS, so we
|
// we'll need to remember if the address qualifies for auto-HTTPS, so we
|
||||||
// can add a TLS conn policy if necessary
|
// can add a TLS conn policy if necessary
|
||||||
@ -873,7 +958,10 @@ func (st *ServerType) serversFromPairings(
|
|||||||
if addressQualifiesForTLS &&
|
if addressQualifiesForTLS &&
|
||||||
!hasCatchAllTLSConnPolicy &&
|
!hasCatchAllTLSConnPolicy &&
|
||||||
(len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") {
|
(len(srv.TLSConnPolicies) > 0 || !autoHTTPSWillAddConnPolicy || defaultSNI != "" || fallbackSNI != "") {
|
||||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{DefaultSNI: defaultSNI, FallbackSNI: fallbackSNI})
|
srv.TLSConnPolicies = append(srv.TLSConnPolicies, &caddytls.ConnectionPolicy{
|
||||||
|
DefaultSNI: defaultSNI,
|
||||||
|
FallbackSNI: fallbackSNI,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// tidy things up a bit
|
// tidy things up a bit
|
||||||
@ -886,8 +974,7 @@ func (st *ServerType) serversFromPairings(
|
|||||||
servers[fmt.Sprintf("srv%d", i)] = srv
|
servers[fmt.Sprintf("srv%d", i)] = srv
|
||||||
}
|
}
|
||||||
|
|
||||||
err := applyServerOptions(servers, options, warnings)
|
if err := applyServerOptions(servers, options, warnings); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("applying global server options: %v", err)
|
return nil, fmt.Errorf("applying global server options: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -932,7 +1019,7 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, sblock := range serverBlocks {
|
for _, sblock := range serverBlocks {
|
||||||
for _, addr := range sblock.keys {
|
for _, addr := range sblock.parsedKeys {
|
||||||
if addr.Scheme == "http" || addr.Port == httpPort {
|
if addr.Scheme == "http" || addr.Port == httpPort {
|
||||||
if err := checkAndSetHTTP(addr); err != nil {
|
if err := checkAndSetHTTP(addr); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1061,7 +1148,7 @@ func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.Connecti
|
|||||||
} else if cps[i].CertSelection != nil && cps[j].CertSelection != nil {
|
} else if cps[i].CertSelection != nil && cps[j].CertSelection != nil {
|
||||||
// if both have one, then combine AnyTag
|
// if both have one, then combine AnyTag
|
||||||
for _, tag := range cps[j].CertSelection.AnyTag {
|
for _, tag := range cps[j].CertSelection.AnyTag {
|
||||||
if !sliceContains(cps[i].CertSelection.AnyTag, tag) {
|
if !slices.Contains(cps[i].CertSelection.AnyTag, tag) {
|
||||||
cps[i].CertSelection.AnyTag = append(cps[i].CertSelection.AnyTag, tag)
|
cps[i].CertSelection.AnyTag = append(cps[i].CertSelection.AnyTag, tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1144,7 +1231,7 @@ func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
|
|||||||
func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) {
|
func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) {
|
||||||
if needsSorting {
|
if needsSorting {
|
||||||
for _, val := range routes {
|
for _, val := range routes {
|
||||||
if !directiveIsOrdered(val.directive) {
|
if !slices.Contains(directiveOrder, val.directive) {
|
||||||
return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive)
|
return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1322,7 +1409,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
|
|||||||
var matcherPairs []*hostPathPair
|
var matcherPairs []*hostPathPair
|
||||||
|
|
||||||
var catchAllHosts bool
|
var catchAllHosts bool
|
||||||
for _, addr := range sblock.keys {
|
for _, addr := range sblock.parsedKeys {
|
||||||
// choose a matcher pair that should be shared by this
|
// choose a matcher pair that should be shared by this
|
||||||
// server block; if none exists yet, create one
|
// server block; if none exists yet, create one
|
||||||
var chosenMatcherPair *hostPathPair
|
var chosenMatcherPair *hostPathPair
|
||||||
@ -1354,19 +1441,10 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
|
|||||||
|
|
||||||
// add this server block's keys to the matcher
|
// add this server block's keys to the matcher
|
||||||
// pair if it doesn't already exist
|
// pair if it doesn't already exist
|
||||||
if addr.Host != "" {
|
if addr.Host != "" && !slices.Contains(chosenMatcherPair.hostm, addr.Host) {
|
||||||
var found bool
|
|
||||||
for _, h := range chosenMatcherPair.hostm {
|
|
||||||
if h == addr.Host {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
chosenMatcherPair.hostm = append(chosenMatcherPair.hostm, addr.Host)
|
chosenMatcherPair.hostm = append(chosenMatcherPair.hostm, addr.Host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// iterate each pairing of host and path matchers and
|
// iterate each pairing of host and path matchers and
|
||||||
// put them into a map for JSON encoding
|
// put them into a map for JSON encoding
|
||||||
@ -1540,16 +1618,6 @@ func tryDuration(val any, warnings *[]caddyconfig.Warning) caddy.Duration {
|
|||||||
return durationVal
|
return durationVal
|
||||||
}
|
}
|
||||||
|
|
||||||
// sliceContains returns true if needle is in haystack.
|
|
||||||
func sliceContains(haystack []string, needle string) bool {
|
|
||||||
for _, s := range haystack {
|
|
||||||
if s == needle {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// listenersUseAnyPortOtherThan returns true if there are any
|
// listenersUseAnyPortOtherThan returns true if there are any
|
||||||
// listeners in addresses that use a port which is not otherPort.
|
// listeners in addresses that use a port which is not otherPort.
|
||||||
// Mostly borrowed from unexported method in caddyhttp package.
|
// Mostly borrowed from unexported method in caddyhttp package.
|
||||||
@ -1613,11 +1681,18 @@ type namedCustomLog struct {
|
|||||||
noHostname bool
|
noHostname bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addressWithProtocols associates a listen address with
|
||||||
|
// the protocols to serve it with
|
||||||
|
type addressWithProtocols struct {
|
||||||
|
address string
|
||||||
|
protocols []string
|
||||||
|
}
|
||||||
|
|
||||||
// sbAddrAssociation is a mapping from a list of
|
// sbAddrAssociation is a mapping from a list of
|
||||||
// addresses to a list of server blocks that are
|
// addresses with protocols, and a list of server
|
||||||
// served on those addresses.
|
// blocks that are served on those addresses.
|
||||||
type sbAddrAssociation struct {
|
type sbAddrAssociation struct {
|
||||||
addresses []string
|
addressesWithProtocols []addressWithProtocols
|
||||||
serverBlocks []serverBlock
|
serverBlocks []serverBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
package httpcaddyfile
|
package httpcaddyfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
@ -30,7 +31,7 @@ func init() {
|
|||||||
RegisterGlobalOption("debug", parseOptTrue)
|
RegisterGlobalOption("debug", parseOptTrue)
|
||||||
RegisterGlobalOption("http_port", parseOptHTTPPort)
|
RegisterGlobalOption("http_port", parseOptHTTPPort)
|
||||||
RegisterGlobalOption("https_port", parseOptHTTPSPort)
|
RegisterGlobalOption("https_port", parseOptHTTPSPort)
|
||||||
RegisterGlobalOption("default_bind", parseOptStringList)
|
RegisterGlobalOption("default_bind", parseOptDefaultBind)
|
||||||
RegisterGlobalOption("grace_period", parseOptDuration)
|
RegisterGlobalOption("grace_period", parseOptDuration)
|
||||||
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
RegisterGlobalOption("shutdown_delay", parseOptDuration)
|
||||||
RegisterGlobalOption("default_sni", parseOptSingleString)
|
RegisterGlobalOption("default_sni", parseOptSingleString)
|
||||||
@ -110,17 +111,12 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
}
|
}
|
||||||
pos := Positional(d.Val())
|
pos := Positional(d.Val())
|
||||||
|
|
||||||
newOrder := directiveOrder
|
// if directive already had an order, drop it
|
||||||
|
newOrder := slices.DeleteFunc(directiveOrder, func(d string) bool {
|
||||||
|
return d == dirName
|
||||||
|
})
|
||||||
|
|
||||||
// if directive exists, first remove it
|
// act on the positional; if it's First or Last, we're done right away
|
||||||
for i, d := range newOrder {
|
|
||||||
if d == dirName {
|
|
||||||
newOrder = append(newOrder[:i], newOrder[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// act on the positional
|
|
||||||
switch pos {
|
switch pos {
|
||||||
case First:
|
case First:
|
||||||
newOrder = append([]string{dirName}, newOrder...)
|
newOrder = append([]string{dirName}, newOrder...)
|
||||||
@ -129,6 +125,7 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
}
|
}
|
||||||
directiveOrder = newOrder
|
directiveOrder = newOrder
|
||||||
return newOrder, nil
|
return newOrder, nil
|
||||||
|
|
||||||
case Last:
|
case Last:
|
||||||
newOrder = append(newOrder, dirName)
|
newOrder = append(newOrder, dirName)
|
||||||
if d.NextArg() {
|
if d.NextArg() {
|
||||||
@ -136,8 +133,11 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
}
|
}
|
||||||
directiveOrder = newOrder
|
directiveOrder = newOrder
|
||||||
return newOrder, nil
|
return newOrder, nil
|
||||||
|
|
||||||
|
// if it's Before or After, continue
|
||||||
case Before:
|
case Before:
|
||||||
case After:
|
case After:
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, d.Errf("unknown positional '%s'", pos)
|
return nil, d.Errf("unknown positional '%s'", pos)
|
||||||
}
|
}
|
||||||
@ -151,17 +151,17 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return nil, d.ArgErr()
|
return nil, d.ArgErr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert directive into proper position
|
// get the position of the target directive
|
||||||
for i, d := range newOrder {
|
targetIndex := slices.Index(newOrder, otherDir)
|
||||||
if d == otherDir {
|
if targetIndex == -1 {
|
||||||
if pos == Before {
|
return nil, d.Errf("directive '%s' not found", otherDir)
|
||||||
newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...)
|
|
||||||
} else if pos == After {
|
|
||||||
newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
// if we're inserting after, we need to increment the index to go after
|
||||||
|
if pos == After {
|
||||||
|
targetIndex++
|
||||||
}
|
}
|
||||||
|
// insert the directive into the new order
|
||||||
|
newOrder = slices.Insert(newOrder, targetIndex, dirName)
|
||||||
|
|
||||||
directiveOrder = newOrder
|
directiveOrder = newOrder
|
||||||
|
|
||||||
@ -284,13 +284,32 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
d.Next() // consume option name
|
d.Next() // consume option name
|
||||||
val := d.RemainingArgs()
|
|
||||||
if len(val) == 0 {
|
var addresses, protocols []string
|
||||||
return "", d.ArgErr()
|
addresses = d.RemainingArgs()
|
||||||
|
|
||||||
|
if len(addresses) == 0 {
|
||||||
|
addresses = append(addresses, "")
|
||||||
}
|
}
|
||||||
return val, nil
|
|
||||||
|
for d.NextBlock(0) {
|
||||||
|
switch d.Val() {
|
||||||
|
case "protocols":
|
||||||
|
protocols = d.RemainingArgs()
|
||||||
|
if len(protocols) == 0 {
|
||||||
|
return nil, d.Errf("protocols requires one or more arguments")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, d.Errf("unknown subdirective: %s", d.Val())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
|
||||||
|
addresses: addresses,
|
||||||
|
protocols: protocols,
|
||||||
|
}}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
@ -433,15 +452,22 @@ func parseOptPersistConfig(d *caddyfile.Dispenser, _ any) (any, error) {
|
|||||||
|
|
||||||
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
func parseOptAutoHTTPS(d *caddyfile.Dispenser, _ any) (any, error) {
|
||||||
d.Next() // consume option name
|
d.Next() // consume option name
|
||||||
if !d.Next() {
|
val := d.RemainingArgs()
|
||||||
|
if len(val) == 0 {
|
||||||
return "", d.ArgErr()
|
return "", d.ArgErr()
|
||||||
}
|
}
|
||||||
val := d.Val()
|
for _, v := range val {
|
||||||
if d.Next() {
|
switch v {
|
||||||
return "", d.ArgErr()
|
case "off":
|
||||||
|
case "disable_redirects":
|
||||||
|
case "disable_certs":
|
||||||
|
case "ignore_loaded_certs":
|
||||||
|
case "prefer_wildcard":
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', 'ignore_loaded_certs', or 'prefer_wildcard'")
|
||||||
}
|
}
|
||||||
if val != "off" && val != "disable_redirects" && val != "disable_certs" && val != "ignore_loaded_certs" {
|
|
||||||
return "", d.Errf("auto_https must be one of 'off', 'disable_redirects', 'disable_certs', or 'ignore_loaded_certs'")
|
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ package httpcaddyfile
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
|
||||||
@ -180,7 +181,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" {
|
if proto != "h1" && proto != "h2" && proto != "h2c" && proto != "h3" {
|
||||||
return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto)
|
return nil, d.Errf("unknown protocol '%s': expected h1, h2, h2c, or h3", proto)
|
||||||
}
|
}
|
||||||
if sliceContains(serverOpts.Protocols, proto) {
|
if slices.Contains(serverOpts.Protocols, proto) {
|
||||||
return nil, d.Errf("protocol %s specified more than once", proto)
|
return nil, d.Errf("protocol %s specified more than once", proto)
|
||||||
}
|
}
|
||||||
serverOpts.Protocols = append(serverOpts.Protocols, proto)
|
serverOpts.Protocols = append(serverOpts.Protocols, proto)
|
||||||
@ -229,7 +230,7 @@ func unmarshalCaddyfileServerOptions(d *caddyfile.Dispenser) (any, error) {
|
|||||||
case "client_ip_headers":
|
case "client_ip_headers":
|
||||||
headers := d.RemainingArgs()
|
headers := d.RemainingArgs()
|
||||||
for _, header := range headers {
|
for _, header := range headers {
|
||||||
if sliceContains(serverOpts.ClientIPHeaders, header) {
|
if slices.Contains(serverOpts.ClientIPHeaders, header) {
|
||||||
return nil, d.Errf("client IP header %s specified more than once", header)
|
return nil, d.Errf("client IP header %s specified more than once", header)
|
||||||
}
|
}
|
||||||
serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header)
|
serverOpts.ClientIPHeaders = append(serverOpts.ClientIPHeaders, header)
|
||||||
@ -288,24 +289,15 @@ func applyServerOptions(
|
|||||||
|
|
||||||
for key, server := range servers {
|
for key, server := range servers {
|
||||||
// find the options that apply to this server
|
// find the options that apply to this server
|
||||||
opts := func() *serverOptions {
|
optsIndex := slices.IndexFunc(serverOpts, func(s serverOptions) bool {
|
||||||
for _, entry := range serverOpts {
|
return s.ListenerAddress == "" || slices.Contains(server.Listen, s.ListenerAddress)
|
||||||
if entry.ListenerAddress == "" {
|
})
|
||||||
return &entry
|
|
||||||
}
|
|
||||||
for _, listener := range server.Listen {
|
|
||||||
if entry.ListenerAddress == listener {
|
|
||||||
return &entry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
// if none apply, then move to the next server
|
// if none apply, then move to the next server
|
||||||
if opts == nil {
|
if optsIndex == -1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
opts := serverOpts[optsIndex]
|
||||||
|
|
||||||
// set all the options
|
// set all the options
|
||||||
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
|
server.ListenerWrappersRaw = opts.ListenerWrappersRaw
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -44,8 +45,8 @@ func (st ServerType) buildTLSApp(
|
|||||||
if hp, ok := options["http_port"].(int); ok {
|
if hp, ok := options["http_port"].(int); ok {
|
||||||
httpPort = strconv.Itoa(hp)
|
httpPort = strconv.Itoa(hp)
|
||||||
}
|
}
|
||||||
autoHTTPS := "on"
|
autoHTTPS := []string{}
|
||||||
if ah, ok := options["auto_https"].(string); ok {
|
if ah, ok := options["auto_https"].([]string); ok {
|
||||||
autoHTTPS = ah
|
autoHTTPS = ah
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,14 +54,17 @@ func (st ServerType) buildTLSApp(
|
|||||||
// key, so that they don't get forgotten/omitted by auto-HTTPS
|
// key, so that they don't get forgotten/omitted by auto-HTTPS
|
||||||
// (since they won't appear in route matchers)
|
// (since they won't appear in route matchers)
|
||||||
httpsHostsSharedWithHostlessKey := make(map[string]struct{})
|
httpsHostsSharedWithHostlessKey := make(map[string]struct{})
|
||||||
if autoHTTPS != "off" {
|
if !slices.Contains(autoHTTPS, "off") {
|
||||||
for _, pair := range pairings {
|
for _, pair := range pairings {
|
||||||
for _, sb := range pair.serverBlocks {
|
for _, sb := range pair.serverBlocks {
|
||||||
for _, addr := range sb.keys {
|
for _, addr := range sb.parsedKeys {
|
||||||
if addr.Host == "" {
|
if addr.Host != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// this server block has a hostless key, now
|
// this server block has a hostless key, now
|
||||||
// go through and add all the hosts to the set
|
// go through and add all the hosts to the set
|
||||||
for _, otherAddr := range sb.keys {
|
for _, otherAddr := range sb.parsedKeys {
|
||||||
if otherAddr.Original == addr.Original {
|
if otherAddr.Original == addr.Original {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -73,7 +77,6 @@ func (st ServerType) buildTLSApp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// a catch-all automation policy is used as a "default" for all subjects that
|
// a catch-all automation policy is used as a "default" for all subjects that
|
||||||
// don't have custom configuration explicitly associated with them; this
|
// don't have custom configuration explicitly associated with them; this
|
||||||
@ -91,7 +94,11 @@ func (st ServerType) buildTLSApp(
|
|||||||
|
|
||||||
for _, p := range pairings {
|
for _, p := range pairings {
|
||||||
// avoid setting up TLS automation policies for a server that is HTTP-only
|
// avoid setting up TLS automation policies for a server that is HTTP-only
|
||||||
if !listenersUseAnyPortOtherThan(p.addresses, httpPort) {
|
var addresses []string
|
||||||
|
for _, addressWithProtocols := range p.addressesWithProtocols {
|
||||||
|
addresses = append(addresses, addressWithProtocols.address)
|
||||||
|
}
|
||||||
|
if !listenersUseAnyPortOtherThan(addresses, httpPort) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,8 +188,8 @@ func (st ServerType) buildTLSApp(
|
|||||||
if acmeIssuer.Challenges.BindHost == "" {
|
if acmeIssuer.Challenges.BindHost == "" {
|
||||||
// only binding to one host is supported
|
// only binding to one host is supported
|
||||||
var bindHost string
|
var bindHost string
|
||||||
if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
|
if asserted, ok := cfgVal.Value.(addressesWithProtocols); ok && len(asserted.addresses) > 0 {
|
||||||
bindHost = bindHosts[0]
|
bindHost = asserted.addresses[0]
|
||||||
}
|
}
|
||||||
acmeIssuer.Challenges.BindHost = bindHost
|
acmeIssuer.Challenges.BindHost = bindHost
|
||||||
}
|
}
|
||||||
@ -344,7 +351,7 @@ func (st ServerType) buildTLSApp(
|
|||||||
internalAP := &caddytls.AutomationPolicy{
|
internalAP := &caddytls.AutomationPolicy{
|
||||||
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
|
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
|
||||||
}
|
}
|
||||||
if autoHTTPS != "off" && autoHTTPS != "disable_certs" {
|
if !slices.Contains(autoHTTPS, "off") && !slices.Contains(autoHTTPS, "disable_certs") {
|
||||||
for h := range httpsHostsSharedWithHostlessKey {
|
for h := range httpsHostsSharedWithHostlessKey {
|
||||||
al = append(al, h)
|
al = append(al, h)
|
||||||
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
||||||
@ -411,7 +418,10 @@ func (st ServerType) buildTLSApp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// consolidate automation policies that are the exact same
|
// consolidate automation policies that are the exact same
|
||||||
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
|
tlsApp.Automation.Policies = consolidateAutomationPolicies(
|
||||||
|
tlsApp.Automation.Policies,
|
||||||
|
slices.Contains(autoHTTPS, "prefer_wildcard"),
|
||||||
|
)
|
||||||
|
|
||||||
// ensure automation policies don't overlap subjects (this should be
|
// ensure automation policies don't overlap subjects (this should be
|
||||||
// an error at provision-time as well, but catch it in the adapt phase
|
// an error at provision-time as well, but catch it in the adapt phase
|
||||||
@ -465,7 +475,7 @@ func fillInGlobalACMEDefaults(issuer certmagic.Issuer, options map[string]any) e
|
|||||||
if globalACMECA != nil && acmeIssuer.CA == "" {
|
if globalACMECA != nil && acmeIssuer.CA == "" {
|
||||||
acmeIssuer.CA = globalACMECA.(string)
|
acmeIssuer.CA = globalACMECA.(string)
|
||||||
}
|
}
|
||||||
if globalACMECARoot != nil && !sliceContains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
|
if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
|
||||||
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
|
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
|
||||||
}
|
}
|
||||||
if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
|
if globalACMEDNS != nil && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil) {
|
||||||
@ -557,7 +567,7 @@ func newBaseAutomationPolicy(
|
|||||||
|
|
||||||
// consolidateAutomationPolicies combines automation policies that are the same,
|
// consolidateAutomationPolicies combines automation policies that are the same,
|
||||||
// for a cleaner overall output.
|
// for a cleaner overall output.
|
||||||
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
|
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy, preferWildcard bool) []*caddytls.AutomationPolicy {
|
||||||
// sort from most specific to least specific; we depend on this ordering
|
// sort from most specific to least specific; we depend on this ordering
|
||||||
sort.SliceStable(aps, func(i, j int) bool {
|
sort.SliceStable(aps, func(i, j int) bool {
|
||||||
if automationPolicyIsSubset(aps[i], aps[j]) {
|
if automationPolicyIsSubset(aps[i], aps[j]) {
|
||||||
@ -580,7 +590,7 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
|||||||
if !automationPolicyHasAllPublicNames(aps[i]) {
|
if !automationPolicyHasAllPublicNames(aps[i]) {
|
||||||
// if this automation policy has internal names, we might as well remove it
|
// if this automation policy has internal names, we might as well remove it
|
||||||
// so auto-https can implicitly use the internal issuer
|
// so auto-https can implicitly use the internal issuer
|
||||||
aps = append(aps[:i], aps[i+1:]...)
|
aps = slices.Delete(aps, i, i+1)
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -597,7 +607,7 @@ outer:
|
|||||||
for j := i + 1; j < len(aps); j++ {
|
for j := i + 1; j < len(aps); j++ {
|
||||||
// if they're exactly equal in every way, just keep one of them
|
// if they're exactly equal in every way, just keep one of them
|
||||||
if reflect.DeepEqual(aps[i], aps[j]) {
|
if reflect.DeepEqual(aps[i], aps[j]) {
|
||||||
aps = append(aps[:j], aps[j+1:]...)
|
aps = slices.Delete(aps, j, j+1)
|
||||||
// must re-evaluate current i against next j; can't skip it!
|
// must re-evaluate current i against next j; can't skip it!
|
||||||
// even if i decrements to -1, will be incremented to 0 immediately
|
// even if i decrements to -1, will be incremented to 0 immediately
|
||||||
i--
|
i--
|
||||||
@ -627,21 +637,46 @@ outer:
|
|||||||
// cause example.com to be served by the less specific policy for
|
// cause example.com to be served by the less specific policy for
|
||||||
// '*.com', which might be different (yes we've seen this happen)
|
// '*.com', which might be different (yes we've seen this happen)
|
||||||
if automationPolicyShadows(i, aps) >= j {
|
if automationPolicyShadows(i, aps) >= j {
|
||||||
aps = append(aps[:i], aps[i+1:]...)
|
aps = slices.Delete(aps, i, i+1)
|
||||||
i--
|
i--
|
||||||
continue outer
|
continue outer
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// avoid repeated subjects
|
// avoid repeated subjects
|
||||||
for _, subj := range aps[j].SubjectsRaw {
|
for _, subj := range aps[j].SubjectsRaw {
|
||||||
if !sliceContains(aps[i].SubjectsRaw, subj) {
|
if !slices.Contains(aps[i].SubjectsRaw, subj) {
|
||||||
aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
|
aps[i].SubjectsRaw = append(aps[i].SubjectsRaw, subj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
aps = append(aps[:j], aps[j+1:]...)
|
aps = slices.Delete(aps, j, j+1)
|
||||||
j--
|
j--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if preferWildcard {
|
||||||
|
// remove subjects from i if they're covered by a wildcard in j
|
||||||
|
iSubjs := aps[i].SubjectsRaw
|
||||||
|
for iSubj := 0; iSubj < len(iSubjs); iSubj++ {
|
||||||
|
for jSubj := range aps[j].SubjectsRaw {
|
||||||
|
if !strings.HasPrefix(aps[j].SubjectsRaw[jSubj], "*.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if certmagic.MatchWildcard(aps[i].SubjectsRaw[iSubj], aps[j].SubjectsRaw[jSubj]) {
|
||||||
|
iSubjs = slices.Delete(iSubjs, iSubj, iSubj+1)
|
||||||
|
iSubj--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aps[i].SubjectsRaw = iSubjs
|
||||||
|
|
||||||
|
// remove i if it has no subjects left
|
||||||
|
if len(aps[i].SubjectsRaw) == 0 {
|
||||||
|
aps = slices.Delete(aps, i, i+1)
|
||||||
|
i--
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -658,13 +693,9 @@ func automationPolicyIsSubset(a, b *caddytls.AutomationPolicy) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, aSubj := range a.SubjectsRaw {
|
for _, aSubj := range a.SubjectsRaw {
|
||||||
var inSuperset bool
|
inSuperset := slices.ContainsFunc(b.SubjectsRaw, func(bSubj string) bool {
|
||||||
for _, bSubj := range b.SubjectsRaw {
|
return certmagic.MatchWildcard(aSubj, bSubj)
|
||||||
if certmagic.MatchWildcard(aSubj, bSubj) {
|
})
|
||||||
inSuperset = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !inSuperset {
|
if !inSuperset {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -709,12 +740,9 @@ func subjectQualifiesForPublicCert(ap *caddytls.AutomationPolicy, subj string) b
|
|||||||
// automationPolicyHasAllPublicNames returns true if all the names on the policy
|
// automationPolicyHasAllPublicNames returns true if all the names on the policy
|
||||||
// do NOT qualify for public certs OR are tailscale domains.
|
// do NOT qualify for public certs OR are tailscale domains.
|
||||||
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
func automationPolicyHasAllPublicNames(ap *caddytls.AutomationPolicy) bool {
|
||||||
for _, subj := range ap.SubjectsRaw {
|
return !slices.ContainsFunc(ap.SubjectsRaw, func(i string) bool {
|
||||||
if !subjectQualifiesForPublicCert(ap, subj) || isTailscaleDomain(subj) {
|
return !subjectQualifiesForPublicCert(ap, i) || isTailscaleDomain(i)
|
||||||
return false
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTailscaleDomain(name string) bool {
|
func isTailscaleDomain(name string) bool {
|
||||||
|
|||||||
@ -84,7 +84,6 @@ func TestLoadUnorderedJSON(t *testing.T) {
|
|||||||
"servers": {
|
"servers": {
|
||||||
"s_server": {
|
"s_server": {
|
||||||
"listen": [
|
"listen": [
|
||||||
":9443",
|
|
||||||
":9080"
|
":9080"
|
||||||
],
|
],
|
||||||
"routes": [
|
"routes": [
|
||||||
|
|||||||
@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
auto_https prefer_wildcard
|
||||||
|
}
|
||||||
|
|
||||||
|
*.example.com {
|
||||||
|
tls {
|
||||||
|
dns mock
|
||||||
|
}
|
||||||
|
respond "fallback"
|
||||||
|
}
|
||||||
|
|
||||||
|
foo.example.com {
|
||||||
|
respond "foo"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"foo.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "foo",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "fallback",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"prefer_wildcard": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"*.example.com"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
auto_https disable_redirects
|
||||||
|
admin off
|
||||||
|
}
|
||||||
|
|
||||||
|
http://localhost {
|
||||||
|
bind fd/{env.CADDY_HTTP_FD} {
|
||||||
|
protocols h1
|
||||||
|
}
|
||||||
|
log
|
||||||
|
respond "Hello, HTTP!"
|
||||||
|
}
|
||||||
|
|
||||||
|
https://localhost {
|
||||||
|
bind fd/{env.CADDY_HTTPS_FD} {
|
||||||
|
protocols h1 h2
|
||||||
|
}
|
||||||
|
bind fdgram/{env.CADDY_HTTP3_FD} {
|
||||||
|
protocols h3
|
||||||
|
}
|
||||||
|
log
|
||||||
|
respond "Hello, HTTPS!"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"admin": {
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
"fd/{env.CADDY_HTTPS_FD}",
|
||||||
|
"fdgram/{env.CADDY_HTTP3_FD}"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Hello, HTTPS!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"disable_redirects": true
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"logger_names": {
|
||||||
|
"localhost": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"listen_protocols": [
|
||||||
|
[
|
||||||
|
"h1",
|
||||||
|
"h2"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"h3"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"srv1": {
|
||||||
|
"automatic_https": {
|
||||||
|
"disable_redirects": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"srv2": {
|
||||||
|
"listen": [
|
||||||
|
"fd/{env.CADDY_HTTP_FD}"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Hello, HTTP!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"automatic_https": {
|
||||||
|
"disable_redirects": true,
|
||||||
|
"skip": [
|
||||||
|
"localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"logs": {
|
||||||
|
"logger_names": {
|
||||||
|
"localhost": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"listen_protocols": [
|
||||||
|
[
|
||||||
|
"h1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
:80
|
||||||
|
|
||||||
|
file_server {
|
||||||
|
browse {
|
||||||
|
sort size desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":80"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"browse": {
|
||||||
|
"sort": [
|
||||||
|
"size",
|
||||||
|
"desc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"handler": "file_server",
|
||||||
|
"hide": [
|
||||||
|
"./Caddyfile"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
:8884
|
||||||
|
|
||||||
|
reverse_proxy 127.0.0.1:65535 {
|
||||||
|
health_uri /health
|
||||||
|
health_method HEAD
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8884"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"health_checks": {
|
||||||
|
"active": {
|
||||||
|
"method": "HEAD",
|
||||||
|
"uri": "/health"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "127.0.0.1:65535"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
:8884
|
||||||
|
|
||||||
|
reverse_proxy 127.0.0.1:65535 {
|
||||||
|
health_uri /health
|
||||||
|
health_request_body "test body"
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":8884"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"health_checks": {
|
||||||
|
"active": {
|
||||||
|
"body": "test body",
|
||||||
|
"uri": "/health"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "127.0.0.1:65535"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
https://example.com {
|
||||||
|
reverse_proxy http://localhost:54321 {
|
||||||
|
transport http {
|
||||||
|
local_address 192.168.0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "reverse_proxy",
|
||||||
|
"transport": {
|
||||||
|
"local_address": "192.168.0.1",
|
||||||
|
"protocol": "http"
|
||||||
|
},
|
||||||
|
"upstreams": [
|
||||||
|
{
|
||||||
|
"dial": "localhost:54321"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,157 @@
|
|||||||
|
*.example.com {
|
||||||
|
tls foo@example.com {
|
||||||
|
dns mock
|
||||||
|
}
|
||||||
|
|
||||||
|
@foo host foo.example.com
|
||||||
|
handle @foo {
|
||||||
|
respond "Foo!"
|
||||||
|
}
|
||||||
|
|
||||||
|
@bar host bar.example.com
|
||||||
|
handle @bar {
|
||||||
|
respond "Bar!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fallback for otherwise unhandled domains
|
||||||
|
handle {
|
||||||
|
abort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------
|
||||||
|
{
|
||||||
|
"apps": {
|
||||||
|
"http": {
|
||||||
|
"servers": {
|
||||||
|
"srv0": {
|
||||||
|
"listen": [
|
||||||
|
":443"
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"*.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Foo!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"foo.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"body": "Bar!",
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
{
|
||||||
|
"host": [
|
||||||
|
"bar.example.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "group3",
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"handler": "subroute",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"handle": [
|
||||||
|
{
|
||||||
|
"abort": true,
|
||||||
|
"handler": "static_response"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminal": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"automation": {
|
||||||
|
"policies": [
|
||||||
|
{
|
||||||
|
"subjects": [
|
||||||
|
"*.example.com"
|
||||||
|
],
|
||||||
|
"issuers": [
|
||||||
|
{
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": "foo@example.com",
|
||||||
|
"module": "acme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ca": "https://acme.zerossl.com/v2/DV90",
|
||||||
|
"challenges": {
|
||||||
|
"dns": {
|
||||||
|
"provider": {
|
||||||
|
"name": "mock"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": "foo@example.com",
|
||||||
|
"module": "acme"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,17 +18,23 @@ func TestIntercept(t *testing.T) {
|
|||||||
|
|
||||||
localhost:9080 {
|
localhost:9080 {
|
||||||
respond /intercept "I'm a teapot" 408
|
respond /intercept "I'm a teapot" 408
|
||||||
|
header /intercept To-Intercept ok
|
||||||
respond /no-intercept "I'm not a teapot"
|
respond /no-intercept "I'm not a teapot"
|
||||||
|
|
||||||
intercept {
|
intercept {
|
||||||
@teapot status 408
|
@teapot status 408
|
||||||
handle_response @teapot {
|
handle_response @teapot {
|
||||||
|
header /intercept intercepted {resp.header.To-Intercept}
|
||||||
respond /intercept "I'm a combined coffee/tea pot that is temporarily out of coffee" 503
|
respond /intercept "I'm a combined coffee/tea pot that is temporarily out of coffee" 503
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, "caddyfile")
|
`, "caddyfile")
|
||||||
|
|
||||||
tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
|
r, _ := tester.AssertGetResponse("http://localhost:9080/intercept", 503, "I'm a combined coffee/tea pot that is temporarily out of coffee")
|
||||||
|
if r.Header.Get("intercepted") != "ok" {
|
||||||
|
t.Fatalf(`header "intercepted" value is not "ok": %s`, r.Header.Get("intercepted"))
|
||||||
|
}
|
||||||
|
|
||||||
tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot")
|
tester.AssertGetResponse("http://localhost:9080/no-intercept", 200, "I'm not a teapot")
|
||||||
}
|
}
|
||||||
|
|||||||
61
caddytest/integration/mockdns_test.go
Normal file
61
caddytest/integration/mockdns_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2"
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
|
"github.com/caddyserver/certmagic"
|
||||||
|
"github.com/libdns/libdns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterModule(MockDNSProvider{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockDNSProvider is a mock DNS provider, for testing config with DNS modules.
|
||||||
|
type MockDNSProvider struct{}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MockDNSProvider) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "dns.providers.mock",
|
||||||
|
New: func() caddy.Module { return new(MockDNSProvider) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision sets up the module.
|
||||||
|
func (MockDNSProvider) Provision(ctx caddy.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
|
||||||
|
func (MockDNSProvider) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendsRecords appends DNS records to the zone.
|
||||||
|
func (MockDNSProvider) AppendRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecords deletes DNS records from the zone.
|
||||||
|
func (MockDNSProvider) DeleteRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecords gets DNS records from the zone.
|
||||||
|
func (MockDNSProvider) GetRecords(ctx context.Context, zone string) ([]libdns.Record, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecords sets DNS records in the zone.
|
||||||
|
func (MockDNSProvider) SetRecords(ctx context.Context, zone string, recs []libdns.Record) ([]libdns.Record, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface guard
|
||||||
|
var _ caddyfile.Unmarshaler = (*MockDNSProvider)(nil)
|
||||||
|
var _ certmagic.DNSProvider = (*MockDNSProvider)(nil)
|
||||||
|
var _ caddy.Provisioner = (*MockDNSProvider)(nil)
|
||||||
|
var _ caddy.Module = (*MockDNSProvider)(nil)
|
||||||
2
caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt
vendored
Normal file
2
caddytest/integration/testdata/foo_with_multiple_trailing_newlines.txt
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
foo
|
||||||
|
|
||||||
1
caddytest/integration/testdata/foo_with_trailing_newline.txt
vendored
Normal file
1
caddytest/integration/testdata/foo_with_trailing_newline.txt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
foo
|
||||||
@ -1,3 +1,8 @@
|
|||||||
|
// The below line is required to enable post-quantum key agreement in Go 1.23
|
||||||
|
// by default without insisting on setting a minimum version of 1.23 in go.mod.
|
||||||
|
// See https://github.com/caddyserver/caddy/issues/6540#issuecomment-2313094905
|
||||||
|
//go:debug tlskyber=1
|
||||||
|
|
||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import (
|
|||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var defaultFactory = newRootCommandFactory(func() *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
Use: "caddy",
|
Use: "caddy",
|
||||||
Long: `Caddy is an extensible server platform written in Go.
|
Long: `Caddy is an extensible server platform written in Go.
|
||||||
|
|
||||||
@ -101,13 +102,16 @@ https://caddyserver.com/docs/running
|
|||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
Version: onlyVersionText(),
|
Version: onlyVersionText(),
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const fullDocsFooter = `Full documentation is available at:
|
const fullDocsFooter = `Full documentation is available at:
|
||||||
https://caddyserver.com/docs/command-line`
|
https://caddyserver.com/docs/command-line`
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
defaultFactory.Use(func(rootCmd *cobra.Command) {
|
||||||
rootCmd.SetVersionTemplate("{{.Version}}\n")
|
rootCmd.SetVersionTemplate("{{.Version}}\n")
|
||||||
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
|
rootCmd.SetHelpTemplate(rootCmd.HelpTemplate() + "\n" + fullDocsFooter + "\n")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func onlyVersionText() string {
|
func onlyVersionText() string {
|
||||||
|
|||||||
28
cmd/commandfactory.go
Normal file
28
cmd/commandfactory.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package caddycmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rootCommandFactory struct {
|
||||||
|
constructor func() *cobra.Command
|
||||||
|
options []func(*cobra.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRootCommandFactory(fn func() *cobra.Command) *rootCommandFactory {
|
||||||
|
return &rootCommandFactory{
|
||||||
|
constructor: fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *rootCommandFactory) Use(fn func(cmd *cobra.Command)) {
|
||||||
|
f.options = append(f.options, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *rootCommandFactory) Build() *cobra.Command {
|
||||||
|
o := f.constructor()
|
||||||
|
for _, v := range f.options {
|
||||||
|
v(o)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
@ -74,6 +74,10 @@ func cmdStart(fl Flags) (int, error) {
|
|||||||
// sure by giving it some random bytes and having it echo
|
// sure by giving it some random bytes and having it echo
|
||||||
// them back to us)
|
// them back to us)
|
||||||
cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
|
cmd := exec.Command(os.Args[0], "run", "--pingback", ln.Addr().String())
|
||||||
|
// we should be able to run caddy in relative paths
|
||||||
|
if errors.Is(cmd.Err, exec.ErrDot) {
|
||||||
|
cmd.Err = nil
|
||||||
|
}
|
||||||
if configFlag != "" {
|
if configFlag != "" {
|
||||||
cmd.Args = append(cmd.Args, "--config", configFlag)
|
cmd.Args = append(cmd.Args, "--config", configFlag)
|
||||||
}
|
}
|
||||||
@ -656,6 +660,8 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
parsedAddr.Host = addr
|
parsedAddr.Host = addr
|
||||||
|
} else if parsedAddr.IsFdNetwork() {
|
||||||
|
origin = "http://127.0.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
// form the request
|
// form the request
|
||||||
@ -663,13 +669,13 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("making request: %v", err)
|
return nil, fmt.Errorf("making request: %v", err)
|
||||||
}
|
}
|
||||||
if parsedAddr.IsUnixNetwork() {
|
if parsedAddr.IsUnixNetwork() || parsedAddr.IsFdNetwork() {
|
||||||
// We used to conform to RFC 2616 Section 14.26 which requires
|
// We used to conform to RFC 2616 Section 14.26 which requires
|
||||||
// an empty host header when there is no host, as is the case
|
// an empty host header when there is no host, as is the case
|
||||||
// with unix sockets. However, Go required a Host value so we
|
// with unix sockets and socket fds. However, Go required a
|
||||||
// used a hack of a space character as the host (it would see
|
// Host value so we used a hack of a space character as the host
|
||||||
// the Host was non-empty, then trim the space later). As of
|
// (it would see the Host was non-empty, then trim the space later).
|
||||||
// Go 1.20.6 (July 2023), this hack no longer works. See:
|
// As of Go 1.20.6 (July 2023), this hack no longer works. See:
|
||||||
// https://github.com/golang/go/issues/60374
|
// https://github.com/golang/go/issues/60374
|
||||||
// See also the discussion here:
|
// See also the discussion here:
|
||||||
// https://github.com/golang/go/issues/61431
|
// https://github.com/golang/go/issues/61431
|
||||||
@ -710,7 +716,7 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
|||||||
|
|
||||||
// if it didn't work, let the user know
|
// if it didn't work, let the user know
|
||||||
if resp.StatusCode >= 400 {
|
if resp.StatusCode >= 400 {
|
||||||
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*10))
|
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*1024*2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
|
return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -438,6 +438,7 @@ EXPERIMENTAL: May be changed or removed.
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defaultFactory.Use(func(rootCmd *cobra.Command) {
|
||||||
RegisterCommand(Command{
|
RegisterCommand(Command{
|
||||||
Name: "manpage",
|
Name: "manpage",
|
||||||
Usage: "--directory <path>",
|
Usage: "--directory <path>",
|
||||||
@ -531,6 +532,7 @@ argument of --directory. If the directory does not exist, it will be created.
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterCommand registers the command cmd.
|
// RegisterCommand registers the command cmd.
|
||||||
@ -563,7 +565,9 @@ func RegisterCommand(cmd Command) {
|
|||||||
if !commandNameRegex.MatchString(cmd.Name) {
|
if !commandNameRegex.MatchString(cmd.Name) {
|
||||||
panic("invalid command name")
|
panic("invalid command name")
|
||||||
}
|
}
|
||||||
|
defaultFactory.Use(func(rootCmd *cobra.Command) {
|
||||||
rootCmd.AddCommand(caddyCmdToCobra(cmd))
|
rootCmd.AddCommand(caddyCmdToCobra(cmd))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
|
var commandNameRegex = regexp.MustCompile(`^[a-z0-9]$|^([a-z0-9]+-?[a-z0-9]*)+[a-z0-9]$`)
|
||||||
|
|||||||
@ -72,7 +72,7 @@ func Main() {
|
|||||||
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
|
caddy.Log().Warn("failed to set GOMAXPROCS", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := defaultFactory.Build().Execute(); err != nil {
|
||||||
var exitError *exitError
|
var exitError *exitError
|
||||||
if errors.As(err, &exitError) {
|
if errors.As(err, &exitError) {
|
||||||
os.Exit(exitError.ExitCode)
|
os.Exit(exitError.ExitCode)
|
||||||
|
|||||||
27
go.mod
27
go.mod
@ -1,8 +1,8 @@
|
|||||||
module github.com/caddyserver/caddy/v2
|
module github.com/caddyserver/caddy/v2
|
||||||
|
|
||||||
go 1.21.0
|
go 1.22.3
|
||||||
|
|
||||||
toolchain go1.22.2
|
toolchain go1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.3.2
|
github.com/BurntSushi/toml v1.3.2
|
||||||
@ -13,13 +13,13 @@ require (
|
|||||||
github.com/caddyserver/zerossl v0.1.3
|
github.com/caddyserver/zerossl v0.1.3
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/go-chi/chi/v5 v5.0.12
|
github.com/go-chi/chi/v5 v5.0.12
|
||||||
github.com/google/cel-go v0.20.1
|
github.com/google/cel-go v0.21.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/klauspost/compress v1.17.8
|
github.com/klauspost/compress v1.17.8
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7
|
github.com/klauspost/cpuid/v2 v2.2.7
|
||||||
github.com/mholt/acmez/v2 v2.0.1
|
github.com/mholt/acmez/v2 v2.0.1
|
||||||
github.com/prometheus/client_golang v1.19.1
|
github.com/prometheus/client_golang v1.19.1
|
||||||
github.com/quic-go/quic-go v0.44.0
|
github.com/quic-go/quic-go v0.47.0
|
||||||
github.com/smallstep/certificates v0.26.1
|
github.com/smallstep/certificates v0.26.1
|
||||||
github.com/smallstep/nosql v0.6.1
|
github.com/smallstep/nosql v0.6.1
|
||||||
github.com/smallstep/truststore v0.13.0
|
github.com/smallstep/truststore v0.13.0
|
||||||
@ -37,11 +37,11 @@ require (
|
|||||||
go.uber.org/automaxprocs v1.5.3
|
go.uber.org/automaxprocs v1.5.3
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go.uber.org/zap/exp v0.2.0
|
go.uber.org/zap/exp v0.2.0
|
||||||
golang.org/x/crypto v0.23.0
|
golang.org/x/crypto v0.26.0
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595
|
||||||
golang.org/x/net v0.25.0
|
golang.org/x/net v0.28.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.8.0
|
||||||
golang.org/x/term v0.20.0
|
golang.org/x/term v0.23.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@ -51,6 +51,7 @@ require (
|
|||||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
|
||||||
github.com/go-kit/log v0.2.1 // indirect
|
github.com/go-kit/log v0.2.1 // indirect
|
||||||
@ -62,7 +63,7 @@ require (
|
|||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
|
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect
|
||||||
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
|
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
|
||||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
|
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
|
||||||
@ -123,7 +124,7 @@ require (
|
|||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
@ -147,9 +148,9 @@ require (
|
|||||||
go.step.sm/linkedca v0.20.1 // indirect
|
go.step.sm/linkedca v0.20.1 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.17.0 // indirect
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.20.0
|
golang.org/x/sys v0.23.0
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
golang.org/x/tools v0.21.0 // indirect
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||||
google.golang.org/grpc v1.63.2 // indirect
|
google.golang.org/grpc v1.63.2 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
howett.net/plist v1.0.0 // indirect
|
howett.net/plist v1.0.0 // indirect
|
||||||
|
|||||||
188
go.sum
188
go.sum
@ -1,3 +1,7 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||||
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
|
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
|
||||||
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
|
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
|
||||||
@ -12,8 +16,13 @@ cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
|
|||||||
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
|
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
|
||||||
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
|
||||||
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
|
||||||
|
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||||
|
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||||
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
@ -38,6 +47,7 @@ github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv
|
|||||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
@ -71,8 +81,11 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1q
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
|
||||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
|
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
|
||||||
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
|
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
|
||||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||||
@ -93,11 +106,13 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
|
|||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
@ -128,11 +143,17 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
|
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
|
||||||
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||||
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
@ -158,33 +179,44 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
|
|||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
|
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
|
||||||
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
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/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
|
github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI=
|
||||||
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
|
github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc=
|
||||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
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 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
|
||||||
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
|
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||||
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||||
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
|
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
|
||||||
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
|
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
|
||||||
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
|
||||||
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU=
|
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU=
|
||||||
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
@ -195,8 +227,14 @@ 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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||||
|
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||||
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
@ -257,7 +295,10 @@ github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx
|
|||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||||
@ -272,6 +313,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
|
|||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -284,7 +326,9 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||||
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
|
||||||
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
@ -297,10 +341,12 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
|
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
|
||||||
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
|
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
@ -313,15 +359,20 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
|||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
|
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
|
||||||
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
|
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964 h1:ct/vxNBgHpASQ4sT8NaBX9LtsEtluZqaUJydLG50U3E=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.1-0.20240628150027-b718e7ce4964/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@ -329,18 +380,22 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||||
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
|
github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
|
||||||
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
|
github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
@ -355,11 +410,34 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
|
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
|
||||||
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
|
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||||
|
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||||
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||||
|
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
|
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||||
|
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||||
|
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||||
|
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||||
|
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||||
|
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||||
|
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||||
|
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||||
|
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||||
|
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||||
|
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||||
|
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||||
|
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||||
|
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||||
|
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
@ -381,6 +459,8 @@ github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjI
|
|||||||
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
|
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
|
||||||
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
|
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
|
||||||
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
|
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
|
||||||
|
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||||
|
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
@ -417,9 +497,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
|||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
|
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
|
||||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
|
||||||
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
|
||||||
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
@ -438,6 +521,7 @@ github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l
|
|||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
|
||||||
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
|
||||||
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||||
@ -497,8 +581,12 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
|||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
|
go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
|
||||||
go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
|
go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
|
||||||
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@ -510,12 +598,16 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw=
|
||||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
@ -523,7 +615,15 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
|||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@ -532,20 +632,33 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
||||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
|
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -567,8 +680,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@ -576,9 +689,10 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
|||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@ -588,11 +702,17 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
@ -603,21 +723,37 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
|
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
|
||||||
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
|
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
|
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
|
||||||
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
|
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
@ -629,9 +765,11 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
@ -639,6 +777,12 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
|
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||||
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
|
|||||||
14
internal/ranges.go
Normal file
14
internal/ranges.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
// PrivateRangesCIDR returns a list of private CIDR range
|
||||||
|
// strings, which can be used as a configuration shortcut.
|
||||||
|
func PrivateRangesCIDR() []string {
|
||||||
|
return []string{
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"172.16.0.0/12",
|
||||||
|
"10.0.0.0/8",
|
||||||
|
"127.0.0.1/8",
|
||||||
|
"fd00::/8",
|
||||||
|
"::1",
|
||||||
|
}
|
||||||
|
}
|
||||||
68
listen.go
68
listen.go
@ -18,7 +18,11 @@ package caddy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -31,10 +35,49 @@ func reuseUnixSocket(network, addr string) (any, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
||||||
switch network {
|
var socketFile *os.File
|
||||||
case "udp", "udp4", "udp6", "unixgram":
|
|
||||||
|
fd := slices.Contains([]string{"fd", "fdgram"}, network)
|
||||||
|
if fd {
|
||||||
|
socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid file descriptor: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
socketFilesMu.Lock()
|
||||||
|
defer socketFilesMu.Unlock()
|
||||||
|
|
||||||
|
socketFdWide := uintptr(socketFd)
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
socketFile, ok = socketFiles[socketFdWide]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
socketFile = os.NewFile(socketFdWide, lnKey)
|
||||||
|
if socketFile != nil {
|
||||||
|
socketFiles[socketFdWide] = socketFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if socketFile == nil {
|
||||||
|
return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
|
||||||
|
if datagram {
|
||||||
sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||||
pc, err := config.ListenPacket(ctx, network, address)
|
var (
|
||||||
|
pc net.PacketConn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if fd {
|
||||||
|
pc, err = net.FilePacketConn(socketFile)
|
||||||
|
} else {
|
||||||
|
pc, err = config.ListenPacket(ctx, network, address)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -44,10 +87,18 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
|
return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
|
||||||
ln, err := config.Listen(ctx, network, address)
|
var (
|
||||||
|
ln net.Listener
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if fd {
|
||||||
|
ln, err = net.FileListener(socketFile)
|
||||||
|
} else {
|
||||||
|
ln, err = config.Listen(ctx, network, address)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -58,7 +109,6 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
|
|||||||
}
|
}
|
||||||
return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
|
return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// fakeCloseListener is a private wrapper over a listener that
|
// fakeCloseListener is a private wrapper over a listener that
|
||||||
// is shared. The state of fakeCloseListener is not shared.
|
// is shared. The state of fakeCloseListener is not shared.
|
||||||
@ -260,3 +310,9 @@ var (
|
|||||||
Unwrap() net.PacketConn
|
Unwrap() net.PacketConn
|
||||||
}) = (*fakeClosePacketConn)(nil)
|
}) = (*fakeClosePacketConn)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor.
|
||||||
|
var socketFiles = map[uintptr]*os.File{}
|
||||||
|
|
||||||
|
// socketFilesMu synchronizes socketFiles insertions
|
||||||
|
var socketFilesMu sync.Mutex
|
||||||
|
|||||||
132
listen_unix.go
132
listen_unix.go
@ -22,10 +22,14 @@ package caddy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@ -34,12 +38,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already
|
// reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already
|
||||||
// have it open; if not, unlink it so we can have it. No-op if not a unix network.
|
// have it open; if not, unlink it so we can have it.
|
||||||
|
// No-op if not a unix network.
|
||||||
func reuseUnixSocket(network, addr string) (any, error) {
|
func reuseUnixSocket(network, addr string) (any, error) {
|
||||||
if !IsUnixNetwork(network) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
socketKey := listenerKey(network, addr)
|
socketKey := listenerKey(network, addr)
|
||||||
|
|
||||||
socket, exists := unixSockets[socketKey]
|
socket, exists := unixSockets[socketKey]
|
||||||
@ -71,7 +72,7 @@ func reuseUnixSocket(network, addr string) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
atomic.AddInt32(unixSocket.count, 1)
|
atomic.AddInt32(unixSocket.count, 1)
|
||||||
unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), addr, socketKey, unixSocket.count}
|
unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), socketKey, unixSocket.count}
|
||||||
}
|
}
|
||||||
|
|
||||||
return unixSockets[socketKey], nil
|
return unixSockets[socketKey], nil
|
||||||
@ -89,7 +90,46 @@ func reuseUnixSocket(network, addr string) (any, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listenReusable creates a new listener for the given network and address, and adds it to listenerPool.
|
||||||
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
|
||||||
|
// even though SO_REUSEPORT lets us bind the socket multiple times,
|
||||||
|
// we still put it in the listenerPool so we can count how many
|
||||||
|
// configs are using this socket; necessary to ensure we can know
|
||||||
|
// whether to enforce shutdown delays, for example (see #5393).
|
||||||
|
var (
|
||||||
|
ln io.Closer
|
||||||
|
err error
|
||||||
|
socketFile *os.File
|
||||||
|
)
|
||||||
|
|
||||||
|
fd := slices.Contains([]string{"fd", "fdgram"}, network)
|
||||||
|
if fd {
|
||||||
|
socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid file descriptor: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
socketFilesMu.Lock()
|
||||||
|
defer socketFilesMu.Unlock()
|
||||||
|
|
||||||
|
socketFdWide := uintptr(socketFd)
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
socketFile, ok = socketFiles[socketFdWide]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
socketFile = os.NewFile(socketFdWide, lnKey)
|
||||||
|
if socketFile != nil {
|
||||||
|
socketFiles[socketFdWide] = socketFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if socketFile == nil {
|
||||||
|
return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs
|
// wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs
|
||||||
oldControl := config.Control
|
oldControl := config.Control
|
||||||
config.Control = func(network, address string, c syscall.RawConn) error {
|
config.Control = func(network, address string, c syscall.RawConn) error {
|
||||||
@ -100,45 +140,57 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
|
|||||||
}
|
}
|
||||||
return reusePort(network, address, c)
|
return reusePort(network, address, c)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// even though SO_REUSEPORT lets us bind the socket multiple times,
|
datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
|
||||||
// we still put it in the listenerPool so we can count how many
|
if datagram {
|
||||||
// configs are using this socket; necessary to ensure we can know
|
if fd {
|
||||||
// whether to enforce shutdown delays, for example (see #5393).
|
ln, err = net.FilePacketConn(socketFile)
|
||||||
var ln io.Closer
|
} else {
|
||||||
var err error
|
|
||||||
switch network {
|
|
||||||
case "udp", "udp4", "udp6", "unixgram":
|
|
||||||
ln, err = config.ListenPacket(ctx, network, address)
|
ln, err = config.ListenPacket(ctx, network, address)
|
||||||
default:
|
}
|
||||||
|
} else {
|
||||||
|
if fd {
|
||||||
|
ln, err = net.FileListener(socketFile)
|
||||||
|
} else {
|
||||||
ln, err = config.Listen(ctx, network, address)
|
ln, err = config.Listen(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
listenerPool.LoadOrStore(lnKey, nil)
|
listenerPool.LoadOrStore(lnKey, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if datagram {
|
||||||
|
if !fd {
|
||||||
|
// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener, so...
|
||||||
|
if unix, ok := ln.(*net.UnixConn); ok {
|
||||||
|
one := int32(1)
|
||||||
|
ln = &unixConn{unix, lnKey, &one}
|
||||||
|
unixSockets[lnKey] = ln.(*unixConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// lightly wrap the connection so that when it is closed,
|
||||||
|
// we can decrement the usage pool counter
|
||||||
|
if specificLn, ok := ln.(net.PacketConn); ok {
|
||||||
|
ln = deletePacketConn{specificLn, lnKey}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !fd {
|
||||||
// if new listener is a unix socket, make sure we can reuse it later
|
// if new listener is a unix socket, make sure we can reuse it later
|
||||||
// (we do our own "unlink on close" -- not required, but more tidy)
|
// (we do our own "unlink on close" -- not required, but more tidy)
|
||||||
one := int32(1)
|
|
||||||
if unix, ok := ln.(*net.UnixListener); ok {
|
if unix, ok := ln.(*net.UnixListener); ok {
|
||||||
unix.SetUnlinkOnClose(false)
|
unix.SetUnlinkOnClose(false)
|
||||||
|
one := int32(1)
|
||||||
ln = &unixListener{unix, lnKey, &one}
|
ln = &unixListener{unix, lnKey, &one}
|
||||||
unixSockets[lnKey] = ln.(*unixListener)
|
unixSockets[lnKey] = ln.(*unixListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so...
|
|
||||||
if unix, ok := ln.(*net.UnixConn); ok {
|
|
||||||
ln = &unixConn{unix, address, lnKey, &one}
|
|
||||||
unixSockets[lnKey] = ln.(*unixConn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// lightly wrap the listener so that when it is closed,
|
// lightly wrap the listener so that when it is closed,
|
||||||
// we can decrement the usage pool counter
|
// we can decrement the usage pool counter
|
||||||
switch specificLn := ln.(type) {
|
if specificLn, ok := ln.(net.Listener); ok {
|
||||||
case net.Listener:
|
ln = deleteListener{specificLn, lnKey}
|
||||||
return deleteListener{specificLn, lnKey}, err
|
}
|
||||||
case net.PacketConn:
|
|
||||||
return deletePacketConn{specificLn, lnKey}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// other types, I guess we just return them directly
|
// other types, I guess we just return them directly
|
||||||
@ -170,12 +222,18 @@ type unixListener struct {
|
|||||||
func (uln *unixListener) Close() error {
|
func (uln *unixListener) Close() error {
|
||||||
newCount := atomic.AddInt32(uln.count, -1)
|
newCount := atomic.AddInt32(uln.count, -1)
|
||||||
if newCount == 0 {
|
if newCount == 0 {
|
||||||
|
file, err := uln.File()
|
||||||
|
var name string
|
||||||
|
if err == nil {
|
||||||
|
name = file.Name()
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
addr := uln.Addr().String()
|
|
||||||
unixSocketsMu.Lock()
|
unixSocketsMu.Lock()
|
||||||
delete(unixSockets, uln.mapKey)
|
delete(unixSockets, uln.mapKey)
|
||||||
unixSocketsMu.Unlock()
|
unixSocketsMu.Unlock()
|
||||||
_ = syscall.Unlink(addr)
|
if err == nil {
|
||||||
|
_ = syscall.Unlink(name)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return uln.UnixListener.Close()
|
return uln.UnixListener.Close()
|
||||||
@ -183,7 +241,6 @@ func (uln *unixListener) Close() error {
|
|||||||
|
|
||||||
type unixConn struct {
|
type unixConn struct {
|
||||||
*net.UnixConn
|
*net.UnixConn
|
||||||
filename string
|
|
||||||
mapKey string
|
mapKey string
|
||||||
count *int32 // accessed atomically
|
count *int32 // accessed atomically
|
||||||
}
|
}
|
||||||
@ -191,11 +248,18 @@ type unixConn struct {
|
|||||||
func (uc *unixConn) Close() error {
|
func (uc *unixConn) Close() error {
|
||||||
newCount := atomic.AddInt32(uc.count, -1)
|
newCount := atomic.AddInt32(uc.count, -1)
|
||||||
if newCount == 0 {
|
if newCount == 0 {
|
||||||
|
file, err := uc.File()
|
||||||
|
var name string
|
||||||
|
if err == nil {
|
||||||
|
name = file.Name()
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
unixSocketsMu.Lock()
|
unixSocketsMu.Lock()
|
||||||
delete(unixSockets, uc.mapKey)
|
delete(unixSockets, uc.mapKey)
|
||||||
unixSocketsMu.Unlock()
|
unixSocketsMu.Unlock()
|
||||||
_ = syscall.Unlink(uc.filename)
|
if err == nil {
|
||||||
|
_ = syscall.Unlink(name)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return uc.UnixConn.Close()
|
return uc.UnixConn.Close()
|
||||||
@ -211,6 +275,12 @@ var unixSockets = make(map[string]interface {
|
|||||||
File() (*os.File, error)
|
File() (*os.File, error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor.
|
||||||
|
var socketFiles = map[uintptr]*os.File{}
|
||||||
|
|
||||||
|
// socketFilesMu synchronizes socketFiles insertions
|
||||||
|
var socketFilesMu sync.Mutex
|
||||||
|
|
||||||
// deleteListener is a type that simply deletes itself
|
// deleteListener is a type that simply deletes itself
|
||||||
// from the listenerPool when it closes. It is used
|
// from the listenerPool when it closes. It is used
|
||||||
// solely for the purpose of reference counting (i.e.
|
// solely for the purpose of reference counting (i.e.
|
||||||
|
|||||||
112
listeners.go
112
listeners.go
@ -31,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
|
"github.com/quic-go/quic-go/qlog"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
@ -57,11 +58,9 @@ type NetworkAddress struct {
|
|||||||
EndPort uint
|
EndPort uint
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAll calls Listen() for all addresses represented by this struct, i.e. all ports in the range.
|
// ListenAll calls Listen for all addresses represented by this struct, i.e. all ports in the range.
|
||||||
// (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.)
|
// (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.)
|
||||||
// It returns an error if any listener failed to bind, and closes any listeners opened up to that point.
|
// It returns an error if any listener failed to bind, and closes any listeners opened up to that point.
|
||||||
//
|
|
||||||
// TODO: Experimental API: subject to change or removal.
|
|
||||||
func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) {
|
func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) {
|
||||||
var listeners []any
|
var listeners []any
|
||||||
var err error
|
var err error
|
||||||
@ -107,7 +106,8 @@ func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig)
|
|||||||
// portOffset to the start port. (For network types that do not use ports, the
|
// portOffset to the start port. (For network types that do not use ports, the
|
||||||
// portOffset is ignored.)
|
// portOffset is ignored.)
|
||||||
//
|
//
|
||||||
// The provided ListenConfig is used to create the listener. Its Control function,
|
// First Listen checks if a plugin can provide a listener from this address. Otherwise,
|
||||||
|
// the provided ListenConfig is used to create the listener. Its Control function,
|
||||||
// if set, may be wrapped by an internally-used Control function. The provided
|
// if set, may be wrapped by an internally-used Control function. The provided
|
||||||
// context may be used to cancel long operations early. The context is not used
|
// context may be used to cancel long operations early. The context is not used
|
||||||
// to close the listener after it has been created.
|
// to close the listener after it has been created.
|
||||||
@ -130,8 +130,8 @@ func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig)
|
|||||||
// Unix sockets will be unlinked before being created, to ensure we can bind to
|
// Unix sockets will be unlinked before being created, to ensure we can bind to
|
||||||
// it even if the previous program using it exited uncleanly; it will also be
|
// it even if the previous program using it exited uncleanly; it will also be
|
||||||
// unlinked upon a graceful exit (or when a new config does not use that socket).
|
// unlinked upon a graceful exit (or when a new config does not use that socket).
|
||||||
//
|
// Listen synchronizes binds to unix domain sockets to avoid race conditions
|
||||||
// TODO: Experimental API: subject to change or removal.
|
// while an existing socket is unlinked.
|
||||||
func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
|
||||||
if na.IsUnixNetwork() {
|
if na.IsUnixNetwork() {
|
||||||
unixSocketsMu.Lock()
|
unixSocketsMu.Lock()
|
||||||
@ -153,50 +153,49 @@ func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net
|
|||||||
err error
|
err error
|
||||||
address string
|
address string
|
||||||
unixFileMode fs.FileMode
|
unixFileMode fs.FileMode
|
||||||
isAbstractUnixSocket bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// split unix socket addr early so lnKey
|
// split unix socket addr early so lnKey
|
||||||
// is independent of permissions bits
|
// is independent of permissions bits
|
||||||
if na.IsUnixNetwork() {
|
if na.IsUnixNetwork() {
|
||||||
var err error
|
|
||||||
address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
|
address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
isAbstractUnixSocket = strings.HasPrefix(address, "@")
|
} else if na.IsFdNetwork() {
|
||||||
|
address = na.Host
|
||||||
} else {
|
} else {
|
||||||
address = na.JoinHostPort(portOffset)
|
address = na.JoinHostPort(portOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this is a unix socket, see if we already have it open,
|
|
||||||
// force socket permissions on it and return early
|
|
||||||
if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
|
|
||||||
if !isAbstractUnixSocket {
|
|
||||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return socket, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lnKey := listenerKey(na.Network, address)
|
|
||||||
|
|
||||||
if strings.HasPrefix(na.Network, "ip") {
|
if strings.HasPrefix(na.Network, "ip") {
|
||||||
ln, err = config.ListenPacket(ctx, na.Network, address)
|
ln, err = config.ListenPacket(ctx, na.Network, address)
|
||||||
} else {
|
} else {
|
||||||
|
if na.IsUnixNetwork() {
|
||||||
|
// if this is a unix socket, see if we already have it open
|
||||||
|
ln, err = reuseUnixSocket(na.Network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ln == nil && err == nil {
|
||||||
|
// otherwise, create a new listener
|
||||||
|
lnKey := listenerKey(na.Network, address)
|
||||||
ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
|
ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ln == nil {
|
if ln == nil {
|
||||||
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
return nil, fmt.Errorf("unsupported network type: %s", na.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsUnixNetwork(na.Network) {
|
if IsUnixNetwork(na.Network) {
|
||||||
|
isAbstractUnixSocket := strings.HasPrefix(address, "@")
|
||||||
if !isAbstractUnixSocket {
|
if !isAbstractUnixSocket {
|
||||||
if err := os.Chmod(address, unixFileMode); err != nil {
|
err = os.Chmod(address, unixFileMode)
|
||||||
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,18 +210,22 @@ func (na NetworkAddress) IsUnixNetwork() bool {
|
|||||||
return IsUnixNetwork(na.Network)
|
return IsUnixNetwork(na.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsUnixNetwork returns true if na.Network is
|
||||||
|
// fd or fdgram.
|
||||||
|
func (na NetworkAddress) IsFdNetwork() bool {
|
||||||
|
return IsFdNetwork(na.Network)
|
||||||
|
}
|
||||||
|
|
||||||
// JoinHostPort is like net.JoinHostPort, but where the port
|
// JoinHostPort is like net.JoinHostPort, but where the port
|
||||||
// is StartPort + offset.
|
// is StartPort + offset.
|
||||||
func (na NetworkAddress) JoinHostPort(offset uint) string {
|
func (na NetworkAddress) JoinHostPort(offset uint) string {
|
||||||
if na.IsUnixNetwork() {
|
if na.IsUnixNetwork() || na.IsFdNetwork() {
|
||||||
return na.Host
|
return na.Host
|
||||||
}
|
}
|
||||||
return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
|
return net.JoinHostPort(na.Host, strconv.FormatUint(uint64(na.StartPort+offset), 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand returns one NetworkAddress for each port in the port range.
|
// Expand returns one NetworkAddress for each port in the port range.
|
||||||
//
|
|
||||||
// This is EXPERIMENTAL and subject to change or removal.
|
|
||||||
func (na NetworkAddress) Expand() []NetworkAddress {
|
func (na NetworkAddress) Expand() []NetworkAddress {
|
||||||
size := na.PortRangeSize()
|
size := na.PortRangeSize()
|
||||||
addrs := make([]NetworkAddress, size)
|
addrs := make([]NetworkAddress, size)
|
||||||
@ -253,7 +256,7 @@ func (na NetworkAddress) PortRangeSize() uint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (na NetworkAddress) isLoopback() bool {
|
func (na NetworkAddress) isLoopback() bool {
|
||||||
if na.IsUnixNetwork() {
|
if na.IsUnixNetwork() || na.IsFdNetwork() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if na.Host == "localhost" {
|
if na.Host == "localhost" {
|
||||||
@ -297,6 +300,30 @@ func IsUnixNetwork(netw string) bool {
|
|||||||
return strings.HasPrefix(netw, "unix")
|
return strings.HasPrefix(netw, "unix")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsFdNetwork returns true if the netw is a fd network.
|
||||||
|
func IsFdNetwork(netw string) bool {
|
||||||
|
return strings.HasPrefix(netw, "fd")
|
||||||
|
}
|
||||||
|
|
||||||
|
// normally we would simply append the port,
|
||||||
|
// but if host is IPv6, we need to ensure it
|
||||||
|
// is enclosed in [ ]; net.JoinHostPort does
|
||||||
|
// this for us, but host might also have a
|
||||||
|
// network type in front (e.g. "tcp/") leading
|
||||||
|
// to "[tcp/::1]" which causes parsing failures
|
||||||
|
// later; what we need is "tcp/[::1]", so we have
|
||||||
|
// to split the network and host, then re-combine
|
||||||
|
func ParseNetworkAddressFromHostPort(host, port string) (NetworkAddress, error) {
|
||||||
|
network, addr, ok := strings.Cut(host, "/")
|
||||||
|
if !ok {
|
||||||
|
addr = network
|
||||||
|
network = ""
|
||||||
|
}
|
||||||
|
addr = strings.Trim(addr, "[]") // IPv6
|
||||||
|
networkAddr := JoinNetworkAddress(network, addr, port)
|
||||||
|
return ParseNetworkAddress(networkAddr)
|
||||||
|
}
|
||||||
|
|
||||||
// ParseNetworkAddress parses addr into its individual
|
// ParseNetworkAddress parses addr into its individual
|
||||||
// components. The input string is expected to be of
|
// components. The input string is expected to be of
|
||||||
// the form "network/host:port-range" where any part is
|
// the form "network/host:port-range" where any part is
|
||||||
@ -327,6 +354,12 @@ func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort ui
|
|||||||
Host: host,
|
Host: host,
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
if IsFdNetwork(network) {
|
||||||
|
return NetworkAddress{
|
||||||
|
Network: network,
|
||||||
|
Host: host,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
var start, end uint64
|
var start, end uint64
|
||||||
if port == "" {
|
if port == "" {
|
||||||
start = uint64(defaultPort)
|
start = uint64(defaultPort)
|
||||||
@ -367,7 +400,7 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
|
|||||||
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
network = strings.ToLower(strings.TrimSpace(beforeSlash))
|
||||||
a = afterSlash
|
a = afterSlash
|
||||||
}
|
}
|
||||||
if IsUnixNetwork(network) {
|
if IsUnixNetwork(network) || IsFdNetwork(network) {
|
||||||
host = a
|
host = a
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -398,7 +431,7 @@ func JoinNetworkAddress(network, host, port string) string {
|
|||||||
if network != "" {
|
if network != "" {
|
||||||
a = network + "/"
|
a = network + "/"
|
||||||
}
|
}
|
||||||
if (host != "" && port == "") || IsUnixNetwork(network) {
|
if (host != "" && port == "") || IsUnixNetwork(network) || IsFdNetwork(network) {
|
||||||
a += host
|
a += host
|
||||||
} else if port != "" {
|
} else if port != "" {
|
||||||
a += net.JoinHostPort(host, port)
|
a += net.JoinHostPort(host, port)
|
||||||
@ -406,9 +439,11 @@ func JoinNetworkAddress(network, host, port string) string {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
|
// ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module.
|
||||||
// The network will be transformed into a QUIC-compatible type (if unix, then
|
//
|
||||||
// unixgram will be used; otherwise, udp will be used).
|
// The network will be transformed into a QUIC-compatible type if the same address can be used with
|
||||||
|
// different networks. Currently this just means that for tcp, udp will be used with the same
|
||||||
|
// address instead.
|
||||||
//
|
//
|
||||||
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
|
// NOTE: This API is EXPERIMENTAL and may be changed or removed.
|
||||||
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICEarlyListener, error) {
|
func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICEarlyListener, error) {
|
||||||
@ -443,7 +478,13 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config
|
|||||||
Conn: h3ln,
|
Conn: h3ln,
|
||||||
VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() },
|
VerifySourceAddress: func(addr net.Addr) bool { return !limiter.Allow() },
|
||||||
}
|
}
|
||||||
earlyLn, err := tr.ListenEarly(http3.ConfigureTLSConfig(quicTlsConfig), &quic.Config{Allow0RTT: true})
|
earlyLn, err := tr.ListenEarly(
|
||||||
|
http3.ConfigureTLSConfig(quicTlsConfig),
|
||||||
|
&quic.Config{
|
||||||
|
Allow0RTT: true,
|
||||||
|
Tracer: qlog.DefaultConnectionTracer,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -616,7 +657,8 @@ func RegisterNetwork(network string, getListener ListenerFunc) {
|
|||||||
if network == "tcp" || network == "tcp4" || network == "tcp6" ||
|
if network == "tcp" || network == "tcp4" || network == "tcp6" ||
|
||||||
network == "udp" || network == "udp4" || network == "udp6" ||
|
network == "udp" || network == "udp4" || network == "udp6" ||
|
||||||
network == "unix" || network == "unixpacket" || network == "unixgram" ||
|
network == "unix" || network == "unixpacket" || network == "unixgram" ||
|
||||||
strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) {
|
strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) ||
|
||||||
|
network == "fd" || network == "fdgram" {
|
||||||
panic("network type " + network + " is reserved")
|
panic("network type " + network + " is reserved")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -124,7 +124,9 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler)
|
app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler)
|
||||||
|
|
||||||
for _, sub := range app.Subscriptions {
|
for _, sub := range app.Subscriptions {
|
||||||
if sub.HandlersRaw != nil {
|
if sub.HandlersRaw == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
handlersIface, err := ctx.LoadModule(sub, "HandlersRaw")
|
handlersIface, err := ctx.LoadModule(sub, "HandlersRaw")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loading event subscriber modules: %v", err)
|
return fmt.Errorf("loading event subscriber modules: %v", err)
|
||||||
@ -137,7 +139,6 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
return fmt.Errorf("no handlers defined")
|
return fmt.Errorf("no handlers defined")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -203,17 +204,75 @@ func (app *App) Provision(ctx caddy.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// the Go standard library does not let us serve only HTTP/2 using
|
|
||||||
// http.Server; we would probably need to write our own server
|
|
||||||
if !srv.protocol("h1") && (srv.protocol("h2") || srv.protocol("h2c")) {
|
|
||||||
return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no protocols configured explicitly, enable all except h2c
|
// if no protocols configured explicitly, enable all except h2c
|
||||||
if len(srv.Protocols) == 0 {
|
if len(srv.Protocols) == 0 {
|
||||||
srv.Protocols = []string{"h1", "h2", "h3"}
|
srv.Protocols = []string{"h1", "h2", "h3"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
srvProtocolsUnique := map[string]struct{}{}
|
||||||
|
for _, srvProtocol := range srv.Protocols {
|
||||||
|
srvProtocolsUnique[srvProtocol] = struct{}{}
|
||||||
|
}
|
||||||
|
_, h1ok := srvProtocolsUnique["h1"]
|
||||||
|
_, h2ok := srvProtocolsUnique["h2"]
|
||||||
|
_, h2cok := srvProtocolsUnique["h2c"]
|
||||||
|
|
||||||
|
// the Go standard library does not let us serve only HTTP/2 using
|
||||||
|
// http.Server; we would probably need to write our own server
|
||||||
|
if !h1ok && (h2ok || h2cok) {
|
||||||
|
return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if srv.ListenProtocols != nil {
|
||||||
|
if len(srv.ListenProtocols) != len(srv.Listen) {
|
||||||
|
return fmt.Errorf("server %s: listener protocols count does not match address count: %d != %d",
|
||||||
|
srvName, len(srv.ListenProtocols), len(srv.Listen))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, lnProtocols := range srv.ListenProtocols {
|
||||||
|
if lnProtocols != nil {
|
||||||
|
// populate empty listen protocols with server protocols
|
||||||
|
lnProtocolsDefault := false
|
||||||
|
var lnProtocolsInclude []string
|
||||||
|
srvProtocolsInclude := maps.Clone(srvProtocolsUnique)
|
||||||
|
|
||||||
|
// keep existing listener protocols unless they are empty
|
||||||
|
for _, lnProtocol := range lnProtocols {
|
||||||
|
if lnProtocol == "" {
|
||||||
|
lnProtocolsDefault = true
|
||||||
|
} else {
|
||||||
|
lnProtocolsInclude = append(lnProtocolsInclude, lnProtocol)
|
||||||
|
delete(srvProtocolsInclude, lnProtocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append server protocols to listener protocols if any listener protocols were empty
|
||||||
|
if lnProtocolsDefault {
|
||||||
|
for _, srvProtocol := range srv.Protocols {
|
||||||
|
if _, ok := srvProtocolsInclude[srvProtocol]; ok {
|
||||||
|
lnProtocolsInclude = append(lnProtocolsInclude, srvProtocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lnProtocolsIncludeUnique := map[string]struct{}{}
|
||||||
|
for _, lnProtocol := range lnProtocolsInclude {
|
||||||
|
lnProtocolsIncludeUnique[lnProtocol] = struct{}{}
|
||||||
|
}
|
||||||
|
_, h1ok := lnProtocolsIncludeUnique["h1"]
|
||||||
|
_, h2ok := lnProtocolsIncludeUnique["h2"]
|
||||||
|
_, h2cok := lnProtocolsIncludeUnique["h2c"]
|
||||||
|
|
||||||
|
// check if any listener protocols contain h2 or h2c without h1
|
||||||
|
if !h1ok && (h2ok || h2cok) {
|
||||||
|
return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.ListenProtocols[i] = lnProtocolsInclude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if not explicitly configured by the user, disallow TLS
|
// if not explicitly configured by the user, disallow TLS
|
||||||
// client auth bypass (domain fronting) which could
|
// client auth bypass (domain fronting) which could
|
||||||
// otherwise be exploited by sending an unprotected SNI
|
// otherwise be exploited by sending an unprotected SNI
|
||||||
@ -344,7 +403,7 @@ func (app *App) Validate() error {
|
|||||||
// check that every address in the port range is unique to this server;
|
// check that every address in the port range is unique to this server;
|
||||||
// we do not use <= here because PortRangeSize() adds 1 to EndPort for us
|
// we do not use <= here because PortRangeSize() adds 1 to EndPort for us
|
||||||
for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
|
for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
|
||||||
addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.Itoa(int(listenAddr.StartPort+i)))
|
addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.FormatUint(uint64(listenAddr.StartPort+i), 10))
|
||||||
if sn, ok := lnAddrs[addr]; ok {
|
if sn, ok := lnAddrs[addr]; ok {
|
||||||
return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
|
return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
|
||||||
}
|
}
|
||||||
@ -422,21 +481,59 @@ func (app *App) Start() error {
|
|||||||
srv.server.Handler = h2c.NewHandler(srv, h2server)
|
srv.server.Handler = h2c.NewHandler(srv, h2server)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, lnAddr := range srv.Listen {
|
for lnIndex, lnAddr := range srv.Listen {
|
||||||
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
|
return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.addresses = append(srv.addresses, listenAddr)
|
srv.addresses = append(srv.addresses, listenAddr)
|
||||||
|
|
||||||
|
protocols := srv.Protocols
|
||||||
|
if srv.ListenProtocols != nil && srv.ListenProtocols[lnIndex] != nil {
|
||||||
|
protocols = srv.ListenProtocols[lnIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolsUnique := map[string]struct{}{}
|
||||||
|
for _, protocol := range protocols {
|
||||||
|
protocolsUnique[protocol] = struct{}{}
|
||||||
|
}
|
||||||
|
_, h1ok := protocolsUnique["h1"]
|
||||||
|
_, h2ok := protocolsUnique["h2"]
|
||||||
|
_, h2cok := protocolsUnique["h2c"]
|
||||||
|
_, h3ok := protocolsUnique["h3"]
|
||||||
|
|
||||||
for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
|
for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
|
||||||
// create the listener for this socket
|
|
||||||
hostport := listenAddr.JoinHostPort(portOffset)
|
hostport := listenAddr.JoinHostPort(portOffset)
|
||||||
|
|
||||||
|
// enable TLS if there is a policy and if this is not the HTTP port
|
||||||
|
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
|
||||||
|
|
||||||
|
// enable HTTP/3 if configured
|
||||||
|
if h3ok && useTLS {
|
||||||
|
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
|
||||||
|
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if h3ok && !useTLS {
|
||||||
|
// Can only serve h3 with TLS enabled
|
||||||
|
app.logger.Warn("HTTP/3 skipped because it requires TLS",
|
||||||
|
zap.String("network", listenAddr.Network),
|
||||||
|
zap.String("addr", hostport))
|
||||||
|
}
|
||||||
|
|
||||||
|
if h1ok || h2ok && useTLS || h2cok {
|
||||||
|
// create the listener for this socket
|
||||||
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
|
lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
|
return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
|
||||||
}
|
}
|
||||||
ln := lnAny.(net.Listener)
|
ln, ok := lnAny.(net.Listener)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network)
|
||||||
|
}
|
||||||
|
|
||||||
// wrap listener before TLS (up to the TLS placeholder wrapper)
|
// wrap listener before TLS (up to the TLS placeholder wrapper)
|
||||||
var lnWrapperIdx int
|
var lnWrapperIdx int
|
||||||
@ -448,36 +545,9 @@ func (app *App) Start() error {
|
|||||||
ln = lnWrapper.WrapListener(ln)
|
ln = lnWrapper.WrapListener(ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable TLS if there is a policy and if this is not the HTTP port
|
|
||||||
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
|
|
||||||
if useTLS {
|
if useTLS {
|
||||||
// create TLS listener - this enables and terminates TLS
|
// create TLS listener - this enables and terminates TLS
|
||||||
ln = tls.NewListener(ln, tlsCfg)
|
ln = tls.NewListener(ln, tlsCfg)
|
||||||
|
|
||||||
// enable HTTP/3 if configured
|
|
||||||
if srv.protocol("h3") {
|
|
||||||
// Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses
|
|
||||||
// a different transport mechanism... which is fine, but the OS doesn't
|
|
||||||
// differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they
|
|
||||||
// are still one file on the system. So even though "unixpacket" and
|
|
||||||
// "unixgram" are different network types just as "tcp" and "udp" are,
|
|
||||||
// the OS will not let us use the same file as both STREAM and DGRAM.
|
|
||||||
if len(srv.Protocols) > 1 && listenAddr.IsUnixNetwork() {
|
|
||||||
app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket",
|
|
||||||
zap.String("file", hostport))
|
|
||||||
for i := range srv.Protocols {
|
|
||||||
if srv.Protocols[i] == "h3" {
|
|
||||||
srv.Protocols = append(srv.Protocols[:i], srv.Protocols[i+1:]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
|
|
||||||
if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// finish wrapping listener where we left off before TLS
|
// finish wrapping listener where we left off before TLS
|
||||||
@ -486,7 +556,7 @@ func (app *App) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle http2 if use tls listener wrapper
|
// handle http2 if use tls listener wrapper
|
||||||
if useTLS {
|
if h2ok {
|
||||||
http2lnWrapper := &http2Listener{
|
http2lnWrapper := &http2Listener{
|
||||||
Listener: ln,
|
Listener: ln,
|
||||||
server: srv.server,
|
server: srv.server,
|
||||||
@ -498,7 +568,7 @@ func (app *App) Start() error {
|
|||||||
|
|
||||||
// if binding to port 0, the OS chooses a port for us;
|
// if binding to port 0, the OS chooses a port for us;
|
||||||
// but the user won't know the port unless we print it
|
// but the user won't know the port unless we print it
|
||||||
if !listenAddr.IsUnixNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
|
if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
|
||||||
app.logger.Info("port 0 listener",
|
app.logger.Info("port 0 listener",
|
||||||
zap.String("input_address", lnAddr),
|
zap.String("input_address", lnAddr),
|
||||||
zap.String("actual_address", ln.Addr().String()))
|
zap.String("actual_address", ln.Addr().String()))
|
||||||
@ -512,11 +582,19 @@ func (app *App) Start() error {
|
|||||||
srv.listeners = append(srv.listeners, ln)
|
srv.listeners = append(srv.listeners, ln)
|
||||||
|
|
||||||
// enable HTTP/1 if configured
|
// enable HTTP/1 if configured
|
||||||
if srv.protocol("h1") {
|
if h1ok {
|
||||||
//nolint:errcheck
|
//nolint:errcheck
|
||||||
go srv.server.Serve(ln)
|
go srv.server.Serve(ln)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h2ok && !useTLS {
|
||||||
|
// Can only serve h2 with TLS enabled
|
||||||
|
app.logger.Warn("HTTP/2 skipped because it requires TLS",
|
||||||
|
zap.String("network", listenAddr.Network),
|
||||||
|
zap.String("addr", hostport))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.logger.Info("server running",
|
srv.logger.Info("server running",
|
||||||
|
|||||||
@ -17,6 +17,7 @@ package caddyhttp
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -64,17 +65,12 @@ type AutoHTTPSConfig struct {
|
|||||||
// enabled. To force automated certificate management
|
// enabled. To force automated certificate management
|
||||||
// regardless of loaded certificates, set this to true.
|
// regardless of loaded certificates, set this to true.
|
||||||
IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
|
IgnoreLoadedCerts bool `json:"ignore_loaded_certificates,omitempty"`
|
||||||
}
|
|
||||||
|
|
||||||
// Skipped returns true if name is in skipSlice, which
|
// If true, automatic HTTPS will prefer wildcard names
|
||||||
// should be either the Skip or SkipCerts field on ahc.
|
// and ignore non-wildcard names if both are available.
|
||||||
func (ahc AutoHTTPSConfig) Skipped(name string, skipSlice []string) bool {
|
// This allows for writing a config with top-level host
|
||||||
for _, n := range skipSlice {
|
// matchers without having those names produce certificates.
|
||||||
if name == n {
|
PreferWildcard bool `json:"prefer_wildcard,omitempty"`
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// automaticHTTPSPhase1 provisions all route matchers, determines
|
// automaticHTTPSPhase1 provisions all route matchers, determines
|
||||||
@ -158,7 +154,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v",
|
return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v",
|
||||||
srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err)
|
srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err)
|
||||||
}
|
}
|
||||||
if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
|
if !slices.Contains(srv.AutoHTTPS.Skip, d) {
|
||||||
serverDomainSet[d] = struct{}{}
|
serverDomainSet[d] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,6 +163,27 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if srv.AutoHTTPS.PreferWildcard {
|
||||||
|
wildcards := make(map[string]struct{})
|
||||||
|
for d := range serverDomainSet {
|
||||||
|
if strings.HasPrefix(d, "*.") {
|
||||||
|
wildcards[d[2:]] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for d := range serverDomainSet {
|
||||||
|
if strings.HasPrefix(d, "*.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
base := d
|
||||||
|
if idx := strings.Index(d, "."); idx != -1 {
|
||||||
|
base = d[idx+1:]
|
||||||
|
}
|
||||||
|
if _, ok := wildcards[base]; ok {
|
||||||
|
delete(serverDomainSet, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// nothing more to do here if there are no domains that qualify for
|
// nothing more to do here if there are no domains that qualify for
|
||||||
// automatic HTTPS and there are no explicit TLS connection policies:
|
// automatic HTTPS and there are no explicit TLS connection policies:
|
||||||
// if there is at least one domain but no TLS conn policy (F&&T), we'll
|
// if there is at least one domain but no TLS conn policy (F&&T), we'll
|
||||||
@ -193,7 +210,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
|||||||
} else {
|
} else {
|
||||||
for d := range serverDomainSet {
|
for d := range serverDomainSet {
|
||||||
if certmagic.SubjectQualifiesForCert(d) &&
|
if certmagic.SubjectQualifiesForCert(d) &&
|
||||||
!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
|
!slices.Contains(srv.AutoHTTPS.SkipCerts, d) {
|
||||||
// if a certificate for this name is already loaded,
|
// if a certificate for this name is already loaded,
|
||||||
// don't obtain another one for it, unless we are
|
// don't obtain another one for it, unless we are
|
||||||
// supposed to ignore loaded certificates
|
// supposed to ignore loaded certificates
|
||||||
@ -742,7 +759,7 @@ func (app *App) automaticHTTPSPhase2() error {
|
|||||||
)
|
)
|
||||||
err := app.tlsApp.Manage(app.allCertDomains)
|
err := app.tlsApp.Manage(app.allCertDomains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("managing certificates for %v: %s", app.allCertDomains, err)
|
return fmt.Errorf("managing certificates for %d domains: %s", len(app.allCertDomains), err)
|
||||||
}
|
}
|
||||||
app.allCertDomains = nil // no longer needed; allow GC to deallocate
|
app.allCertDomains = nil // no longer needed; allow GC to deallocate
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@ -76,9 +77,9 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
for provName, prov := range a.Providers {
|
for provName, prov := range a.Providers {
|
||||||
user, authed, err = prov.Authenticate(w, r)
|
user, authed, err = prov.Authenticate(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Error("auth provider returned error",
|
if c := a.logger.Check(zapcore.ErrorLevel, "auth provider returned error"); c != nil {
|
||||||
zap.String("provider", provName),
|
c.Write(zap.String("provider", provName), zap.Error(err))
|
||||||
zap.Error(err))
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if authed {
|
if authed {
|
||||||
|
|||||||
@ -126,6 +126,10 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
|||||||
// light (and possibly naïve) syntactic sugar
|
// light (and possibly naïve) syntactic sugar
|
||||||
m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
|
m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
|
||||||
|
|
||||||
|
// as a second pass, we'll strip the escape character from an escaped
|
||||||
|
// placeholder, so that it can be used as an input to other CEL functions
|
||||||
|
m.expandedExpr = escapedPlaceholderRegexp.ReplaceAllString(m.expandedExpr, escapedPlaceholderExpansion)
|
||||||
|
|
||||||
// our type adapter expands CEL's standard type support
|
// our type adapter expands CEL's standard type support
|
||||||
m.ta = celTypeAdapter{}
|
m.ta = celTypeAdapter{}
|
||||||
|
|
||||||
@ -159,14 +163,17 @@ func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
// create the CEL environment
|
// create the CEL environment
|
||||||
env, err := cel.NewEnv(
|
env, err := cel.NewEnv(
|
||||||
cel.Function(placeholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload(
|
cel.Function(CELPlaceholderFuncName, cel.SingletonBinaryBinding(m.caddyPlaceholderFunc), cel.Overload(
|
||||||
placeholderFuncName+"_httpRequest_string",
|
CELPlaceholderFuncName+"_httpRequest_string",
|
||||||
[]*cel.Type{httpRequestObjectType, cel.StringType},
|
[]*cel.Type{httpRequestObjectType, cel.StringType},
|
||||||
cel.AnyType,
|
cel.AnyType,
|
||||||
)),
|
)),
|
||||||
cel.Variable("request", httpRequestObjectType),
|
cel.Variable(CELRequestVarName, httpRequestObjectType),
|
||||||
cel.CustomTypeAdapter(m.ta),
|
cel.CustomTypeAdapter(m.ta),
|
||||||
ext.Strings(),
|
ext.Strings(),
|
||||||
|
ext.Bindings(),
|
||||||
|
ext.Lists(),
|
||||||
|
ext.Math(),
|
||||||
matcherLib,
|
matcherLib,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -247,7 +254,7 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
|
|||||||
return types.NewErr(
|
return types.NewErr(
|
||||||
"invalid request of type '%v' to %s(request, placeholderVarName)",
|
"invalid request of type '%v' to %s(request, placeholderVarName)",
|
||||||
lhs.Type(),
|
lhs.Type(),
|
||||||
placeholderFuncName,
|
CELPlaceholderFuncName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
phStr, ok := rhs.(types.String)
|
phStr, ok := rhs.(types.String)
|
||||||
@ -255,7 +262,7 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
|
|||||||
return types.NewErr(
|
return types.NewErr(
|
||||||
"invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)",
|
"invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)",
|
||||||
rhs.Type(),
|
rhs.Type(),
|
||||||
placeholderFuncName,
|
CELPlaceholderFuncName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +282,7 @@ var httpRequestCELType = cel.ObjectType("http.Request", traits.ReceiverType)
|
|||||||
type celHTTPRequest struct{ *http.Request }
|
type celHTTPRequest struct{ *http.Request }
|
||||||
|
|
||||||
func (cr celHTTPRequest) ResolveName(name string) (any, bool) {
|
func (cr celHTTPRequest) ResolveName(name string) (any, bool) {
|
||||||
if name == "request" {
|
if name == CELRequestVarName {
|
||||||
return cr, true
|
return cr, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
@ -340,7 +347,7 @@ func (celTypeAdapter) NativeToValue(value any) ref.Val {
|
|||||||
case time.Time:
|
case time.Time:
|
||||||
return types.Timestamp{Time: v}
|
return types.Timestamp{Time: v}
|
||||||
case error:
|
case error:
|
||||||
types.NewErr(v.Error())
|
return types.WrapErr(v)
|
||||||
}
|
}
|
||||||
return types.DefaultTypeAdapter.NativeToValue(value)
|
return types.DefaultTypeAdapter.NativeToValue(value)
|
||||||
}
|
}
|
||||||
@ -457,15 +464,15 @@ func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.Int
|
|||||||
callArgs := call.Args()
|
callArgs := call.Args()
|
||||||
reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute)
|
reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("missing 'request' argument")
|
return nil, errors.New("missing 'req' argument")
|
||||||
}
|
}
|
||||||
nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute)
|
nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("missing 'request' argument")
|
return nil, errors.New("missing 'req' argument")
|
||||||
}
|
}
|
||||||
varNames := nsAttr.CandidateVariableNames()
|
varNames := nsAttr.CandidateVariableNames()
|
||||||
if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != "request" {
|
if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != CELRequestVarName {
|
||||||
return nil, errors.New("missing 'request' argument")
|
return nil, errors.New("missing 'req' argument")
|
||||||
}
|
}
|
||||||
matcherData, ok := callArgs[1].(interpreter.InterpretableConst)
|
matcherData, ok := callArgs[1].(interpreter.InterpretableConst)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -499,7 +506,7 @@ func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions
|
|||||||
return func(celReq, matcherData ref.Val) ref.Val {
|
return func(celReq, matcherData ref.Val) ref.Val {
|
||||||
matcher, err := fac(matcherData)
|
matcher, err := fac(matcherData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.NewErr(err.Error())
|
return types.WrapErr(err)
|
||||||
}
|
}
|
||||||
httpReq := celReq.Value().(celHTTPRequest)
|
httpReq := celReq.Value().(celHTTPRequest)
|
||||||
return types.Bool(matcher.Match(httpReq.Request))
|
return types.Bool(matcher.Match(httpReq.Request))
|
||||||
@ -524,7 +531,7 @@ func celMatcherStringListMacroExpander(funcName string) cel.MacroFactory {
|
|||||||
return nil, eh.NewError(arg.ID(), "matcher arguments must be string constants")
|
return nil, eh.NewError(arg.ID(), "matcher arguments must be string constants")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return eh.NewCall(funcName, eh.NewIdent("request"), eh.NewList(matchArgs...)), nil
|
return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), eh.NewList(matchArgs...)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,7 +545,7 @@ func celMatcherStringMacroExpander(funcName string) parser.MacroExpander {
|
|||||||
return nil, eh.NewError(0, "matcher requires one argument")
|
return nil, eh.NewError(0, "matcher requires one argument")
|
||||||
}
|
}
|
||||||
if isCELStringExpr(args[0]) {
|
if isCELStringExpr(args[0]) {
|
||||||
return eh.NewCall(funcName, eh.NewIdent("request"), args[0]), nil
|
return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), args[0]), nil
|
||||||
}
|
}
|
||||||
return nil, eh.NewError(args[0].ID(), "matcher argument must be a string literal")
|
return nil, eh.NewError(args[0].ID(), "matcher argument must be a string literal")
|
||||||
}
|
}
|
||||||
@ -572,7 +579,7 @@ func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander {
|
|||||||
return nil, eh.NewError(entry.AsMapEntry().Value().ID(), "matcher map values must be string or list literals")
|
return nil, eh.NewError(entry.AsMapEntry().Value().ID(), "matcher map values must be string or list literals")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return eh.NewCall(funcName, eh.NewIdent("request"), arg), nil
|
return eh.NewCall(funcName, eh.NewIdent(CELRequestVarName), arg), nil
|
||||||
case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind:
|
case ast.UnspecifiedExprKind, ast.CallKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.SelectKind:
|
||||||
// appeasing the linter :)
|
// appeasing the linter :)
|
||||||
}
|
}
|
||||||
@ -646,7 +653,7 @@ func isCELCaddyPlaceholderCall(e ast.Expr) bool {
|
|||||||
switch e.Kind() {
|
switch e.Kind() {
|
||||||
case ast.CallKind:
|
case ast.CallKind:
|
||||||
call := e.AsCall()
|
call := e.AsCall()
|
||||||
if call.FunctionName() == "caddyPlaceholder" {
|
if call.FunctionName() == CELPlaceholderFuncName {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
|
case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
|
||||||
@ -701,8 +708,15 @@ func isCELStringListLiteral(e ast.Expr) bool {
|
|||||||
// expressions with a proper CEL function call; this is
|
// expressions with a proper CEL function call; this is
|
||||||
// just for syntactic sugar.
|
// just for syntactic sugar.
|
||||||
var (
|
var (
|
||||||
placeholderRegexp = regexp.MustCompile(`{([a-zA-Z][\w.-]+)}`)
|
// The placeholder may not be preceded by a backslash; the expansion
|
||||||
placeholderExpansion = `caddyPlaceholder(request, "${1}")`
|
// will include the preceding character if it is not a backslash.
|
||||||
|
placeholderRegexp = regexp.MustCompile(`([^\\]|^){([a-zA-Z][\w.-]+)}`)
|
||||||
|
placeholderExpansion = `${1}ph(req, "${2}")`
|
||||||
|
|
||||||
|
// As a second pass, we need to strip the escape character in front of
|
||||||
|
// the placeholder, if it exists.
|
||||||
|
escapedPlaceholderRegexp = regexp.MustCompile(`\\{([a-zA-Z][\w.-]+)}`)
|
||||||
|
escapedPlaceholderExpansion = `{${1}}`
|
||||||
|
|
||||||
CELTypeJSON = cel.MapType(cel.StringType, cel.DynType)
|
CELTypeJSON = cel.MapType(cel.StringType, cel.DynType)
|
||||||
)
|
)
|
||||||
@ -710,7 +724,10 @@ var (
|
|||||||
var httpRequestObjectType = cel.ObjectType("http.Request")
|
var httpRequestObjectType = cel.ObjectType("http.Request")
|
||||||
|
|
||||||
// The name of the CEL function which accesses Replacer values.
|
// The name of the CEL function which accesses Replacer values.
|
||||||
const placeholderFuncName = "caddyPlaceholder"
|
const CELPlaceholderFuncName = "ph"
|
||||||
|
|
||||||
|
// The name of the CEL request variable.
|
||||||
|
const CELRequestVarName = "req"
|
||||||
|
|
||||||
const MatcherNameCtxKey = "matcher_name"
|
const MatcherNameCtxKey = "matcher_name"
|
||||||
|
|
||||||
|
|||||||
@ -70,12 +70,35 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
wantResult: true,
|
wantResult: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "header error (MatchHeader)",
|
name: "header matches an escaped placeholder value (MatchHeader)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `header({'Field': '\\\{foobar}'})`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
httpHeader: &http.Header{"Field": []string{"{foobar}"}},
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "header matches an placeholder replaced during the header matcher (MatchHeader)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `header({'Field': '\{http.request.uri.path}'})`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
httpHeader: &http.Header{"Field": []string{"/foo"}},
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "header error, invalid escape sequence (MatchHeader)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `header({'Field': '\\{foobar}'})`,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "header error, needs to be JSON syntax with field as key (MatchHeader)",
|
||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `header('foo')`,
|
Expr: `header('foo')`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -110,8 +133,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `header_regexp('foo')`,
|
Expr: `header_regexp('foo')`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -143,7 +164,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `host(80)`,
|
Expr: `host(80)`,
|
||||||
},
|
},
|
||||||
urlTarget: "http://localhost:80",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -169,8 +189,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `method()`,
|
Expr: `method()`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://foo.example.com",
|
|
||||||
httpMethod: "PUT",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -266,7 +284,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `protocol()`,
|
Expr: `protocol()`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -274,7 +291,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `protocol('grpc', 'https')`,
|
Expr: `protocol('grpc', 'https')`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -282,7 +298,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `protocol(true)`,
|
Expr: `protocol(true)`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -330,7 +345,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `query({1: "1"})`,
|
Expr: `query({1: "1"})`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -338,7 +352,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `query(Message{field: "1"})`,
|
Expr: `query(Message{field: "1"})`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -346,7 +359,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `query({"debug": 1})`,
|
Expr: `query({"debug": 1})`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo/?debug=1",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -354,7 +366,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `query()`,
|
Expr: `query()`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo/?debug=1",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -362,7 +373,6 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
expression: &MatchExpression{
|
expression: &MatchExpression{
|
||||||
Expr: `remote_ip()`,
|
Expr: `remote_ip()`,
|
||||||
},
|
},
|
||||||
urlTarget: "https://example.com/foo",
|
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -373,6 +383,67 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
|||||||
urlTarget: "https://example.com/foo",
|
urlTarget: "https://example.com/foo",
|
||||||
wantResult: true,
|
wantResult: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "vars value (VarsMatcher)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars({'foo': 'bar'})`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars matches placeholder, needs escape (VarsMatcher)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars({'\{http.request.uri.path}': '/foo'})`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars error wrong syntax (VarsMatcher)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars('foo', 'bar')`,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars error no args (VarsMatcher)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars()`,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars_regexp value (MatchVarsRE)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars_regexp('foo', 'ba?r')`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars_regexp value with name (MatchVarsRE)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars_regexp('name', 'foo', 'ba?r')`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars_regexp matches placeholder, needs escape (MatchVarsRE)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars_regexp('\{http.request.uri.path}', '/fo?o')`,
|
||||||
|
},
|
||||||
|
urlTarget: "https://example.com/foo",
|
||||||
|
wantResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vars_regexp error no args (MatchVarsRE)",
|
||||||
|
expression: &MatchExpression{
|
||||||
|
Expr: `vars_regexp()`,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -396,6 +467,9 @@ func TestMatchExpressionMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
repl := caddy.NewReplacer()
|
repl := caddy.NewReplacer()
|
||||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||||
|
ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
})
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||||
|
|
||||||
@ -436,6 +510,9 @@ func BenchmarkMatchExpressionMatch(b *testing.B) {
|
|||||||
}
|
}
|
||||||
repl := caddy.NewReplacer()
|
repl := caddy.NewReplacer()
|
||||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||||
|
ctx = context.WithValue(ctx, VarsCtxKey, map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
})
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||||
if tc.clientCertificate != nil {
|
if tc.clientCertificate != nil {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -112,7 +113,8 @@ func (enc *Encode) Provision(ctx caddy.Context) error {
|
|||||||
"application/x-ttf*",
|
"application/x-ttf*",
|
||||||
"application/xhtml+xml*",
|
"application/xhtml+xml*",
|
||||||
"application/xml*",
|
"application/xml*",
|
||||||
"font/*",
|
"font/ttf*",
|
||||||
|
"font/otf*",
|
||||||
"image/svg+xml*",
|
"image/svg+xml*",
|
||||||
"image/vnd.microsoft.icon*",
|
"image/vnd.microsoft.icon*",
|
||||||
"image/x-icon*",
|
"image/x-icon*",
|
||||||
@ -265,6 +267,14 @@ func (rw *responseWriter) FlushError() error {
|
|||||||
// to rw.Write (see bug in #4314)
|
// to rw.Write (see bug in #4314)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// also flushes the encoder, if any
|
||||||
|
// see: https://github.com/jjiang-stripe/caddy-slow-gzip
|
||||||
|
if rw.w != nil {
|
||||||
|
err := rw.w.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
//nolint:bodyclose
|
//nolint:bodyclose
|
||||||
return http.NewResponseController(rw.ResponseWriter).Flush()
|
return http.NewResponseController(rw.ResponseWriter).Flush()
|
||||||
}
|
}
|
||||||
@ -432,12 +442,9 @@ func AcceptedEncodings(r *http.Request, preferredOrder []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set server preference
|
// set server preference
|
||||||
prefOrder := -1
|
prefOrder := slices.Index(preferredOrder, encName)
|
||||||
for i, p := range preferredOrder {
|
if prefOrder > -1 {
|
||||||
if encName == p {
|
prefOrder = len(preferredOrder) - prefOrder
|
||||||
prefOrder = len(preferredOrder) - i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs = append(prefs, encodingPreference{
|
prefs = append(prefs, encodingPreference{
|
||||||
@ -474,6 +481,7 @@ type encodingPreference struct {
|
|||||||
type Encoder interface {
|
type Encoder interface {
|
||||||
io.WriteCloser
|
io.WriteCloser
|
||||||
Reset(io.Writer)
|
Reset(io.Writer)
|
||||||
|
Flush() error // encoder by default buffers data to maximize compressing rate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encoding is a type which can create encoders of its kind
|
// Encoding is a type which can create encoders of its kind
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@ -52,14 +53,25 @@ var BrowseTemplate string
|
|||||||
type Browse struct {
|
type Browse struct {
|
||||||
// Filename of the template to use instead of the embedded browse template.
|
// Filename of the template to use instead of the embedded browse template.
|
||||||
TemplateFile string `json:"template_file,omitempty"`
|
TemplateFile string `json:"template_file,omitempty"`
|
||||||
|
|
||||||
// Determines whether or not targets of symlinks should be revealed.
|
// Determines whether or not targets of symlinks should be revealed.
|
||||||
RevealSymlinks bool `json:"reveal_symlinks,omitempty"`
|
RevealSymlinks bool `json:"reveal_symlinks,omitempty"`
|
||||||
|
|
||||||
|
// Override the default sort.
|
||||||
|
// It includes the following options:
|
||||||
|
// - sort_by: name(default), namedirfirst, size, time
|
||||||
|
// - order: asc(default), desc
|
||||||
|
// eg.:
|
||||||
|
// - `sort time desc` will sort by time in descending order
|
||||||
|
// - `sort size` will sort by size in ascending order
|
||||||
|
// The first option must be `sort_by` and the second option must be `order` (if exists).
|
||||||
|
SortOptions []string `json:"sort,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
fsrv.logger.Debug("browse enabled; listing directory contents",
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "browse enabled; listing directory contents"); c != nil {
|
||||||
zap.String("path", dirPath),
|
c.Write(zap.String("path", dirPath), zap.String("root", root))
|
||||||
zap.String("root", root))
|
}
|
||||||
|
|
||||||
// Navigation on the client-side gets messed up if the
|
// Navigation on the client-side gets messed up if the
|
||||||
// URL doesn't end in a trailing slash because hrefs to
|
// URL doesn't end in a trailing slash because hrefs to
|
||||||
@ -81,7 +93,9 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
|
|||||||
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
|
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
|
||||||
if r.URL.Path == "" || path.Base(origReq.URL.Path) == path.Base(r.URL.Path) {
|
if r.URL.Path == "" || path.Base(origReq.URL.Path) == path.Base(r.URL.Path) {
|
||||||
if !strings.HasSuffix(origReq.URL.Path, "/") {
|
if !strings.HasSuffix(origReq.URL.Path, "/") {
|
||||||
fsrv.logger.Debug("redirecting to trailing slash to preserve hrefs", zap.String("request_path", r.URL.Path))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to trailing slash to preserve hrefs"); c != nil {
|
||||||
|
c.Write(zap.String("request_path", r.URL.Path))
|
||||||
|
}
|
||||||
return redirect(w, r, origReq.URL.Path+"/")
|
return redirect(w, r, origReq.URL.Path+"/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -206,11 +220,34 @@ func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, fileSystem fs
|
|||||||
// browseApplyQueryParams applies query parameters to the listing.
|
// browseApplyQueryParams applies query parameters to the listing.
|
||||||
// It mutates the listing and may set cookies.
|
// It mutates the listing and may set cookies.
|
||||||
func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) {
|
func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Request, listing *browseTemplateContext) {
|
||||||
|
var orderParam, sortParam string
|
||||||
|
|
||||||
|
// The configs in Caddyfile have lower priority than Query params,
|
||||||
|
// so put it at first.
|
||||||
|
for idx, item := range fsrv.Browse.SortOptions {
|
||||||
|
// Only `sort` & `order`, 2 params are allowed
|
||||||
|
if idx >= 2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch item {
|
||||||
|
case sortByName, sortByNameDirFirst, sortBySize, sortByTime:
|
||||||
|
sortParam = item
|
||||||
|
case sortOrderAsc, sortOrderDesc:
|
||||||
|
orderParam = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layoutParam := r.URL.Query().Get("layout")
|
layoutParam := r.URL.Query().Get("layout")
|
||||||
sortParam := r.URL.Query().Get("sort")
|
|
||||||
orderParam := r.URL.Query().Get("order")
|
|
||||||
limitParam := r.URL.Query().Get("limit")
|
limitParam := r.URL.Query().Get("limit")
|
||||||
offsetParam := r.URL.Query().Get("offset")
|
offsetParam := r.URL.Query().Get("offset")
|
||||||
|
sortParamTmp := r.URL.Query().Get("sort")
|
||||||
|
if sortParamTmp != "" {
|
||||||
|
sortParam = sortParamTmp
|
||||||
|
}
|
||||||
|
orderParamTmp := r.URL.Query().Get("order")
|
||||||
|
if orderParamTmp != "" {
|
||||||
|
orderParam = orderParamTmp
|
||||||
|
}
|
||||||
|
|
||||||
switch layoutParam {
|
switch layoutParam {
|
||||||
case "list", "grid", "":
|
case "list", "grid", "":
|
||||||
@ -233,11 +270,11 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re
|
|||||||
// then figure out the order
|
// then figure out the order
|
||||||
switch orderParam {
|
switch orderParam {
|
||||||
case "":
|
case "":
|
||||||
orderParam = "asc"
|
orderParam = sortOrderAsc
|
||||||
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
||||||
orderParam = orderCookie.Value
|
orderParam = orderCookie.Value
|
||||||
}
|
}
|
||||||
case "asc", "desc":
|
case sortOrderAsc, sortOrderDesc:
|
||||||
http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
|
http.SetCookie(w, &http.Cookie{Name: "order", Value: orderParam, Secure: r.TLS != nil})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
|
{{ $nonce := uuidv4 -}}
|
||||||
|
{{ $nonceAttribute := print "nonce=" (quote $nonce) -}}
|
||||||
|
{{ $csp := printf "default-src 'none'; img-src 'self'; object-src 'none'; base-uri 'none'; script-src 'nonce-%s'; style-src 'nonce-%s'; frame-ancestors 'self'; form-action 'self';" $nonce $nonce -}}
|
||||||
|
{{/* To disable the Content-Security-Policy, set this to false */}}{{ $enableCsp := true -}}
|
||||||
|
{{ if $enableCsp -}}
|
||||||
|
{{- .RespHeader.Set "Content-Security-Policy" $csp -}}
|
||||||
|
{{ end -}}
|
||||||
{{- define "icon"}}
|
{{- define "icon"}}
|
||||||
{{- if .IsDir}}
|
{{- if .IsDir}}
|
||||||
{{- if .IsSymlink}}
|
{{- if .IsSymlink}}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M9 3a1 1 0 0 1 .608 .206l.1 .087l2.706 2.707h6.586a3 3 0 0 1 2.995 2.824l.005 .176v8a3 3 0 0 1 -2.824 2.995l-.176 .005h-14a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-11a3 3 0 0 1 2.824 -2.995l.176 -.005h4z" stroke-width="0" fill="currentColor"/>
|
<path d="M9 3a1 1 0 0 1 .608 .206l.1 .087l2.706 2.707h6.586a3 3 0 0 1 2.995 2.824l.005 .176v8a3 3 0 0 1 -2.824 2.995l-.176 .005h-14a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-11a3 3 0 0 1 2.824 -2.995l.176 -.005h4z" stroke-width="0" fill="currentColor"/>
|
||||||
<path fill="#000" d="M2.795 17.306c0-2.374 1.792-4.314 4.078-4.538v-1.104a.38.38 0 0 1 .651-.272l2.45 2.492a.132.132 0 0 1 0 .188l-2.45 2.492a.381.381 0 0 1-.651-.272V15.24c-1.889.297-3.436 1.39-3.817 3.26a2.809 2.809 0 0 1-.261-1.193Z" style="stroke-width:.127478"/>
|
<path fill="#000" d="M2.795 17.306c0-2.374 1.792-4.314 4.078-4.538v-1.104a.38.38 0 0 1 .651-.272l2.45 2.492a.132.132 0 0 1 0 .188l-2.45 2.492a.381.381 0 0 1-.651-.272V15.24c-1.889.297-3.436 1.39-3.817 3.26a2.809 2.809 0 0 1-.261-1.193Z" stroke-width=".127478"/>
|
||||||
</svg>
|
</svg>
|
||||||
{{- else}}
|
{{- else}}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-folder-filled" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
@ -303,7 +310,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<style>
|
<style {{ $nonceAttribute }}>
|
||||||
* { padding: 0; margin: 0; box-sizing: border-box; }
|
* { padding: 0; margin: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -342,6 +349,10 @@ svg,
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#layout-list, #layout-grid {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@ -768,10 +779,10 @@ footer {
|
|||||||
|
|
||||||
</style>
|
</style>
|
||||||
{{- if eq .Layout "grid"}}
|
{{- if eq .Layout "grid"}}
|
||||||
<style>.wrapper { max-width: none; } main { margin-top: 1px; }</style>
|
<style {{ $nonceAttribute }}>.wrapper { max-width: none; } main { margin-top: 1px; }</style>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</head>
|
</head>
|
||||||
<body onload="initPage()">
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="breadcrumbs">Folder Path</div>
|
<div class="breadcrumbs">Folder Path</div>
|
||||||
@ -799,7 +810,7 @@ footer {
|
|||||||
</span>
|
</span>
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</div>
|
</div>
|
||||||
<a href="javascript:queryParam('layout', '')" id="layout-list" class='layout{{if eq $.Layout "list" ""}}current{{end}}'>
|
<a id="layout-list" class='layout{{if eq $.Layout "list" ""}}current{{end}}'>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-list" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-list" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"/>
|
<path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v2a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"/>
|
||||||
@ -807,7 +818,7 @@ footer {
|
|||||||
</svg>
|
</svg>
|
||||||
List
|
List
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript:queryParam('layout', 'grid')" id="layout-grid" class='layout{{if eq $.Layout "grid"}}current{{end}}'>
|
<a id="layout-grid" class='layout{{if eq $.Layout "grid"}}current{{end}}'>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-grid" width="16" height="16" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/>
|
<path d="M4 4m0 1a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1z"/>
|
||||||
@ -886,7 +897,7 @@ footer {
|
|||||||
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"/>
|
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0"/>
|
||||||
<path d="M21 21l-6 -6"/>
|
<path d="M21 21l-6 -6"/>
|
||||||
</svg>
|
</svg>
|
||||||
<input type="search" placeholder="Search" id="filter" onkeyup='filter()'>
|
<input type="search" placeholder="Search" id="filter">
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
@ -980,7 +991,7 @@ footer {
|
|||||||
<div class="sizebar">
|
<div class="sizebar">
|
||||||
<div class="sizebar-bar"></div>
|
<div class="sizebar-bar"></div>
|
||||||
<div class="sizebar-text">
|
<div class="sizebar-text">
|
||||||
{{.HumanSize}}
|
{{if .IsSymlink}}↱ {{end}}{{.HumanSize}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -1000,70 +1011,70 @@ footer {
|
|||||||
<footer>
|
<footer>
|
||||||
Served with
|
Served with
|
||||||
<a rel="noopener noreferrer" href="https://caddyserver.com">
|
<a rel="noopener noreferrer" href="https://caddyserver.com">
|
||||||
<svg class="caddy-logo" viewBox="0 0 379 114" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;">
|
<svg class="caddy-logo" viewBox="0 0 379 114" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" fill-rule="evenodd" clip-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<g transform="matrix(1,0,0,1,-1982.99,-530.985)">
|
<g transform="matrix(1,0,0,1,-1982.99,-530.985)">
|
||||||
<g transform="matrix(1.16548,0,0,1.10195,1823.12,393.466)">
|
<g transform="matrix(1.16548,0,0,1.10195,1823.12,393.466)">
|
||||||
<g transform="matrix(1,0,0,1,0.233052,1.17986)">
|
<g transform="matrix(1,0,0,1,0.233052,1.17986)">
|
||||||
<g id="Icon" transform="matrix(0.858013,0,0,0.907485,-3224.99,-1435.83)">
|
<g id="Icon" transform="matrix(0.858013,0,0,0.907485,-3224.99,-1435.83)">
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)">
|
<g transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)">
|
||||||
<path d="M3901.56,610.734C3893.53,610.261 3886.06,608.1 3879.2,604.877C3872.24,601.608 3866.04,597.093 3860.8,591.633C3858.71,589.457 3856.76,587.149 3854.97,584.709C3853.2,582.281 3851.57,579.733 3850.13,577.066C3845.89,569.224 3843.21,560.381 3842.89,550.868C3842.57,543.321 3843.64,536.055 3845.94,529.307C3848.37,522.203 3852.08,515.696 3856.83,510.049L3855.79,509.095C3850.39,514.54 3846.02,520.981 3842.9,528.125C3839.84,535.125 3838.03,542.781 3837.68,550.868C3837.34,561.391 3839.51,571.425 3843.79,580.306C3845.27,583.38 3847.03,586.304 3849.01,589.049C3851.01,591.806 3853.24,594.39 3855.69,596.742C3861.75,602.568 3869,607.19 3877.03,610.1C3884.66,612.867 3892.96,614.059 3901.56,613.552L3901.56,610.734Z" style="fill:rgb(0,144,221);"/>
|
<path d="M3901.56,610.734C3893.53,610.261 3886.06,608.1 3879.2,604.877C3872.24,601.608 3866.04,597.093 3860.8,591.633C3858.71,589.457 3856.76,587.149 3854.97,584.709C3853.2,582.281 3851.57,579.733 3850.13,577.066C3845.89,569.224 3843.21,560.381 3842.89,550.868C3842.57,543.321 3843.64,536.055 3845.94,529.307C3848.37,522.203 3852.08,515.696 3856.83,510.049L3855.79,509.095C3850.39,514.54 3846.02,520.981 3842.9,528.125C3839.84,535.125 3838.03,542.781 3837.68,550.868C3837.34,561.391 3839.51,571.425 3843.79,580.306C3845.27,583.38 3847.03,586.304 3849.01,589.049C3851.01,591.806 3853.24,594.39 3855.69,596.742C3861.75,602.568 3869,607.19 3877.03,610.1C3884.66,612.867 3892.96,614.059 3901.56,613.552L3901.56,610.734Z" fill="rgb(0,144,221)"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)">
|
<g transform="matrix(-0.191794,-0.715786,0.715786,-0.191794,4329.14,4673.64)">
|
||||||
<path d="M3875.69,496.573C3879.62,494.538 3883.8,492.897 3888.2,491.786C3892.49,490.704 3896.96,490.124 3901.56,490.032C3903.82,490.13 3906.03,490.332 3908.21,490.688C3917.13,492.147 3925.19,495.814 3932.31,500.683C3936.13,503.294 3939.59,506.335 3942.81,509.619C3947.09,513.98 3950.89,518.816 3953.85,524.232C3958.2,532.197 3960.96,541.186 3961.32,550.868C3961.61,558.748 3960.46,566.345 3957.88,573.322C3956.09,578.169 3953.7,582.753 3950.66,586.838C3947.22,591.461 3942.96,595.427 3938.27,598.769C3933.66,602.055 3928.53,604.619 3923.09,606.478C3922.37,606.721 3921.6,606.805 3920.93,607.167C3920.42,607.448 3920.14,607.854 3919.69,608.224L3920.37,610.389C3920.98,610.432 3921.47,610.573 3922.07,610.474C3922.86,610.344 3923.55,609.883 3924.28,609.566C3931.99,606.216 3938.82,601.355 3944.57,595.428C3947.02,592.903 3949.25,590.174 3951.31,587.319C3953.59,584.168 3955.66,580.853 3957.43,577.348C3961.47,569.34 3964.01,560.422 3964.36,550.868C3964.74,540.511 3962.66,530.628 3958.48,521.868C3955.57,515.775 3951.72,510.163 3946.95,505.478C3943.37,501.962 3939.26,498.99 3934.84,496.562C3926.88,492.192 3917.87,489.76 3908.37,489.229C3906.12,489.104 3903.86,489.054 3901.56,489.154C3896.87,489.06 3892.3,489.519 3887.89,490.397C3883.3,491.309 3878.89,492.683 3874.71,494.525L3875.69,496.573Z" style="fill:rgb(0,144,221);"/>
|
<path d="M3875.69,496.573C3879.62,494.538 3883.8,492.897 3888.2,491.786C3892.49,490.704 3896.96,490.124 3901.56,490.032C3903.82,490.13 3906.03,490.332 3908.21,490.688C3917.13,492.147 3925.19,495.814 3932.31,500.683C3936.13,503.294 3939.59,506.335 3942.81,509.619C3947.09,513.98 3950.89,518.816 3953.85,524.232C3958.2,532.197 3960.96,541.186 3961.32,550.868C3961.61,558.748 3960.46,566.345 3957.88,573.322C3956.09,578.169 3953.7,582.753 3950.66,586.838C3947.22,591.461 3942.96,595.427 3938.27,598.769C3933.66,602.055 3928.53,604.619 3923.09,606.478C3922.37,606.721 3921.6,606.805 3920.93,607.167C3920.42,607.448 3920.14,607.854 3919.69,608.224L3920.37,610.389C3920.98,610.432 3921.47,610.573 3922.07,610.474C3922.86,610.344 3923.55,609.883 3924.28,609.566C3931.99,606.216 3938.82,601.355 3944.57,595.428C3947.02,592.903 3949.25,590.174 3951.31,587.319C3953.59,584.168 3955.66,580.853 3957.43,577.348C3961.47,569.34 3964.01,560.422 3964.36,550.868C3964.74,540.511 3962.66,530.628 3958.48,521.868C3955.57,515.775 3951.72,510.163 3946.95,505.478C3943.37,501.962 3939.26,498.99 3934.84,496.562C3926.88,492.192 3917.87,489.76 3908.37,489.229C3906.12,489.104 3903.86,489.054 3901.56,489.154C3896.87,489.06 3892.3,489.519 3887.89,490.397C3883.3,491.309 3878.89,492.683 3874.71,494.525L3875.69,496.573Z" fill="rgb(0,144,221)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(-3.37109,-0.514565,0.514565,-3.37109,4078.07,1806.88)">
|
<g transform="matrix(-3.37109,-0.514565,0.514565,-3.37109,4078.07,1806.88)">
|
||||||
<path d="M22,12C22,10.903 21.097,10 20,10C19.421,10 18.897,10.251 18.53,10.649C18.202,11.006 18,11.481 18,12C18,13.097 18.903,14 20,14C21.097,14 22,13.097 22,12Z" style="fill:none;fill-rule:nonzero;stroke:rgb(0,144,221);stroke-width:1.05px;"/>
|
<path d="M22,12C22,10.903 21.097,10 20,10C19.421,10 18.897,10.251 18.53,10.649C18.202,11.006 18,11.481 18,12C18,13.097 18.903,14 20,14C21.097,14 22,13.097 22,12Z" fill="none" fill-rule="nonzero" stroke="rgb(0,144,221)" stroke-width="1.05px"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(-5.33921,-5.26159,-3.12106,-6.96393,4073.87,1861.55)">
|
<g transform="matrix(-5.33921,-5.26159,-3.12106,-6.96393,4073.87,1861.55)">
|
||||||
<path d="M10.315,5.333C10.315,5.333 9.748,5.921 9.03,6.673C7.768,7.995 6.054,9.805 6.054,9.805L6.237,9.86C6.237,9.86 8.045,8.077 9.36,6.771C10.107,6.028 10.689,5.444 10.689,5.444L10.315,5.333Z" style="fill:rgb(0,144,221);"/>
|
<path d="M10.315,5.333C10.315,5.333 9.748,5.921 9.03,6.673C7.768,7.995 6.054,9.805 6.054,9.805L6.237,9.86C6.237,9.86 8.045,8.077 9.36,6.771C10.107,6.028 10.689,5.444 10.689,5.444L10.315,5.333Z" fill="rgb(0,144,221)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="Padlock" transform="matrix(3.11426,0,0,3.11426,3938.31,1737.25)">
|
<g id="Padlock" transform="matrix(3.11426,0,0,3.11426,3938.31,1737.25)">
|
||||||
<g>
|
<g>
|
||||||
<path d="M9.876,21L18.162,21C18.625,21 19,20.625 19,20.162L19,11.838C19,11.375 18.625,11 18.162,11L5.838,11C5.375,11 5,11.375 5,11.838L5,16.758" style="fill:none;stroke:rgb(34,182,56);stroke-width:1.89px;stroke-linecap:butt;stroke-linejoin:miter;"/>
|
<path d="M9.876,21L18.162,21C18.625,21 19,20.625 19,20.162L19,11.838C19,11.375 18.625,11 18.162,11L5.838,11C5.375,11 5,11.375 5,11.838L5,16.758" fill="none" stroke="rgb(34,182,56)" stroke-width="1.89px" stroke-linecap="butt" stroke-linejoin="miter"/>
|
||||||
<path d="M8,11L8,7C8,4.806 9.806,3 12,3C14.194,3 16,4.806 16,7L16,11" style="fill:none;fill-rule:nonzero;stroke:rgb(34,182,56);stroke-width:1.89px;"/>
|
<path d="M8,11L8,7C8,4.806 9.806,3 12,3C14.194,3 16,4.806 16,7L16,11" fill="none" fill-rule="nonzero" stroke="rgb(34,182,56)" stroke-width="1.89px"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(5.30977,0.697415,-0.697415,5.30977,3852.72,1727.97)">
|
<g transform="matrix(5.30977,0.697415,-0.697415,5.30977,3852.72,1727.97)">
|
||||||
<path d="M22,12C22,11.659 21.913,11.337 21.76,11.055C21.421,10.429 20.756,10 20,10C18.903,10 18,10.903 18,12C18,13.097 18.903,14 20,14C21.097,14 22,13.097 22,12Z" style="fill:none;fill-rule:nonzero;stroke:rgb(0,144,221);stroke-width:0.98px;"/>
|
<path d="M22,12C22,11.659 21.913,11.337 21.76,11.055C21.421,10.429 20.756,10 20,10C18.903,10 18,10.903 18,12C18,13.097 18.903,14 20,14C21.097,14 22,13.097 22,12Z" fill="none" fill-rule="nonzero" stroke="rgb(0,144,221)" stroke-width="0.98px"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(4.93114,2.49604,1.11018,5.44847,3921.41,1726.72)">
|
<g transform="matrix(4.93114,2.49604,1.11018,5.44847,3921.41,1726.72)">
|
||||||
<path d="M8.902,6.77C8.902,6.77 7.235,8.253 6.027,9.366C5.343,9.996 4.819,10.502 4.819,10.502L5.52,11.164C5.52,11.164 6.021,10.637 6.646,9.951C7.749,8.739 9.219,7.068 9.219,7.068L8.902,6.77Z" style="fill:rgb(0,144,221);"/>
|
<path d="M8.902,6.77C8.902,6.77 7.235,8.253 6.027,9.366C5.343,9.996 4.819,10.502 4.819,10.502L5.52,11.164C5.52,11.164 6.021,10.637 6.646,9.951C7.749,8.739 9.219,7.068 9.219,7.068L8.902,6.77Z" fill="rgb(0,144,221)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="Text">
|
<g id="Text">
|
||||||
<g id="Wordmark" transform="matrix(1.32271,0,0,2.60848,-899.259,-791.691)">
|
<g id="Wordmark" transform="matrix(1.32271,0,0,2.60848,-899.259,-791.691)">
|
||||||
<g id="y" transform="matrix(0.50291,0,0,0.281607,905.533,304.987)">
|
<g id="y" transform="matrix(0.50291,0,0,0.281607,905.533,304.987)">
|
||||||
<path d="M192.152,286.875L202.629,268.64C187.804,270.106 183.397,265.779 180.143,263.391C176.888,261.004 174.362,257.99 172.563,254.347C170.765,250.705 169.866,246.691 169.866,242.305L169.866,208.107L183.21,208.107L183.21,242.213C183.21,245.188 183.896,247.822 185.268,250.116C186.64,252.41 188.465,254.197 190.743,255.475C193.022,256.754 195.501,257.393 198.182,257.393C200.894,257.393 203.393,256.75 205.68,255.463C207.966,254.177 209.799,252.391 211.178,250.105C212.558,247.818 213.248,245.188 213.248,242.213L213.248,208.107L226.545,208.107L226.545,242.305C226.545,246.707 225.378,258.46 218.079,268.64C215.735,271.909 207.835,286.875 207.835,286.875L192.152,286.875Z" style="fill:rgb(47,47,47);fill-rule:nonzero;"/>
|
<path d="M192.152,286.875L202.629,268.64C187.804,270.106 183.397,265.779 180.143,263.391C176.888,261.004 174.362,257.99 172.563,254.347C170.765,250.705 169.866,246.691 169.866,242.305L169.866,208.107L183.21,208.107L183.21,242.213C183.21,245.188 183.896,247.822 185.268,250.116C186.64,252.41 188.465,254.197 190.743,255.475C193.022,256.754 195.501,257.393 198.182,257.393C200.894,257.393 203.393,256.75 205.68,255.463C207.966,254.177 209.799,252.391 211.178,250.105C212.558,247.818 213.248,245.188 213.248,242.213L213.248,208.107L226.545,208.107L226.545,242.305C226.545,246.707 225.378,258.46 218.079,268.64C215.735,271.909 207.835,286.875 207.835,286.875L192.152,286.875Z" fill="rgb(47,47,47)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
<g id="add" transform="matrix(0.525075,0,0,0.281607,801.871,304.987)">
|
<g id="add" transform="matrix(0.525075,0,0,0.281607,801.871,304.987)">
|
||||||
<g transform="matrix(116.242,0,0,116.242,161.846,267.39)">
|
<g transform="matrix(116.242,0,0,116.242,161.846,267.39)">
|
||||||
<path d="M0.276,0.012C0.227,0.012 0.186,0 0.15,-0.024C0.115,-0.048 0.088,-0.08 0.069,-0.12C0.05,-0.161 0.04,-0.205 0.04,-0.254C0.04,-0.305 0.051,-0.35 0.072,-0.39C0.094,-0.431 0.125,-0.463 0.165,-0.487C0.205,-0.51 0.254,-0.522 0.31,-0.522C0.366,-0.522 0.413,-0.51 0.452,-0.486C0.491,-0.463 0.521,-0.431 0.542,-0.39C0.562,-0.35 0.573,-0.305 0.573,-0.256L0.573,-0L0.458,-0L0.458,-0.095L0.456,-0.095C0.446,-0.076 0.433,-0.058 0.417,-0.042C0.401,-0.026 0.381,-0.013 0.358,-0.003C0.335,0.007 0.307,0.012 0.276,0.012ZM0.307,-0.086C0.337,-0.086 0.363,-0.093 0.386,-0.108C0.408,-0.123 0.426,-0.144 0.438,-0.17C0.45,-0.195 0.456,-0.224 0.456,-0.256C0.456,-0.288 0.45,-0.317 0.438,-0.342C0.426,-0.367 0.409,-0.387 0.387,-0.402C0.365,-0.417 0.338,-0.424 0.308,-0.424C0.276,-0.424 0.249,-0.417 0.226,-0.402C0.204,-0.387 0.186,-0.366 0.174,-0.341C0.162,-0.315 0.156,-0.287 0.156,-0.255C0.156,-0.224 0.162,-0.195 0.174,-0.169C0.186,-0.144 0.203,-0.123 0.226,-0.108C0.248,-0.093 0.275,-0.086 0.307,-0.086Z" style="fill:rgb(47,47,47);fill-rule:nonzero;"/>
|
<path d="M0.276,0.012C0.227,0.012 0.186,0 0.15,-0.024C0.115,-0.048 0.088,-0.08 0.069,-0.12C0.05,-0.161 0.04,-0.205 0.04,-0.254C0.04,-0.305 0.051,-0.35 0.072,-0.39C0.094,-0.431 0.125,-0.463 0.165,-0.487C0.205,-0.51 0.254,-0.522 0.31,-0.522C0.366,-0.522 0.413,-0.51 0.452,-0.486C0.491,-0.463 0.521,-0.431 0.542,-0.39C0.562,-0.35 0.573,-0.305 0.573,-0.256L0.573,-0L0.458,-0L0.458,-0.095L0.456,-0.095C0.446,-0.076 0.433,-0.058 0.417,-0.042C0.401,-0.026 0.381,-0.013 0.358,-0.003C0.335,0.007 0.307,0.012 0.276,0.012ZM0.307,-0.086C0.337,-0.086 0.363,-0.093 0.386,-0.108C0.408,-0.123 0.426,-0.144 0.438,-0.17C0.45,-0.195 0.456,-0.224 0.456,-0.256C0.456,-0.288 0.45,-0.317 0.438,-0.342C0.426,-0.367 0.409,-0.387 0.387,-0.402C0.365,-0.417 0.338,-0.424 0.308,-0.424C0.276,-0.424 0.249,-0.417 0.226,-0.402C0.204,-0.387 0.186,-0.366 0.174,-0.341C0.162,-0.315 0.156,-0.287 0.156,-0.255C0.156,-0.224 0.162,-0.195 0.174,-0.169C0.186,-0.144 0.203,-0.123 0.226,-0.108C0.248,-0.093 0.275,-0.086 0.307,-0.086Z" fill="rgb(47,47,47)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(116.242,0,0,116.242,226.592,267.39)">
|
<g transform="matrix(116.242,0,0,116.242,226.592,267.39)">
|
||||||
<path d="M0.306,0.012C0.265,0.012 0.229,0.006 0.196,-0.008C0.163,-0.021 0.135,-0.039 0.112,-0.064C0.089,-0.088 0.071,-0.117 0.059,-0.151C0.046,-0.185 0.04,-0.222 0.04,-0.263C0.04,-0.315 0.051,-0.36 0.072,-0.399C0.093,-0.437 0.122,-0.468 0.159,-0.489C0.196,-0.511 0.239,-0.522 0.287,-0.522C0.311,-0.522 0.333,-0.518 0.355,-0.511C0.377,-0.504 0.396,-0.493 0.413,-0.48C0.431,-0.466 0.445,-0.451 0.455,-0.433L0.456,-0.433L0.456,-0.73L0.571,-0.73L0.571,-0.261C0.571,-0.205 0.56,-0.156 0.537,-0.115C0.515,-0.074 0.484,-0.043 0.444,-0.021C0.405,0.001 0.358,0.012 0.306,0.012ZM0.306,-0.086C0.335,-0.086 0.361,-0.093 0.384,-0.107C0.406,-0.122 0.423,-0.141 0.436,-0.167C0.448,-0.192 0.455,-0.221 0.455,-0.255C0.455,-0.288 0.448,-0.317 0.436,-0.343C0.423,-0.368 0.406,-0.388 0.383,-0.402C0.361,-0.417 0.335,-0.424 0.305,-0.424C0.276,-0.424 0.251,-0.417 0.228,-0.402C0.206,-0.387 0.188,-0.368 0.175,-0.342C0.163,-0.317 0.156,-0.288 0.156,-0.255C0.156,-0.222 0.163,-0.193 0.175,-0.167C0.188,-0.142 0.206,-0.122 0.229,-0.108C0.251,-0.093 0.277,-0.086 0.306,-0.086Z" style="fill:rgb(47,47,47);fill-rule:nonzero;"/>
|
<path d="M0.306,0.012C0.265,0.012 0.229,0.006 0.196,-0.008C0.163,-0.021 0.135,-0.039 0.112,-0.064C0.089,-0.088 0.071,-0.117 0.059,-0.151C0.046,-0.185 0.04,-0.222 0.04,-0.263C0.04,-0.315 0.051,-0.36 0.072,-0.399C0.093,-0.437 0.122,-0.468 0.159,-0.489C0.196,-0.511 0.239,-0.522 0.287,-0.522C0.311,-0.522 0.333,-0.518 0.355,-0.511C0.377,-0.504 0.396,-0.493 0.413,-0.48C0.431,-0.466 0.445,-0.451 0.455,-0.433L0.456,-0.433L0.456,-0.73L0.571,-0.73L0.571,-0.261C0.571,-0.205 0.56,-0.156 0.537,-0.115C0.515,-0.074 0.484,-0.043 0.444,-0.021C0.405,0.001 0.358,0.012 0.306,0.012ZM0.306,-0.086C0.335,-0.086 0.361,-0.093 0.384,-0.107C0.406,-0.122 0.423,-0.141 0.436,-0.167C0.448,-0.192 0.455,-0.221 0.455,-0.255C0.455,-0.288 0.448,-0.317 0.436,-0.343C0.423,-0.368 0.406,-0.388 0.383,-0.402C0.361,-0.417 0.335,-0.424 0.305,-0.424C0.276,-0.424 0.251,-0.417 0.228,-0.402C0.206,-0.387 0.188,-0.368 0.175,-0.342C0.163,-0.317 0.156,-0.288 0.156,-0.255C0.156,-0.222 0.163,-0.193 0.175,-0.167C0.188,-0.142 0.206,-0.122 0.229,-0.108C0.251,-0.093 0.277,-0.086 0.306,-0.086Z" fill="rgb(47,47,47)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(116.242,0,0,116.242,290.293,267.39)">
|
<g transform="matrix(116.242,0,0,116.242,290.293,267.39)">
|
||||||
<path d="M0.306,0.012C0.265,0.012 0.229,0.006 0.196,-0.008C0.163,-0.021 0.135,-0.039 0.112,-0.064C0.089,-0.088 0.071,-0.117 0.059,-0.151C0.046,-0.185 0.04,-0.222 0.04,-0.263C0.04,-0.315 0.051,-0.36 0.072,-0.399C0.093,-0.437 0.122,-0.468 0.159,-0.489C0.196,-0.511 0.239,-0.522 0.287,-0.522C0.311,-0.522 0.333,-0.518 0.355,-0.511C0.377,-0.504 0.396,-0.493 0.413,-0.48C0.431,-0.466 0.445,-0.451 0.455,-0.433L0.456,-0.433L0.456,-0.73L0.571,-0.73L0.571,-0.261C0.571,-0.205 0.56,-0.156 0.537,-0.115C0.515,-0.074 0.484,-0.043 0.444,-0.021C0.405,0.001 0.358,0.012 0.306,0.012ZM0.306,-0.086C0.335,-0.086 0.361,-0.093 0.384,-0.107C0.406,-0.122 0.423,-0.141 0.436,-0.167C0.448,-0.192 0.455,-0.221 0.455,-0.255C0.455,-0.288 0.448,-0.317 0.436,-0.343C0.423,-0.368 0.406,-0.388 0.383,-0.402C0.361,-0.417 0.335,-0.424 0.305,-0.424C0.276,-0.424 0.251,-0.417 0.228,-0.402C0.206,-0.387 0.188,-0.368 0.175,-0.342C0.163,-0.317 0.156,-0.288 0.156,-0.255C0.156,-0.222 0.163,-0.193 0.175,-0.167C0.188,-0.142 0.206,-0.122 0.229,-0.108C0.251,-0.093 0.277,-0.086 0.306,-0.086Z" style="fill:rgb(47,47,47);fill-rule:nonzero;"/>
|
<path d="M0.306,0.012C0.265,0.012 0.229,0.006 0.196,-0.008C0.163,-0.021 0.135,-0.039 0.112,-0.064C0.089,-0.088 0.071,-0.117 0.059,-0.151C0.046,-0.185 0.04,-0.222 0.04,-0.263C0.04,-0.315 0.051,-0.36 0.072,-0.399C0.093,-0.437 0.122,-0.468 0.159,-0.489C0.196,-0.511 0.239,-0.522 0.287,-0.522C0.311,-0.522 0.333,-0.518 0.355,-0.511C0.377,-0.504 0.396,-0.493 0.413,-0.48C0.431,-0.466 0.445,-0.451 0.455,-0.433L0.456,-0.433L0.456,-0.73L0.571,-0.73L0.571,-0.261C0.571,-0.205 0.56,-0.156 0.537,-0.115C0.515,-0.074 0.484,-0.043 0.444,-0.021C0.405,0.001 0.358,0.012 0.306,0.012ZM0.306,-0.086C0.335,-0.086 0.361,-0.093 0.384,-0.107C0.406,-0.122 0.423,-0.141 0.436,-0.167C0.448,-0.192 0.455,-0.221 0.455,-0.255C0.455,-0.288 0.448,-0.317 0.436,-0.343C0.423,-0.368 0.406,-0.388 0.383,-0.402C0.361,-0.417 0.335,-0.424 0.305,-0.424C0.276,-0.424 0.251,-0.417 0.228,-0.402C0.206,-0.387 0.188,-0.368 0.175,-0.342C0.163,-0.317 0.156,-0.288 0.156,-0.255C0.156,-0.222 0.163,-0.193 0.175,-0.167C0.188,-0.142 0.206,-0.122 0.229,-0.108C0.251,-0.093 0.277,-0.086 0.306,-0.086Z" fill="rgb(47,47,47)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="c" transform="matrix(-0.0716462,0.31304,-0.583685,-0.0384251,1489.76,-444.051)">
|
<g id="c" transform="matrix(-0.0716462,0.31304,-0.583685,-0.0384251,1489.76,-444.051)">
|
||||||
<path d="M2668.11,700.4C2666.79,703.699 2666.12,707.216 2666.12,710.766C2666.12,726.268 2678.71,738.854 2694.21,738.854C2709.71,738.854 2722.3,726.268 2722.3,710.766C2722.3,704.111 2719.93,697.672 2715.63,692.597L2707.63,699.378C2710.33,702.559 2711.57,706.602 2711.81,710.766C2712.2,717.38 2706.61,724.52 2697.27,726.637C2683.9,728.581 2676.61,720.482 2676.61,710.766C2676.61,708.541 2677.03,706.336 2677.85,704.269L2668.11,700.4Z" style="fill:rgb(46,46,46);"/>
|
<path d="M2668.11,700.4C2666.79,703.699 2666.12,707.216 2666.12,710.766C2666.12,726.268 2678.71,738.854 2694.21,738.854C2709.71,738.854 2722.3,726.268 2722.3,710.766C2722.3,704.111 2719.93,697.672 2715.63,692.597L2707.63,699.378C2710.33,702.559 2711.57,706.602 2711.81,710.766C2712.2,717.38 2706.61,724.52 2697.27,726.637C2683.9,728.581 2676.61,720.482 2676.61,710.766C2676.61,708.541 2677.03,706.336 2677.85,704.269L2668.11,700.4Z" fill="rgb(46,46,46)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g id="R" transform="matrix(0.426446,0,0,0.451034,-1192.44,-722.167)">
|
<g id="R" transform="matrix(0.426446,0,0,0.451034,-1192.44,-722.167)">
|
||||||
<g transform="matrix(1,0,0,1,-0.10786,0.450801)">
|
<g transform="matrix(1,0,0,1,-0.10786,0.450801)">
|
||||||
<g transform="matrix(12.1247,0,0,12.1247,3862.61,1929.9)">
|
<g transform="matrix(12.1247,0,0,12.1247,3862.61,1929.9)">
|
||||||
<path d="M0.073,-0L0.073,-0.7L0.383,-0.7C0.428,-0.7 0.469,-0.69 0.506,-0.67C0.543,-0.651 0.572,-0.623 0.594,-0.588C0.616,-0.553 0.627,-0.512 0.627,-0.465C0.627,-0.418 0.615,-0.377 0.592,-0.342C0.569,-0.306 0.539,-0.279 0.501,-0.259L0.57,-0.128C0.574,-0.12 0.579,-0.115 0.584,-0.111C0.59,-0.107 0.596,-0.106 0.605,-0.106L0.664,-0.106L0.664,-0L0.587,-0C0.56,-0 0.535,-0.007 0.514,-0.02C0.493,-0.034 0.476,-0.052 0.463,-0.075L0.381,-0.232C0.375,-0.231 0.368,-0.231 0.361,-0.231C0.354,-0.231 0.347,-0.231 0.34,-0.231L0.192,-0.231L0.192,-0L0.073,-0ZM0.192,-0.336L0.368,-0.336C0.394,-0.336 0.417,-0.341 0.438,-0.351C0.459,-0.361 0.476,-0.376 0.489,-0.396C0.501,-0.415 0.507,-0.438 0.507,-0.465C0.507,-0.492 0.501,-0.516 0.488,-0.535C0.475,-0.554 0.459,-0.569 0.438,-0.579C0.417,-0.59 0.394,-0.595 0.369,-0.595L0.192,-0.595L0.192,-0.336Z" style="fill:rgb(46,46,46);fill-rule:nonzero;"/>
|
<path d="M0.073,-0L0.073,-0.7L0.383,-0.7C0.428,-0.7 0.469,-0.69 0.506,-0.67C0.543,-0.651 0.572,-0.623 0.594,-0.588C0.616,-0.553 0.627,-0.512 0.627,-0.465C0.627,-0.418 0.615,-0.377 0.592,-0.342C0.569,-0.306 0.539,-0.279 0.501,-0.259L0.57,-0.128C0.574,-0.12 0.579,-0.115 0.584,-0.111C0.59,-0.107 0.596,-0.106 0.605,-0.106L0.664,-0.106L0.664,-0L0.587,-0C0.56,-0 0.535,-0.007 0.514,-0.02C0.493,-0.034 0.476,-0.052 0.463,-0.075L0.381,-0.232C0.375,-0.231 0.368,-0.231 0.361,-0.231C0.354,-0.231 0.347,-0.231 0.34,-0.231L0.192,-0.231L0.192,-0L0.073,-0ZM0.192,-0.336L0.368,-0.336C0.394,-0.336 0.417,-0.341 0.438,-0.351C0.459,-0.361 0.476,-0.376 0.489,-0.396C0.501,-0.415 0.507,-0.438 0.507,-0.465C0.507,-0.492 0.501,-0.516 0.488,-0.535C0.475,-0.554 0.459,-0.569 0.438,-0.579C0.417,-0.59 0.394,-0.595 0.369,-0.595L0.192,-0.595L0.192,-0.336Z" fill="rgb(46,46,46)" fill-rule="nonzero"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(1,0,0,1,0.278569,0.101881)">
|
<g transform="matrix(1,0,0,1,0.278569,0.101881)">
|
||||||
<circle cx="3866.43" cy="1926.14" r="8.923" style="fill:none;stroke:rgb(46,46,46);stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;"/>
|
<circle cx="3866.43" cy="1926.14" r="8.923" fill="none" stroke="rgb(46,46,46)" stroke-width="2px" stroke-linecap="butt" stroke-linejoin="miter"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
@ -1074,7 +1085,7 @@ footer {
|
|||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script {{ $nonceAttribute }}>
|
||||||
const filterEl = document.getElementById('filter');
|
const filterEl = document.getElementById('filter');
|
||||||
filterEl?.focus({ preventScroll: true });
|
filterEl?.focus({ preventScroll: true });
|
||||||
|
|
||||||
@ -1120,6 +1131,20 @@ footer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterElem = document.getElementById("filter");
|
||||||
|
if (filterElem) {
|
||||||
|
filterElem.addEventListener("keyup", filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("layout-list").addEventListener("click", function() {
|
||||||
|
queryParam('layout', '');
|
||||||
|
});
|
||||||
|
document.getElementById("layout-grid").addEventListener("click", function() {
|
||||||
|
queryParam('layout', 'grid');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("load", initPage);
|
||||||
|
|
||||||
function queryParam(k, v) {
|
function queryParam(k, v) {
|
||||||
const qs = new URLSearchParams(window.location.search);
|
const qs = new URLSearchParams(window.location.search);
|
||||||
if (!v) {
|
if (!v) {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -28,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@ -57,9 +59,9 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
|||||||
|
|
||||||
info, err := entry.Info()
|
info, err := entry.Info()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fsrv.logger.Error("could not get info about directory entry",
|
if c := fsrv.logger.Check(zapcore.ErrorLevel, "could not get info about directory entry"); c != nil {
|
||||||
zap.String("name", entry.Name()),
|
c.Write(zap.String("name", entry.Name()), zap.String("root", root))
|
||||||
zap.String("root", root))
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +82,13 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
|||||||
}
|
}
|
||||||
|
|
||||||
size := info.Size()
|
size := info.Size()
|
||||||
|
|
||||||
|
if !isDir {
|
||||||
|
// increase the total by the symlink's size, not the target's size,
|
||||||
|
// by incrementing before we follow the symlink
|
||||||
|
tplCtx.TotalFileSize += size
|
||||||
|
}
|
||||||
|
|
||||||
fileIsSymlink := isSymlink(info)
|
fileIsSymlink := isSymlink(info)
|
||||||
symlinkPath := ""
|
symlinkPath := ""
|
||||||
if fileIsSymlink {
|
if fileIsSymlink {
|
||||||
@ -103,7 +112,8 @@ func (fsrv *FileServer) directoryListing(ctx context.Context, fileSystem fs.FS,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isDir {
|
if !isDir {
|
||||||
tplCtx.TotalFileSize += size
|
// increase the total including the symlink target's size
|
||||||
|
tplCtx.TotalFileSizeFollowingSymlinks += size
|
||||||
}
|
}
|
||||||
|
|
||||||
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
||||||
@ -150,9 +160,15 @@ type browseTemplateContext struct {
|
|||||||
// The number of files (items that aren't directories) in the listing.
|
// The number of files (items that aren't directories) in the listing.
|
||||||
NumFiles int `json:"num_files"`
|
NumFiles int `json:"num_files"`
|
||||||
|
|
||||||
// The total size of all files in the listing.
|
// The total size of all files in the listing. Only includes the
|
||||||
|
// size of the files themselves, not the size of symlink targets
|
||||||
|
// (i.e. the calculation of this value does not follow symlinks).
|
||||||
TotalFileSize int64 `json:"total_file_size"`
|
TotalFileSize int64 `json:"total_file_size"`
|
||||||
|
|
||||||
|
// The total size of all files in the listing, including the
|
||||||
|
// size of the files targeted by symlinks.
|
||||||
|
TotalFileSizeFollowingSymlinks int64 `json:"total_file_size_following_symlinks"`
|
||||||
|
|
||||||
// Sort column used
|
// Sort column used
|
||||||
Sort string `json:"sort,omitempty"`
|
Sort string `json:"sort,omitempty"`
|
||||||
|
|
||||||
@ -266,12 +282,9 @@ type fileInfo struct {
|
|||||||
|
|
||||||
// HasExt returns true if the filename has any of the given suffixes, case-insensitive.
|
// HasExt returns true if the filename has any of the given suffixes, case-insensitive.
|
||||||
func (fi fileInfo) HasExt(exts ...string) bool {
|
func (fi fileInfo) HasExt(exts ...string) bool {
|
||||||
for _, ext := range exts {
|
return slices.ContainsFunc(exts, func(ext string) bool {
|
||||||
if strings.HasSuffix(strings.ToLower(fi.Name), strings.ToLower(ext)) {
|
return strings.HasSuffix(strings.ToLower(fi.Name), strings.ToLower(ext))
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HumanSize returns the size of the file as a
|
// HumanSize returns the size of the file as a
|
||||||
@ -288,6 +301,12 @@ func (btc browseTemplateContext) HumanTotalFileSize() string {
|
|||||||
return humanize.IBytes(uint64(btc.TotalFileSize))
|
return humanize.IBytes(uint64(btc.TotalFileSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HumanTotalFileSizeFollowingSymlinks is the same as HumanTotalFileSize
|
||||||
|
// except the returned value reflects the size of symlink targets.
|
||||||
|
func (btc browseTemplateContext) HumanTotalFileSizeFollowingSymlinks() string {
|
||||||
|
return humanize.IBytes(uint64(btc.TotalFileSizeFollowingSymlinks))
|
||||||
|
}
|
||||||
|
|
||||||
// HumanModTime returns the modified time of the file
|
// HumanModTime returns the modified time of the file
|
||||||
// as a human-readable string given by format.
|
// as a human-readable string given by format.
|
||||||
func (fi fileInfo) HumanModTime(format string) string {
|
func (fi fileInfo) HumanModTime(format string) string {
|
||||||
@ -353,4 +372,7 @@ const (
|
|||||||
sortByNameDirFirst = "namedirfirst"
|
sortByNameDirFirst = "namedirfirst"
|
||||||
sortBySize = "size"
|
sortBySize = "size"
|
||||||
sortByTime = "time"
|
sortByTime = "time"
|
||||||
|
|
||||||
|
sortOrderAsc = "asc"
|
||||||
|
sortOrderDesc = "desc"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -119,6 +119,16 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
return d.Err("Symlinks path reveal is already enabled")
|
return d.Err("Symlinks path reveal is already enabled")
|
||||||
}
|
}
|
||||||
fsrv.Browse.RevealSymlinks = true
|
fsrv.Browse.RevealSymlinks = true
|
||||||
|
case "sort":
|
||||||
|
for d.NextArg() {
|
||||||
|
dVal := d.Val()
|
||||||
|
switch dVal {
|
||||||
|
case sortByName, sortByNameDirFirst, sortBySize, sortByTime, sortOrderAsc, sortOrderDesc:
|
||||||
|
fsrv.Browse.SortOptions = append(fsrv.Browse.SortOptions, dVal)
|
||||||
|
default:
|
||||||
|
return d.Errf("unknown sort option '%s'", dVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return d.Errf("unknown subdirective '%s'", d.Val())
|
return d.Errf("unknown subdirective '%s'", d.Val())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/google/cel-go/common/types/ref"
|
"github.com/google/cel-go/common/types/ref"
|
||||||
"github.com/google/cel-go/parser"
|
"github.com/google/cel-go/parser"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
@ -224,7 +225,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander {
|
|||||||
return func(eh parser.ExprHelper, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) {
|
return func(eh parser.ExprHelper, target ast.Expr, args []ast.Expr) (ast.Expr, *common.Error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return eh.NewCall("file",
|
return eh.NewCall("file",
|
||||||
eh.NewIdent("request"),
|
eh.NewIdent(caddyhttp.CELRequestVarName),
|
||||||
eh.NewMap(),
|
eh.NewMap(),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
@ -232,7 +233,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander {
|
|||||||
arg := args[0]
|
arg := args[0]
|
||||||
if isCELStringLiteral(arg) || isCELCaddyPlaceholderCall(arg) {
|
if isCELStringLiteral(arg) || isCELCaddyPlaceholderCall(arg) {
|
||||||
return eh.NewCall("file",
|
return eh.NewCall("file",
|
||||||
eh.NewIdent("request"),
|
eh.NewIdent(caddyhttp.CELRequestVarName),
|
||||||
eh.NewMap(eh.NewMapEntry(
|
eh.NewMap(eh.NewMapEntry(
|
||||||
eh.NewLiteral(types.String("try_files")),
|
eh.NewLiteral(types.String("try_files")),
|
||||||
eh.NewList(arg),
|
eh.NewList(arg),
|
||||||
@ -241,7 +242,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander {
|
|||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
if isCELTryFilesLiteral(arg) {
|
if isCELTryFilesLiteral(arg) {
|
||||||
return eh.NewCall("file", eh.NewIdent("request"), arg), nil
|
return eh.NewCall("file", eh.NewIdent(caddyhttp.CELRequestVarName), arg), nil
|
||||||
}
|
}
|
||||||
return nil, &common.Error{
|
return nil, &common.Error{
|
||||||
Location: eh.OffsetLocation(arg.ID()),
|
Location: eh.OffsetLocation(arg.ID()),
|
||||||
@ -258,7 +259,7 @@ func celFileMatcherMacroExpander() parser.MacroExpander {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return eh.NewCall("file",
|
return eh.NewCall("file",
|
||||||
eh.NewIdent("request"),
|
eh.NewIdent(caddyhttp.CELRequestVarName),
|
||||||
eh.NewMap(eh.NewMapEntry(
|
eh.NewMap(eh.NewMapEntry(
|
||||||
eh.NewLiteral(types.String("try_files")),
|
eh.NewLiteral(types.String("try_files")),
|
||||||
eh.NewList(args...),
|
eh.NewList(args...),
|
||||||
@ -326,7 +327,9 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
|||||||
|
|
||||||
fileSystem, ok := m.fsmap.Get(fsName)
|
fileSystem, ok := m.fsmap.Get(fsName)
|
||||||
if !ok {
|
if !ok {
|
||||||
m.logger.Error("use of unregistered filesystem", zap.String("fs", fsName))
|
if c := m.logger.Check(zapcore.ErrorLevel, "use of unregistered filesystem"); c != nil {
|
||||||
|
c.Write(zap.String("fs", fsName))
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
type matchCandidate struct {
|
type matchCandidate struct {
|
||||||
@ -356,7 +359,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
|||||||
return val, nil
|
return val, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Error("evaluating placeholders", zap.Error(err))
|
if c := m.logger.Check(zapcore.ErrorLevel, "evaluating placeholders"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
expandedFile = file // "oh well," I guess?
|
expandedFile = file // "oh well," I guess?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,7 +385,9 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
|||||||
} else {
|
} else {
|
||||||
globResults, err = fs.Glob(fileSystem, fullPattern)
|
globResults, err = fs.Glob(fileSystem, fullPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Error("expanding glob", zap.Error(err))
|
if c := m.logger.Check(zapcore.ErrorLevel, "expanding glob"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,7 +634,7 @@ func isCELCaddyPlaceholderCall(e ast.Expr) bool {
|
|||||||
switch e.Kind() {
|
switch e.Kind() {
|
||||||
case ast.CallKind:
|
case ast.CallKind:
|
||||||
call := e.AsCall()
|
call := e.AsCall()
|
||||||
if call.FunctionName() == "caddyPlaceholder" {
|
if call.FunctionName() == caddyhttp.CELPlaceholderFuncName {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
|
case ast.UnspecifiedExprKind, ast.ComprehensionKind, ast.IdentKind, ast.ListKind, ast.LiteralKind, ast.MapKind, ast.SelectKind, ast.StructKind:
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@ -236,6 +237,24 @@ func (fsrv *FileServer) Provision(ctx caddy.Context) error {
|
|||||||
fsrv.precompressors[ae] = p
|
fsrv.precompressors[ae] = p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fsrv.Browse != nil {
|
||||||
|
// check sort options
|
||||||
|
for idx, sortOption := range fsrv.Browse.SortOptions {
|
||||||
|
switch idx {
|
||||||
|
case 0:
|
||||||
|
if sortOption != sortByName && sortOption != sortByNameDirFirst && sortOption != sortBySize && sortOption != sortByTime {
|
||||||
|
return fmt.Errorf("the first option must be one of the following: %s, %s, %s, %s, but got %s", sortByName, sortByNameDirFirst, sortBySize, sortByTime, sortOption)
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
if sortOption != sortOrderAsc && sortOption != sortOrderDesc {
|
||||||
|
return fmt.Errorf("the second option must be one of the following: %s, %s, but got %s", sortOrderAsc, sortOrderDesc, sortOption)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("only max 2 sort options are allowed, but got %d", idx+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,11 +287,14 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
// remove any trailing `/` as it breaks fs.ValidPath() in the stdlib
|
// remove any trailing `/` as it breaks fs.ValidPath() in the stdlib
|
||||||
filename := strings.TrimSuffix(caddyhttp.SanitizedPathJoin(root, r.URL.Path), "/")
|
filename := strings.TrimSuffix(caddyhttp.SanitizedPathJoin(root, r.URL.Path), "/")
|
||||||
|
|
||||||
fsrv.logger.Debug("sanitized path join",
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "sanitized path join"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("site_root", root),
|
zap.String("site_root", root),
|
||||||
zap.String("fs", fsName),
|
zap.String("fs", fsName),
|
||||||
zap.String("request_path", r.URL.Path),
|
zap.String("request_path", r.URL.Path),
|
||||||
zap.String("result", filename))
|
zap.String("result", filename),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// get information about the file
|
// get information about the file
|
||||||
info, err := fs.Stat(fileSystem, filename)
|
info, err := fs.Stat(fileSystem, filename)
|
||||||
@ -295,9 +317,12 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
indexPath := caddyhttp.SanitizedPathJoin(filename, indexPage)
|
indexPath := caddyhttp.SanitizedPathJoin(filename, indexPage)
|
||||||
if fileHidden(indexPath, filesToHide) {
|
if fileHidden(indexPath, filesToHide) {
|
||||||
// pretend this file doesn't exist
|
// pretend this file doesn't exist
|
||||||
fsrv.logger.Debug("hiding index file",
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "hiding index file"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("filename", indexPath),
|
zap.String("filename", indexPath),
|
||||||
zap.Strings("files_to_hide", filesToHide))
|
zap.Strings("files_to_hide", filesToHide),
|
||||||
|
)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,7 +342,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
info = indexInfo
|
info = indexInfo
|
||||||
filename = indexPath
|
filename = indexPath
|
||||||
implicitIndexFile = true
|
implicitIndexFile = true
|
||||||
fsrv.logger.Debug("located index file", zap.String("filename", filename))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "located index file"); c != nil {
|
||||||
|
c.Write(zap.String("filename", filename))
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,9 +352,12 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
// if still referencing a directory, delegate
|
// if still referencing a directory, delegate
|
||||||
// to browse or return an error
|
// to browse or return an error
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
fsrv.logger.Debug("no index file in directory",
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "no index file in directory"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("path", filename),
|
zap.String("path", filename),
|
||||||
zap.Strings("index_filenames", fsrv.IndexNames))
|
zap.Strings("index_filenames", fsrv.IndexNames),
|
||||||
|
)
|
||||||
|
}
|
||||||
if fsrv.Browse != nil && !fileHidden(filename, filesToHide) {
|
if fsrv.Browse != nil && !fileHidden(filename, filesToHide) {
|
||||||
return fsrv.serveBrowse(fileSystem, root, filename, w, r, next)
|
return fsrv.serveBrowse(fileSystem, root, filename, w, r, next)
|
||||||
}
|
}
|
||||||
@ -337,9 +367,12 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
// one last check to ensure the file isn't hidden (we might
|
// one last check to ensure the file isn't hidden (we might
|
||||||
// have changed the filename from when we last checked)
|
// have changed the filename from when we last checked)
|
||||||
if fileHidden(filename, filesToHide) {
|
if fileHidden(filename, filesToHide) {
|
||||||
fsrv.logger.Debug("hiding file",
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "hiding file"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("filename", filename),
|
zap.String("filename", filename),
|
||||||
zap.Strings("files_to_hide", filesToHide))
|
zap.Strings("files_to_hide", filesToHide),
|
||||||
|
)
|
||||||
|
}
|
||||||
return fsrv.notFound(w, r, next)
|
return fsrv.notFound(w, r, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,15 +390,21 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
if path.Base(origReq.URL.Path) == path.Base(r.URL.Path) {
|
if path.Base(origReq.URL.Path) == path.Base(r.URL.Path) {
|
||||||
if implicitIndexFile && !strings.HasSuffix(origReq.URL.Path, "/") {
|
if implicitIndexFile && !strings.HasSuffix(origReq.URL.Path, "/") {
|
||||||
to := origReq.URL.Path + "/"
|
to := origReq.URL.Path + "/"
|
||||||
fsrv.logger.Debug("redirecting to canonical URI (adding trailing slash for directory)",
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to canonical URI (adding trailing slash for directory"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("from_path", origReq.URL.Path),
|
zap.String("from_path", origReq.URL.Path),
|
||||||
zap.String("to_path", to))
|
zap.String("to_path", to),
|
||||||
|
)
|
||||||
|
}
|
||||||
return redirect(w, r, to)
|
return redirect(w, r, to)
|
||||||
} else if !implicitIndexFile && strings.HasSuffix(origReq.URL.Path, "/") {
|
} else if !implicitIndexFile && strings.HasSuffix(origReq.URL.Path, "/") {
|
||||||
to := origReq.URL.Path[:len(origReq.URL.Path)-1]
|
to := origReq.URL.Path[:len(origReq.URL.Path)-1]
|
||||||
fsrv.logger.Debug("redirecting to canonical URI (removing trailing slash for file)",
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "redirecting to canonical URI (removing trailing slash for file"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("from_path", origReq.URL.Path),
|
zap.String("from_path", origReq.URL.Path),
|
||||||
zap.String("to_path", to))
|
zap.String("to_path", to),
|
||||||
|
)
|
||||||
|
}
|
||||||
return redirect(w, r, to)
|
return redirect(w, r, to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -393,13 +432,19 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
compressedFilename := filename + precompress.Suffix()
|
compressedFilename := filename + precompress.Suffix()
|
||||||
compressedInfo, err := fs.Stat(fileSystem, compressedFilename)
|
compressedInfo, err := fs.Stat(fileSystem, compressedFilename)
|
||||||
if err != nil || compressedInfo.IsDir() {
|
if err != nil || compressedInfo.IsDir() {
|
||||||
fsrv.logger.Debug("precompressed file not accessible", zap.String("filename", compressedFilename), zap.Error(err))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "precompressed file not accessible"); c != nil {
|
||||||
|
c.Write(zap.String("filename", compressedFilename), zap.Error(err))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fsrv.logger.Debug("opening compressed sidecar file", zap.String("filename", compressedFilename), zap.Error(err))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "opening compressed sidecar file"); c != nil {
|
||||||
|
c.Write(zap.String("filename", compressedFilename), zap.Error(err))
|
||||||
|
}
|
||||||
file, err = fsrv.openFile(fileSystem, compressedFilename, w)
|
file, err = fsrv.openFile(fileSystem, compressedFilename, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fsrv.logger.Warn("opening precompressed file failed", zap.String("filename", compressedFilename), zap.Error(err))
|
if c := fsrv.logger.Check(zapcore.WarnLevel, "opening precompressed file failed"); c != nil {
|
||||||
|
c.Write(zap.String("filename", compressedFilename), zap.Error(err))
|
||||||
|
}
|
||||||
if caddyErr, ok := err.(caddyhttp.HandlerError); ok && caddyErr.StatusCode == http.StatusServiceUnavailable {
|
if caddyErr, ok := err.(caddyhttp.HandlerError); ok && caddyErr.StatusCode == http.StatusServiceUnavailable {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -430,7 +475,9 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
|||||||
|
|
||||||
// no precompressed file found, use the actual file
|
// no precompressed file found, use the actual file
|
||||||
if file == nil {
|
if file == nil {
|
||||||
fsrv.logger.Debug("opening file", zap.String("filename", filename))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "opening file"); c != nil {
|
||||||
|
c.Write(zap.String("filename", filename))
|
||||||
|
}
|
||||||
|
|
||||||
// open the file
|
// open the file
|
||||||
file, err = fsrv.openFile(fileSystem, filename, w)
|
file, err = fsrv.openFile(fileSystem, filename, w)
|
||||||
@ -530,10 +577,14 @@ func (fsrv *FileServer) openFile(fileSystem fs.FS, filename string, w http.Respo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
err = fsrv.mapDirOpenError(fileSystem, err, filename)
|
err = fsrv.mapDirOpenError(fileSystem, err, filename)
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
fsrv.logger.Debug("file not found", zap.String("filename", filename), zap.Error(err))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "file not found"); c != nil {
|
||||||
|
c.Write(zap.String("filename", filename), zap.Error(err))
|
||||||
|
}
|
||||||
return nil, caddyhttp.Error(http.StatusNotFound, err)
|
return nil, caddyhttp.Error(http.StatusNotFound, err)
|
||||||
} else if errors.Is(err, fs.ErrPermission) {
|
} else if errors.Is(err, fs.ErrPermission) {
|
||||||
fsrv.logger.Debug("permission denied", zap.String("filename", filename), zap.Error(err))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "permission denied"); c != nil {
|
||||||
|
c.Write(zap.String("filename", filename), zap.Error(err))
|
||||||
|
}
|
||||||
return nil, caddyhttp.Error(http.StatusForbidden, err)
|
return nil, caddyhttp.Error(http.StatusForbidden, err)
|
||||||
}
|
}
|
||||||
// maybe the server is under load and ran out of file descriptors?
|
// maybe the server is under load and ran out of file descriptors?
|
||||||
@ -541,7 +592,9 @@ func (fsrv *FileServer) openFile(fileSystem fs.FS, filename string, w http.Respo
|
|||||||
//nolint:gosec
|
//nolint:gosec
|
||||||
backoff := weakrand.Intn(maxBackoff-minBackoff) + minBackoff
|
backoff := weakrand.Intn(maxBackoff-minBackoff) + minBackoff
|
||||||
w.Header().Set("Retry-After", strconv.Itoa(backoff))
|
w.Header().Set("Retry-After", strconv.Itoa(backoff))
|
||||||
fsrv.logger.Debug("retry after backoff", zap.String("filename", filename), zap.Int("backoff", backoff), zap.Error(err))
|
if c := fsrv.logger.Check(zapcore.DebugLevel, "retry after backoff"); c != nil {
|
||||||
|
c.Write(zap.String("filename", filename), zap.Int("backoff", backoff), zap.Error(err))
|
||||||
|
}
|
||||||
return nil, caddyhttp.Error(http.StatusServiceUnavailable, err)
|
return nil, caddyhttp.Error(http.StatusServiceUnavailable, err)
|
||||||
}
|
}
|
||||||
return file, nil
|
return file, nil
|
||||||
|
|||||||
@ -135,7 +135,9 @@ type HeaderOps struct {
|
|||||||
func (ops *HeaderOps) Provision(_ caddy.Context) error {
|
func (ops *HeaderOps) Provision(_ caddy.Context) error {
|
||||||
for fieldName, replacements := range ops.Replace {
|
for fieldName, replacements := range ops.Replace {
|
||||||
for i, r := range replacements {
|
for i, r := range replacements {
|
||||||
if r.SearchRegexp != "" {
|
if r.SearchRegexp == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
re, err := regexp.Compile(r.SearchRegexp)
|
re, err := regexp.Compile(r.SearchRegexp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err)
|
return fmt.Errorf("replacement %d for header field '%s': %v", i, fieldName, err)
|
||||||
@ -143,7 +145,6 @@ func (ops *HeaderOps) Provision(_ caddy.Context) error {
|
|||||||
replacements[i].re = re
|
replacements[i].re = re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
@ -50,7 +51,6 @@ type Intercept struct {
|
|||||||
//
|
//
|
||||||
// Three new placeholders are available in this handler chain:
|
// Three new placeholders are available in this handler chain:
|
||||||
// - `{http.intercept.status_code}` The status code from the response
|
// - `{http.intercept.status_code}` The status code from the response
|
||||||
// - `{http.intercept.status_text}` The status text from the response
|
|
||||||
// - `{http.intercept.header.*}` The headers from the response
|
// - `{http.intercept.header.*}` The headers from the response
|
||||||
HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"`
|
HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"`
|
||||||
|
|
||||||
@ -161,12 +161,14 @@ func (ir Intercept) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
|
|||||||
|
|
||||||
// set up the replacer so that parts of the original response can be
|
// set up the replacer so that parts of the original response can be
|
||||||
// used for routing decisions
|
// used for routing decisions
|
||||||
for field, value := range r.Header {
|
for field, value := range rec.Header() {
|
||||||
repl.Set("http.intercept.header."+field, strings.Join(value, ","))
|
repl.Set("http.intercept.header."+field, strings.Join(value, ","))
|
||||||
}
|
}
|
||||||
repl.Set("http.intercept.status_code", rec.Status())
|
repl.Set("http.intercept.status_code", rec.Status())
|
||||||
|
|
||||||
ir.logger.Debug("handling response", zap.Int("handler", rec.handlerIndex))
|
if c := ir.logger.Check(zapcore.DebugLevel, "handling response"); c != nil {
|
||||||
|
c.Write(zap.Int("handler", rec.handlerIndex))
|
||||||
|
}
|
||||||
|
|
||||||
// pass the request through the response handler routes
|
// pass the request through the response handler routes
|
||||||
return rec.handler.Routes.Compile(next).ServeHTTP(w, r)
|
return rec.handler.Routes.Compile(next).ServeHTTP(w, r)
|
||||||
|
|||||||
@ -26,9 +26,11 @@ import (
|
|||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/common/types/ref"
|
"github.com/google/cel-go/common/types/ref"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MatchRemoteIP matches requests by the remote IP address,
|
// MatchRemoteIP matches requests by the remote IP address,
|
||||||
@ -79,7 +81,7 @@ func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
return d.Err("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
|
return d.Err("the 'forwarded' option is no longer supported; use the 'client_ip' matcher instead")
|
||||||
}
|
}
|
||||||
if d.Val() == "private_ranges" {
|
if d.Val() == "private_ranges" {
|
||||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.Ranges = append(m.Ranges, d.Val())
|
m.Ranges = append(m.Ranges, d.Val())
|
||||||
@ -143,15 +145,23 @@ func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchRemoteIP) Match(r *http.Request) bool {
|
func (m MatchRemoteIP) Match(r *http.Request) bool {
|
||||||
|
if r.TLS != nil && !r.TLS.HandshakeComplete {
|
||||||
|
return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed
|
||||||
|
}
|
||||||
address := r.RemoteAddr
|
address := r.RemoteAddr
|
||||||
clientIP, zoneID, err := parseIPZoneFromString(address)
|
clientIP, zoneID, err := parseIPZoneFromString(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Error("getting remote IP", zap.Error(err))
|
if c := m.logger.Check(zapcore.ErrorLevel, "getting remote "); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
|
matches, zoneFilter := matchIPByCidrZones(clientIP, zoneID, m.cidrs, m.zones)
|
||||||
if !matches && !zoneFilter {
|
if !matches && !zoneFilter {
|
||||||
m.logger.Debug("zone ID from remote IP did not match", zap.String("zone", zoneID))
|
if c := m.logger.Check(zapcore.DebugLevel, "zone ID from remote IP did not match"); c != nil {
|
||||||
|
c.Write(zap.String("zone", zoneID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
@ -170,7 +180,7 @@ func (m *MatchClientIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
for d.Next() {
|
for d.Next() {
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
if d.Val() == "private_ranges" {
|
if d.Val() == "private_ranges" {
|
||||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.Ranges = append(m.Ranges, d.Val())
|
m.Ranges = append(m.Ranges, d.Val())
|
||||||
@ -228,6 +238,9 @@ func (m *MatchClientIP) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchClientIP) Match(r *http.Request) bool {
|
func (m MatchClientIP) Match(r *http.Request) bool {
|
||||||
|
if r.TLS != nil && !r.TLS.HandshakeComplete {
|
||||||
|
return false // if handshake is not finished, we infer 0-RTT that has not verified remote IP; could be spoofed
|
||||||
|
}
|
||||||
address := GetVar(r.Context(), ClientIPVarKey).(string)
|
address := GetVar(r.Context(), ClientIPVarKey).(string)
|
||||||
clientIP, zoneID, err := parseIPZoneFromString(address)
|
clientIP, zoneID, err := parseIPZoneFromString(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -244,7 +257,9 @@ func (m MatchClientIP) Match(r *http.Request) bool {
|
|||||||
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
|
func provisionCidrsZonesFromRanges(ranges []string) ([]*netip.Prefix, []string, error) {
|
||||||
cidrs := []*netip.Prefix{}
|
cidrs := []*netip.Prefix{}
|
||||||
zones := []string{}
|
zones := []string{}
|
||||||
|
repl := caddy.NewReplacer()
|
||||||
for _, str := range ranges {
|
for _, str := range ranges {
|
||||||
|
str = repl.ReplaceAll(str, "")
|
||||||
// Exclude the zone_id from the IP
|
// Exclude the zone_id from the IP
|
||||||
if strings.Contains(str, "%") {
|
if strings.Contains(str, "%") {
|
||||||
split := strings.Split(str, "%")
|
split := strings.Split(str, "%")
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -92,7 +93,7 @@ func (m *StaticIPRange) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
}
|
}
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
if d.Val() == "private_ranges" {
|
if d.Val() == "private_ranges" {
|
||||||
m.Ranges = append(m.Ranges, PrivateRangesCIDR()...)
|
m.Ranges = append(m.Ranges, internal.PrivateRangesCIDR()...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.Ranges = append(m.Ranges, d.Val())
|
m.Ranges = append(m.Ranges, d.Val())
|
||||||
@ -121,22 +122,16 @@ func CIDRExpressionToPrefix(expr string) (netip.Prefix, error) {
|
|||||||
return prefix, nil
|
return prefix, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivateRangesCIDR returns a list of private CIDR range
|
|
||||||
// strings, which can be used as a configuration shortcut.
|
|
||||||
func PrivateRangesCIDR() []string {
|
|
||||||
return []string{
|
|
||||||
"192.168.0.0/16",
|
|
||||||
"172.16.0.0/12",
|
|
||||||
"10.0.0.0/8",
|
|
||||||
"127.0.0.1/8",
|
|
||||||
"fd00::/8",
|
|
||||||
"::1",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ caddy.Provisioner = (*StaticIPRange)(nil)
|
_ caddy.Provisioner = (*StaticIPRange)(nil)
|
||||||
_ caddyfile.Unmarshaler = (*StaticIPRange)(nil)
|
_ caddyfile.Unmarshaler = (*StaticIPRange)(nil)
|
||||||
_ IPRangeSource = (*StaticIPRange)(nil)
|
_ IPRangeSource = (*StaticIPRange)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PrivateRangesCIDR returns a list of private CIDR range
|
||||||
|
// strings, which can be used as a configuration shortcut.
|
||||||
|
// Note: this function is used at least by mholt/caddy-l4.
|
||||||
|
func PrivateRangesCIDR() []string {
|
||||||
|
return internal.PrivateRangesCIDR()
|
||||||
|
}
|
||||||
|
|||||||
@ -193,7 +193,7 @@ func (sa *StringArray) UnmarshalJSON(b []byte) error {
|
|||||||
// to use, the error log message, and any extra fields.
|
// to use, the error log message, and any extra fields.
|
||||||
// If err is a HandlerError, the returned values will
|
// If err is a HandlerError, the returned values will
|
||||||
// have richer information.
|
// have richer information.
|
||||||
func errLogValues(err error) (status int, msg string, fields []zapcore.Field) {
|
func errLogValues(err error) (status int, msg string, fields func() []zapcore.Field) {
|
||||||
var handlerErr HandlerError
|
var handlerErr HandlerError
|
||||||
if errors.As(err, &handlerErr) {
|
if errors.As(err, &handlerErr) {
|
||||||
status = handlerErr.StatusCode
|
status = handlerErr.StatusCode
|
||||||
@ -202,11 +202,13 @@ func errLogValues(err error) (status int, msg string, fields []zapcore.Field) {
|
|||||||
} else {
|
} else {
|
||||||
msg = handlerErr.Err.Error()
|
msg = handlerErr.Err.Error()
|
||||||
}
|
}
|
||||||
fields = []zapcore.Field{
|
fields = func() []zapcore.Field {
|
||||||
|
return []zapcore.Field{
|
||||||
zap.Int("status", handlerErr.StatusCode),
|
zap.Int("status", handlerErr.StatusCode),
|
||||||
zap.String("err_id", handlerErr.ID),
|
zap.String("err_id", handlerErr.ID),
|
||||||
zap.String("err_trace", handlerErr.Trace),
|
zap.String("err_trace", handlerErr.Trace),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
status = http.StatusInternalServerError
|
status = http.StatusInternalServerError
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
@ -126,7 +127,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
|
|||||||
// defer work until a variable is actually evaluated by using replacer's Map callback
|
// defer work until a variable is actually evaluated by using replacer's Map callback
|
||||||
repl.Map(func(key string) (any, bool) {
|
repl.Map(func(key string) (any, bool) {
|
||||||
// return early if the variable is not even a configured destination
|
// return early if the variable is not even a configured destination
|
||||||
destIdx := h.destinationIndex(key)
|
destIdx := slices.Index(h.Destinations, key)
|
||||||
if destIdx < 0 {
|
if destIdx < 0 {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@ -170,17 +171,6 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
|
|||||||
return next.ServeHTTP(w, r)
|
return next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// destinationIndex returns the positional index of the destination
|
|
||||||
// is name is a known destination; otherwise it returns -1.
|
|
||||||
func (h Handler) destinationIndex(name string) int {
|
|
||||||
for i, dest := range h.Destinations {
|
|
||||||
if dest == name {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mapping describes a mapping from input to outputs.
|
// Mapping describes a mapping from input to outputs.
|
||||||
type Mapping struct {
|
type Mapping struct {
|
||||||
// The input value to match. Must be distinct from other mappings.
|
// The input value to match. Must be distinct from other mappings.
|
||||||
|
|||||||
@ -178,6 +178,22 @@ type (
|
|||||||
// "http/2", "http/3", or minimum versions: "http/2+", etc.
|
// "http/2", "http/3", or minimum versions: "http/2+", etc.
|
||||||
MatchProtocol string
|
MatchProtocol string
|
||||||
|
|
||||||
|
// MatchTLS matches HTTP requests based on the underlying
|
||||||
|
// TLS connection state. If this matcher is specified but
|
||||||
|
// the request did not come over TLS, it will never match.
|
||||||
|
// If this matcher is specified but is empty and the request
|
||||||
|
// did come in over TLS, it will always match.
|
||||||
|
MatchTLS struct {
|
||||||
|
// Matches if the TLS handshake has completed. QUIC 0-RTT early
|
||||||
|
// data may arrive before the handshake completes. Generally, it
|
||||||
|
// is unsafe to replay these requests if they are not idempotent;
|
||||||
|
// additionally, the remote IP of early data packets can more
|
||||||
|
// easily be spoofed. It is conventional to respond with HTTP 425
|
||||||
|
// Too Early if the request cannot risk being processed in this
|
||||||
|
// state.
|
||||||
|
HandshakeComplete *bool `json:"handshake_complete,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// MatchNot matches requests by negating the results of its matcher
|
// MatchNot matches requests by negating the results of its matcher
|
||||||
// sets. A single "not" matcher takes one or more matcher sets. Each
|
// sets. A single "not" matcher takes one or more matcher sets. Each
|
||||||
// matcher set is OR'ed; in other words, if any matcher set returns
|
// matcher set is OR'ed; in other words, if any matcher set returns
|
||||||
@ -213,6 +229,7 @@ func init() {
|
|||||||
caddy.RegisterModule(MatchHeader{})
|
caddy.RegisterModule(MatchHeader{})
|
||||||
caddy.RegisterModule(MatchHeaderRE{})
|
caddy.RegisterModule(MatchHeaderRE{})
|
||||||
caddy.RegisterModule(new(MatchProtocol))
|
caddy.RegisterModule(new(MatchProtocol))
|
||||||
|
caddy.RegisterModule(MatchTLS{})
|
||||||
caddy.RegisterModule(MatchNot{})
|
caddy.RegisterModule(MatchNot{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,12 +764,7 @@ func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
|
|
||||||
// Match returns true if r matches m.
|
// Match returns true if r matches m.
|
||||||
func (m MatchMethod) Match(r *http.Request) bool {
|
func (m MatchMethod) Match(r *http.Request) bool {
|
||||||
for _, method := range m {
|
return slices.Contains(m, r.Method)
|
||||||
if r.Method == method {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CELLibrary produces options that expose this matcher for use in CEL
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
@ -1236,6 +1248,53 @@ func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaddyModule returns the Caddy module information.
|
||||||
|
func (MatchTLS) CaddyModule() caddy.ModuleInfo {
|
||||||
|
return caddy.ModuleInfo{
|
||||||
|
ID: "http.matchers.tls",
|
||||||
|
New: func() caddy.Module { return new(MatchTLS) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if r matches m.
|
||||||
|
func (m MatchTLS) Match(r *http.Request) bool {
|
||||||
|
if r.TLS == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m.HandshakeComplete != nil {
|
||||||
|
if (!*m.HandshakeComplete && r.TLS.HandshakeComplete) ||
|
||||||
|
(*m.HandshakeComplete && !r.TLS.HandshakeComplete) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile parses Caddyfile tokens for this matcher. Syntax:
|
||||||
|
//
|
||||||
|
// ... tls [early_data]
|
||||||
|
//
|
||||||
|
// EXPERIMENTAL SYNTAX: Subject to change.
|
||||||
|
func (m *MatchTLS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
// iterate to merge multiple matchers into one
|
||||||
|
for d.Next() {
|
||||||
|
if d.NextArg() {
|
||||||
|
switch d.Val() {
|
||||||
|
case "early_data":
|
||||||
|
var false bool
|
||||||
|
m.HandshakeComplete = &false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
if d.NextBlock(0) {
|
||||||
|
return d.Err("malformed tls matcher: blocks are not supported yet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CaddyModule returns the Caddy module information.
|
// CaddyModule returns the Caddy module information.
|
||||||
func (MatchNot) CaddyModule() caddy.ModuleInfo {
|
func (MatchNot) CaddyModule() caddy.ModuleInfo {
|
||||||
return caddy.ModuleInfo{
|
return caddy.ModuleInfo{
|
||||||
@ -1503,8 +1562,8 @@ var (
|
|||||||
_ CELLibraryProducer = (*MatchHeader)(nil)
|
_ CELLibraryProducer = (*MatchHeader)(nil)
|
||||||
_ CELLibraryProducer = (*MatchHeaderRE)(nil)
|
_ CELLibraryProducer = (*MatchHeaderRE)(nil)
|
||||||
_ CELLibraryProducer = (*MatchProtocol)(nil)
|
_ CELLibraryProducer = (*MatchProtocol)(nil)
|
||||||
// _ CELLibraryProducer = (*VarsMatcher)(nil)
|
_ CELLibraryProducer = (*VarsMatcher)(nil)
|
||||||
// _ CELLibraryProducer = (*MatchVarsRE)(nil)
|
_ CELLibraryProducer = (*MatchVarsRE)(nil)
|
||||||
|
|
||||||
_ json.Marshaler = (*MatchNot)(nil)
|
_ json.Marshaler = (*MatchNot)(nil)
|
||||||
_ json.Unmarshaler = (*MatchNot)(nil)
|
_ json.Unmarshaler = (*MatchNot)(nil)
|
||||||
|
|||||||
@ -40,7 +40,7 @@ type ListenerWrapper struct {
|
|||||||
Allow []string `json:"allow,omitempty"`
|
Allow []string `json:"allow,omitempty"`
|
||||||
allow []netip.Prefix
|
allow []netip.Prefix
|
||||||
|
|
||||||
// Denby is an optional list of CIDR ranges to
|
// Deny is an optional list of CIDR ranges to
|
||||||
// deny PROXY headers from.
|
// deny PROXY headers from.
|
||||||
Deny []string `json:"deny,omitempty"`
|
Deny []string `json:"deny,omitempty"`
|
||||||
deny []netip.Prefix
|
deny []netip.Prefix
|
||||||
@ -50,7 +50,7 @@ type ListenerWrapper struct {
|
|||||||
// Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
|
// Policy definitions are here: https://pkg.go.dev/github.com/pires/go-proxyproto@v0.7.0#Policy
|
||||||
FallbackPolicy Policy `json:"fallback_policy,omitempty"`
|
FallbackPolicy Policy `json:"fallback_policy,omitempty"`
|
||||||
|
|
||||||
policy goproxy.PolicyFunc
|
policy goproxy.ConnPolicyFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision sets up the listener wrapper.
|
// Provision sets up the listener wrapper.
|
||||||
@ -69,13 +69,14 @@ func (pp *ListenerWrapper) Provision(ctx caddy.Context) error {
|
|||||||
}
|
}
|
||||||
pp.deny = append(pp.deny, ipnet)
|
pp.deny = append(pp.deny, ipnet)
|
||||||
}
|
}
|
||||||
pp.policy = func(upstream net.Addr) (goproxy.Policy, error) {
|
|
||||||
|
pp.policy = func(options goproxy.ConnPolicyOptions) (goproxy.Policy, error) {
|
||||||
// trust unix sockets
|
// trust unix sockets
|
||||||
if network := upstream.Network(); caddy.IsUnixNetwork(network) {
|
if network := options.Upstream.Network(); caddy.IsUnixNetwork(network) || caddy.IsFdNetwork(network) {
|
||||||
return goproxy.USE, nil
|
return goproxy.USE, nil
|
||||||
}
|
}
|
||||||
ret := pp.FallbackPolicy
|
ret := pp.FallbackPolicy
|
||||||
host, _, err := net.SplitHostPort(upstream.String())
|
host, _, err := net.SplitHostPort(options.Upstream.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return goproxy.REJECT, err
|
return goproxy.REJECT, err
|
||||||
}
|
}
|
||||||
@ -106,6 +107,6 @@ func (pp *ListenerWrapper) WrapListener(l net.Listener) net.Listener {
|
|||||||
Listener: l,
|
Listener: l,
|
||||||
ReadHeaderTimeout: time.Duration(pp.Timeout),
|
ReadHeaderTimeout: time.Duration(pp.Timeout),
|
||||||
}
|
}
|
||||||
pl.Policy = pp.policy
|
pl.ConnPolicy = pp.policy
|
||||||
return pl
|
return pl
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@ -92,14 +93,17 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
|
|||||||
|
|
||||||
// push first!
|
// push first!
|
||||||
for _, resource := range h.Resources {
|
for _, resource := range h.Resources {
|
||||||
h.logger.Debug("pushing resource",
|
if c := h.logger.Check(zapcore.DebugLevel, "pushing resource"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("uri", r.RequestURI),
|
zap.String("uri", r.RequestURI),
|
||||||
zap.String("push_method", resource.Method),
|
zap.String("push_method", resource.Method),
|
||||||
zap.String("push_target", resource.Target),
|
zap.String("push_target", resource.Target),
|
||||||
zap.Object("push_headers", caddyhttp.LoggableHTTPHeader{
|
zap.Object("push_headers", caddyhttp.LoggableHTTPHeader{
|
||||||
Header: hdr,
|
Header: hdr,
|
||||||
ShouldLogCredentials: shouldLogCredentials,
|
ShouldLogCredentials: shouldLogCredentials,
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{
|
err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{
|
||||||
Method: resource.Method,
|
Method: resource.Method,
|
||||||
Header: hdr,
|
Header: hdr,
|
||||||
@ -209,7 +213,9 @@ func (lp linkPusher) WriteHeader(statusCode int) {
|
|||||||
if links, ok := lp.ResponseWriter.Header()["Link"]; ok {
|
if links, ok := lp.ResponseWriter.Header()["Link"]; ok {
|
||||||
// only initiate these pushes if it hasn't been done yet
|
// only initiate these pushes if it hasn't been done yet
|
||||||
if val := caddyhttp.GetVar(lp.request.Context(), pushedLink); val == nil {
|
if val := caddyhttp.GetVar(lp.request.Context(), pushedLink); val == nil {
|
||||||
lp.handler.logger.Debug("pushing Link resources", zap.Strings("linked", links))
|
if c := lp.handler.logger.Check(zapcore.DebugLevel, "pushing Link resources"); c != nil {
|
||||||
|
c.Write(zap.Strings("linked", links))
|
||||||
|
}
|
||||||
caddyhttp.SetVar(lp.request.Context(), pushedLink, true)
|
caddyhttp.SetVar(lp.request.Context(), pushedLink, true)
|
||||||
lp.handler.servePreloadLinks(lp.pusher, lp.header, links)
|
lp.handler.servePreloadLinks(lp.pusher, lp.header, links)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -142,8 +142,16 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
|
|||||||
}
|
}
|
||||||
return port, true
|
return port, true
|
||||||
case "http.request.remote":
|
case "http.request.remote":
|
||||||
|
if req.TLS != nil && !req.TLS.HandshakeComplete {
|
||||||
|
// without a complete handshake (QUIC "early data") we can't trust the remote IP address to not be spoofed
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
return req.RemoteAddr, true
|
return req.RemoteAddr, true
|
||||||
case "http.request.remote.host":
|
case "http.request.remote.host":
|
||||||
|
if req.TLS != nil && !req.TLS.HandshakeComplete {
|
||||||
|
// without a complete handshake (QUIC "early data") we can't trust the remote IP address to not be spoofed
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// req.RemoteAddr is host:port for tcp and udp sockets and /unix/socket.path
|
// req.RemoteAddr is host:port for tcp and udp sockets and /unix/socket.path
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@ -69,12 +70,16 @@ func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next cad
|
|||||||
rc := http.NewResponseController(w)
|
rc := http.NewResponseController(w)
|
||||||
if rb.ReadTimeout > 0 {
|
if rb.ReadTimeout > 0 {
|
||||||
if err := rc.SetReadDeadline(time.Now().Add(rb.ReadTimeout)); err != nil {
|
if err := rc.SetReadDeadline(time.Now().Add(rb.ReadTimeout)); err != nil {
|
||||||
rb.logger.Error("could not set read deadline", zap.Error(err))
|
if c := rb.logger.Check(zapcore.ErrorLevel, "could not set read deadline"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rb.WriteTimeout > 0 {
|
if rb.WriteTimeout > 0 {
|
||||||
if err := rc.SetWriteDeadline(time.Now().Add(rb.WriteTimeout)); err != nil {
|
if err := rc.SetWriteDeadline(time.Now().Add(rb.WriteTimeout)); err != nil {
|
||||||
rb.logger.Error("could not set write deadline", zap.Error(err))
|
if c := rb.logger.Check(zapcore.ErrorLevel, "could not set write deadline"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,9 +42,13 @@ func (rww *ResponseWriterWrapper) Push(target string, opts *http.PushOptions) er
|
|||||||
return ErrNotImplemented
|
return ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFrom implements io.ReaderFrom. It simply calls io.Copy,
|
// ReadFrom implements io.ReaderFrom. It retries to use io.ReaderFrom if available,
|
||||||
// which uses io.ReaderFrom if available.
|
// then fallback to io.Copy.
|
||||||
|
// see: https://github.com/caddyserver/caddy/issues/6546
|
||||||
func (rww *ResponseWriterWrapper) ReadFrom(r io.Reader) (n int64, err error) {
|
func (rww *ResponseWriterWrapper) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
if rf, ok := rww.ResponseWriter.(io.ReaderFrom); ok {
|
||||||
|
return rf.ReadFrom(r)
|
||||||
|
}
|
||||||
return io.Copy(rww.ResponseWriter, r)
|
return io.Copy(rww.ResponseWriter, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -137,7 +137,7 @@ func parseUpstreamDialAddress(upstreamAddr string) (parsedAddr, error) {
|
|||||||
}
|
}
|
||||||
// we can assume a port if only a hostname is specified, but use of a
|
// we can assume a port if only a hostname is specified, but use of a
|
||||||
// placeholder without a port likely means a port will be filled in
|
// placeholder without a port likely means a port will be filled in
|
||||||
if port == "" && !strings.Contains(host, "{") && !caddy.IsUnixNetwork(network) {
|
if port == "" && !strings.Contains(host, "{") && !caddy.IsUnixNetwork(network) && !caddy.IsFdNetwork(network) {
|
||||||
port = "80"
|
port = "80"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ package reverseproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -27,6 +28,7 @@ import (
|
|||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||||
|
"github.com/caddyserver/caddy/v2/internal"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||||
@ -75,6 +77,8 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
|||||||
// health_timeout <duration>
|
// health_timeout <duration>
|
||||||
// health_status <status>
|
// health_status <status>
|
||||||
// health_body <regexp>
|
// health_body <regexp>
|
||||||
|
// health_method <value>
|
||||||
|
// health_request_body <value>
|
||||||
// health_follow_redirects
|
// health_follow_redirects
|
||||||
// health_headers {
|
// health_headers {
|
||||||
// <field> [<values...>]
|
// <field> [<values...>]
|
||||||
@ -89,12 +93,11 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
|||||||
//
|
//
|
||||||
// # streaming
|
// # streaming
|
||||||
// flush_interval <duration>
|
// flush_interval <duration>
|
||||||
// buffer_requests
|
// request_buffers <size>
|
||||||
// buffer_responses
|
// response_buffers <size>
|
||||||
// max_buffer_size <size>
|
|
||||||
// stream_timeout <duration>
|
// stream_timeout <duration>
|
||||||
// stream_close_delay <duration>
|
// stream_close_delay <duration>
|
||||||
// trace_logs
|
// verbose_logs
|
||||||
//
|
//
|
||||||
// # request manipulation
|
// # request manipulation
|
||||||
// trusted_proxies [private_ranges] <ranges...>
|
// trusted_proxies [private_ranges] <ranges...>
|
||||||
@ -353,6 +356,26 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
h.HealthChecks.Active.Path = d.Val()
|
h.HealthChecks.Active.Path = d.Val()
|
||||||
caddy.Log().Named("config.adapter.caddyfile").Warn("the 'health_path' subdirective is deprecated, please use 'health_uri' instead!")
|
caddy.Log().Named("config.adapter.caddyfile").Warn("the 'health_path' subdirective is deprecated, please use 'health_uri' instead!")
|
||||||
|
|
||||||
|
case "health_upstream":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
if h.HealthChecks == nil {
|
||||||
|
h.HealthChecks = new(HealthChecks)
|
||||||
|
}
|
||||||
|
if h.HealthChecks.Active == nil {
|
||||||
|
h.HealthChecks.Active = new(ActiveHealthChecks)
|
||||||
|
}
|
||||||
|
_, port, err := net.SplitHostPort(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("health_upstream is malformed '%s': %v", d.Val(), err)
|
||||||
|
}
|
||||||
|
_, err = strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("bad port number '%s': %v", d.Val(), err)
|
||||||
|
}
|
||||||
|
h.HealthChecks.Active.Upstream = d.Val()
|
||||||
|
|
||||||
case "health_port":
|
case "health_port":
|
||||||
if !d.NextArg() {
|
if !d.NextArg() {
|
||||||
return d.ArgErr()
|
return d.ArgErr()
|
||||||
@ -363,6 +386,9 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
if h.HealthChecks.Active == nil {
|
if h.HealthChecks.Active == nil {
|
||||||
h.HealthChecks.Active = new(ActiveHealthChecks)
|
h.HealthChecks.Active = new(ActiveHealthChecks)
|
||||||
}
|
}
|
||||||
|
if h.HealthChecks.Active.Upstream != "" {
|
||||||
|
return d.Errf("the 'health_port' subdirective is ignored if 'health_upstream' is used!")
|
||||||
|
}
|
||||||
portNum, err := strconv.Atoi(d.Val())
|
portNum, err := strconv.Atoi(d.Val())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return d.Errf("bad port number '%s': %v", d.Val(), err)
|
return d.Errf("bad port number '%s': %v", d.Val(), err)
|
||||||
@ -387,6 +413,30 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
}
|
}
|
||||||
h.HealthChecks.Active.Headers = healthHeaders
|
h.HealthChecks.Active.Headers = healthHeaders
|
||||||
|
|
||||||
|
case "health_method":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
if h.HealthChecks == nil {
|
||||||
|
h.HealthChecks = new(HealthChecks)
|
||||||
|
}
|
||||||
|
if h.HealthChecks.Active == nil {
|
||||||
|
h.HealthChecks.Active = new(ActiveHealthChecks)
|
||||||
|
}
|
||||||
|
h.HealthChecks.Active.Method = d.Val()
|
||||||
|
|
||||||
|
case "health_request_body":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
if h.HealthChecks == nil {
|
||||||
|
h.HealthChecks = new(HealthChecks)
|
||||||
|
}
|
||||||
|
if h.HealthChecks.Active == nil {
|
||||||
|
h.HealthChecks.Active = new(ActiveHealthChecks)
|
||||||
|
}
|
||||||
|
h.HealthChecks.Active.Body = d.Val()
|
||||||
|
|
||||||
case "health_interval":
|
case "health_interval":
|
||||||
if !d.NextArg() {
|
if !d.NextArg() {
|
||||||
return d.ArgErr()
|
return d.ArgErr()
|
||||||
@ -651,7 +701,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
case "trusted_proxies":
|
case "trusted_proxies":
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
if d.Val() == "private_ranges" {
|
if d.Val() == "private_ranges" {
|
||||||
h.TrustedProxies = append(h.TrustedProxies, caddyhttp.PrivateRangesCIDR()...)
|
h.TrustedProxies = append(h.TrustedProxies, internal.PrivateRangesCIDR()...)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h.TrustedProxies = append(h.TrustedProxies, d.Val())
|
h.TrustedProxies = append(h.TrustedProxies, d.Val())
|
||||||
@ -1275,7 +1325,11 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||||||
return d.Err("cannot specify \"tls_trust_pool\" twice in caddyfile")
|
return d.Err("cannot specify \"tls_trust_pool\" twice in caddyfile")
|
||||||
}
|
}
|
||||||
h.TLS.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil)
|
h.TLS.CARaw = caddyconfig.JSONModuleObject(ca, "provider", modStem, nil)
|
||||||
|
case "local_address":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
h.LocalAddress = d.Val()
|
||||||
default:
|
default:
|
||||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -229,11 +229,13 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
|
|||||||
|
|
||||||
if changeHost {
|
if changeHost {
|
||||||
if handler.Headers == nil {
|
if handler.Headers == nil {
|
||||||
handler.Headers = &headers.Handler{
|
handler.Headers = new(headers.Handler)
|
||||||
Request: &headers.HeaderOps{
|
|
||||||
Set: http.Header{},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
if handler.Headers.Request == nil {
|
||||||
|
handler.Headers.Request = new(headers.HeaderOps)
|
||||||
|
}
|
||||||
|
if handler.Headers.Request.Set == nil {
|
||||||
|
handler.Headers.Request.Set = http.Header{}
|
||||||
}
|
}
|
||||||
handler.Headers.Request.Set.Set("Host", "{http.reverse_proxy.upstream.hostport}")
|
handler.Headers.Request.Set.Set("Host", "{http.reverse_proxy.upstream.hostport}")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FCGIListenSockFileno describes listen socket file number.
|
// FCGIListenSockFileno describes listen socket file number.
|
||||||
@ -184,10 +185,13 @@ func (f clientCloser) Close() error {
|
|||||||
return f.rwc.Close()
|
return f.rwc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logLevel := zapcore.WarnLevel
|
||||||
if f.status >= 400 {
|
if f.status >= 400 {
|
||||||
f.logger.Error("stderr", zap.ByteString("body", stderr))
|
logLevel = zapcore.ErrorLevel
|
||||||
} else {
|
}
|
||||||
f.logger.Warn("stderr", zap.ByteString("body", stderr))
|
|
||||||
|
if c := f.logger.Check(logLevel, "stderr"); c != nil {
|
||||||
|
c.Write(zap.ByteString("body", stderr))
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.rwc.Close()
|
return f.rwc.Close()
|
||||||
|
|||||||
@ -148,10 +148,13 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
|
|||||||
zap.Object("request", loggableReq),
|
zap.Object("request", loggableReq),
|
||||||
zap.Object("env", loggableEnv),
|
zap.Object("env", loggableEnv),
|
||||||
)
|
)
|
||||||
logger.Debug("roundtrip",
|
if c := t.logger.Check(zapcore.DebugLevel, "roundtrip"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("dial", address),
|
zap.String("dial", address),
|
||||||
zap.Object("env", loggableEnv),
|
zap.Object("env", loggableEnv),
|
||||||
zap.Object("request", loggableReq))
|
zap.Object("request", loggableReq),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// connect to the backend
|
// connect to the backend
|
||||||
dialer := net.Dialer{Timeout: time.Duration(t.DialTimeout)}
|
dialer := net.Dialer{Timeout: time.Duration(t.DialTimeout)}
|
||||||
|
|||||||
@ -23,10 +23,13 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@ -75,13 +78,27 @@ type ActiveHealthChecks struct {
|
|||||||
// The URI (path and query) to use for health checks
|
// The URI (path and query) to use for health checks
|
||||||
URI string `json:"uri,omitempty"`
|
URI string `json:"uri,omitempty"`
|
||||||
|
|
||||||
|
// The host:port to use (if different from the upstream's dial address)
|
||||||
|
// for health checks. This should be used in tandem with `health_header` and
|
||||||
|
// `{http.reverse_proxy.active.target_upstream}`. This can be helpful when
|
||||||
|
// creating an intermediate service to do a more thorough health check.
|
||||||
|
// If upstream is set, the active health check port is ignored.
|
||||||
|
Upstream string `json:"upstream,omitempty"`
|
||||||
|
|
||||||
// The port to use (if different from the upstream's dial
|
// The port to use (if different from the upstream's dial
|
||||||
// address) for health checks.
|
// address) for health checks. If active upstream is set,
|
||||||
|
// this value is ignored.
|
||||||
Port int `json:"port,omitempty"`
|
Port int `json:"port,omitempty"`
|
||||||
|
|
||||||
// HTTP headers to set on health check requests.
|
// HTTP headers to set on health check requests.
|
||||||
Headers http.Header `json:"headers,omitempty"`
|
Headers http.Header `json:"headers,omitempty"`
|
||||||
|
|
||||||
|
// The HTTP method to use for health checks (default "GET").
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
|
||||||
|
// The body to send with the health check request.
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
|
||||||
// Whether to follow HTTP redirects in response to active health checks (default off).
|
// Whether to follow HTTP redirects in response to active health checks (default off).
|
||||||
FollowRedirects bool `json:"follow_redirects,omitempty"`
|
FollowRedirects bool `json:"follow_redirects,omitempty"`
|
||||||
|
|
||||||
@ -133,6 +150,11 @@ func (a *ActiveHealthChecks) Provision(ctx caddy.Context, h *Handler) error {
|
|||||||
}
|
}
|
||||||
a.Headers = cleaned
|
a.Headers = cleaned
|
||||||
|
|
||||||
|
// If Method is not set, default to GET
|
||||||
|
if a.Method == "" {
|
||||||
|
a.Method = http.MethodGet
|
||||||
|
}
|
||||||
|
|
||||||
h.HealthChecks.Active.logger = h.logger.Named("health_checker.active")
|
h.HealthChecks.Active.logger = h.logger.Named("health_checker.active")
|
||||||
|
|
||||||
timeout := time.Duration(a.Timeout)
|
timeout := time.Duration(a.Timeout)
|
||||||
@ -165,9 +187,14 @@ func (a *ActiveHealthChecks) Provision(ctx caddy.Context, h *Handler) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, upstream := range h.Upstreams {
|
for _, upstream := range h.Upstreams {
|
||||||
|
// if there's an alternative upstream for health-check provided in the config,
|
||||||
|
// then use it, otherwise use the upstream's dial address. if upstream is used,
|
||||||
|
// then the port is ignored.
|
||||||
|
if a.Upstream != "" {
|
||||||
|
upstream.activeHealthCheckUpstream = a.Upstream
|
||||||
|
} else if a.Port != 0 {
|
||||||
// if there's an alternative port for health-check provided in the config,
|
// if there's an alternative port for health-check provided in the config,
|
||||||
// then use it, otherwise use the port of upstream.
|
// then use it, otherwise use the port of upstream.
|
||||||
if a.Port != 0 {
|
|
||||||
upstream.activeHealthCheckPort = a.Port
|
upstream.activeHealthCheckPort = a.Port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,9 +272,12 @@ type CircuitBreaker interface {
|
|||||||
func (h *Handler) activeHealthChecker() {
|
func (h *Handler) activeHealthChecker() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
h.HealthChecks.Active.logger.Error("active health checker panicked",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health checker panicked"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Any("error", err),
|
zap.Any("error", err),
|
||||||
zap.ByteString("stack", debug.Stack()))
|
zap.ByteString("stack", debug.Stack()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
ticker := time.NewTicker(time.Duration(h.HealthChecks.Active.Interval))
|
ticker := time.NewTicker(time.Duration(h.HealthChecks.Active.Interval))
|
||||||
@ -270,55 +300,66 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
|
|||||||
go func(upstream *Upstream) {
|
go func(upstream *Upstream) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
h.HealthChecks.Active.logger.Error("active health check panicked",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health checker panicked"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Any("error", err),
|
zap.Any("error", err),
|
||||||
zap.ByteString("stack", debug.Stack()))
|
zap.ByteString("stack", debug.Stack()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
networkAddr, err := caddy.NewReplacer().ReplaceOrErr(upstream.Dial, true, true)
|
networkAddr, err := caddy.NewReplacer().ReplaceOrErr(upstream.Dial, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Active.logger.Error("invalid use of placeholders in dial address for active health checks",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "invalid use of placeholders in dial address for active health checks"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("address", networkAddr),
|
zap.String("address", networkAddr),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
addr, err := caddy.ParseNetworkAddress(networkAddr)
|
addr, err := caddy.ParseNetworkAddress(networkAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Active.logger.Error("bad network address",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "bad network address"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("address", networkAddr),
|
zap.String("address", networkAddr),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if hcp := uint(upstream.activeHealthCheckPort); hcp != 0 {
|
if hcp := uint(upstream.activeHealthCheckPort); hcp != 0 {
|
||||||
if addr.IsUnixNetwork() {
|
if addr.IsUnixNetwork() || addr.IsFdNetwork() {
|
||||||
addr.Network = "tcp" // I guess we just assume TCP since we are using a port??
|
addr.Network = "tcp" // I guess we just assume TCP since we are using a port??
|
||||||
}
|
}
|
||||||
addr.StartPort, addr.EndPort = hcp, hcp
|
addr.StartPort, addr.EndPort = hcp, hcp
|
||||||
}
|
}
|
||||||
if addr.PortRangeSize() != 1 {
|
if addr.PortRangeSize() != 1 {
|
||||||
h.HealthChecks.Active.logger.Error("multiple addresses (upstream must map to only one address)",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "multiple addresses (upstream must map to only one address)"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("address", networkAddr),
|
zap.String("address", networkAddr),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hostAddr := addr.JoinHostPort(0)
|
hostAddr := addr.JoinHostPort(0)
|
||||||
dialAddr := hostAddr
|
dialAddr := hostAddr
|
||||||
if addr.IsUnixNetwork() {
|
if addr.IsUnixNetwork() || addr.IsFdNetwork() {
|
||||||
// this will be used as the Host portion of a http.Request URL, and
|
// this will be used as the Host portion of a http.Request URL, and
|
||||||
// paths to socket files would produce an error when creating URL,
|
// paths to socket files would produce an error when creating URL,
|
||||||
// so use a fake Host value instead; unix sockets are usually local
|
// so use a fake Host value instead; unix sockets are usually local
|
||||||
hostAddr = "localhost"
|
hostAddr = "localhost"
|
||||||
}
|
}
|
||||||
err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: dialAddr}, hostAddr, upstream)
|
err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: dialAddr}, hostAddr, networkAddr, upstream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Active.logger.Error("active health check failed",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "active health check failed"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("address", hostAddr),
|
zap.String("address", hostAddr),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}(upstream)
|
}(upstream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -330,7 +371,7 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
|
|||||||
// according to whether it passes the health check. An error is
|
// according to whether it passes the health check. An error is
|
||||||
// returned only if the health check fails to occur or if marking
|
// returned only if the health check fails to occur or if marking
|
||||||
// the host's health status fails.
|
// the host's health status fails.
|
||||||
func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstream *Upstream) error {
|
func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, networkAddr string, upstream *Upstream) error {
|
||||||
// create the URL for the request that acts as a health check
|
// create the URL for the request that acts as a health check
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
@ -342,7 +383,12 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
host = hostAddr
|
host = hostAddr
|
||||||
}
|
}
|
||||||
if h.HealthChecks.Active.Port != 0 {
|
|
||||||
|
// ignore active health check port if active upstream is provided as the
|
||||||
|
// active upstream already contains the replacement port
|
||||||
|
if h.HealthChecks.Active.Upstream != "" {
|
||||||
|
u.Host = h.HealthChecks.Active.Upstream
|
||||||
|
} else if h.HealthChecks.Active.Port != 0 {
|
||||||
port := strconv.Itoa(h.HealthChecks.Active.Port)
|
port := strconv.Itoa(h.HealthChecks.Active.Port)
|
||||||
u.Host = net.JoinHostPort(host, port)
|
u.Host = net.JoinHostPort(host, port)
|
||||||
}
|
}
|
||||||
@ -352,14 +398,10 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
u.Scheme = "https"
|
u.Scheme = "https"
|
||||||
|
|
||||||
// if the port is in the except list, flip back to HTTP
|
// if the port is in the except list, flip back to HTTP
|
||||||
if ht, ok := h.Transport.(*HTTPTransport); ok {
|
if ht, ok := h.Transport.(*HTTPTransport); ok && slices.Contains(ht.TLS.ExceptPorts, port) {
|
||||||
for _, exceptPort := range ht.TLS.ExceptPorts {
|
|
||||||
if exceptPort == port {
|
|
||||||
u.Scheme = "http"
|
u.Scheme = "http"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have a provisioned uri, use that, otherwise use
|
// if we have a provisioned uri, use that, otherwise use
|
||||||
// the deprecated Path option
|
// the deprecated Path option
|
||||||
@ -370,6 +412,16 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
u.Path = h.HealthChecks.Active.Path
|
u.Path = h.HealthChecks.Active.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replacer used for both body and headers. Only globals (env vars, system info, etc.) are available
|
||||||
|
repl := caddy.NewReplacer()
|
||||||
|
|
||||||
|
// if body is provided, create a reader for it, otherwise nil
|
||||||
|
var requestBody io.Reader
|
||||||
|
if h.HealthChecks.Active.Body != "" {
|
||||||
|
// set body, using replacer
|
||||||
|
requestBody = strings.NewReader(repl.ReplaceAll(h.HealthChecks.Active.Body, ""))
|
||||||
|
}
|
||||||
|
|
||||||
// attach dialing information to this request, as well as context values that
|
// attach dialing information to this request, as well as context values that
|
||||||
// may be expected by handlers of this request
|
// may be expected by handlers of this request
|
||||||
ctx := h.ctx.Context
|
ctx := h.ctx.Context
|
||||||
@ -377,15 +429,15 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{
|
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]any{
|
||||||
dialInfoVarKey: dialInfo,
|
dialInfoVarKey: dialInfo,
|
||||||
})
|
})
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
req, err := http.NewRequestWithContext(ctx, h.HealthChecks.Active.Method, u.String(), requestBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("making request: %v", err)
|
return fmt.Errorf("making request: %v", err)
|
||||||
}
|
}
|
||||||
ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *req)
|
ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *req)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
// set headers, using a replacer with only globals (env vars, system info, etc.)
|
// set headers, using replacer
|
||||||
repl := caddy.NewReplacer()
|
repl.Set("http.reverse_proxy.active.target_upstream", networkAddr)
|
||||||
for key, vals := range h.HealthChecks.Active.Headers {
|
for key, vals := range h.HealthChecks.Active.Headers {
|
||||||
key = repl.ReplaceAll(key, "")
|
key = repl.ReplaceAll(key, "")
|
||||||
if key == "Host" {
|
if key == "Host" {
|
||||||
@ -401,9 +453,12 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
// increment failures and then check if it has reached the threshold to mark unhealthy
|
// increment failures and then check if it has reached the threshold to mark unhealthy
|
||||||
err := upstream.Host.countHealthFail(1)
|
err := upstream.Host.countHealthFail(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Active.logger.Error("could not count active health failure",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count active health failure"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("host", upstream.Dial),
|
zap.String("host", upstream.Dial),
|
||||||
zap.Error(err))
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if upstream.Host.activeHealthFails() >= h.HealthChecks.Active.Fails {
|
if upstream.Host.activeHealthFails() >= h.HealthChecks.Active.Fails {
|
||||||
@ -419,13 +474,19 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
// increment passes and then check if it has reached the threshold to be healthy
|
// increment passes and then check if it has reached the threshold to be healthy
|
||||||
err := upstream.Host.countHealthPass(1)
|
err := upstream.Host.countHealthPass(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Active.logger.Error("could not count active health pass",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count active health pass"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("host", upstream.Dial),
|
zap.String("host", upstream.Dial),
|
||||||
zap.Error(err))
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if upstream.Host.activeHealthPasses() >= h.HealthChecks.Active.Passes {
|
if upstream.Host.activeHealthPasses() >= h.HealthChecks.Active.Passes {
|
||||||
if upstream.setHealthy(true) {
|
if upstream.setHealthy(true) {
|
||||||
|
if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "host is up"); c != nil {
|
||||||
|
c.Write(zap.String("host", hostAddr))
|
||||||
|
}
|
||||||
h.events.Emit(h.ctx, "healthy", map[string]any{"host": hostAddr})
|
h.events.Emit(h.ctx, "healthy", map[string]any{"host": hostAddr})
|
||||||
upstream.Host.resetHealth()
|
upstream.Host.resetHealth()
|
||||||
}
|
}
|
||||||
@ -435,10 +496,12 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
// do the request, being careful to tame the response body
|
// do the request, being careful to tame the response body
|
||||||
resp, err := h.HealthChecks.Active.httpClient.Do(req)
|
resp, err := h.HealthChecks.Active.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Active.logger.Info("HTTP request failed",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "HTTP request failed"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("host", hostAddr),
|
zap.String("host", hostAddr),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
markUnhealthy()
|
markUnhealthy()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -455,18 +518,22 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
// if status code is outside criteria, mark down
|
// if status code is outside criteria, mark down
|
||||||
if h.HealthChecks.Active.ExpectStatus > 0 {
|
if h.HealthChecks.Active.ExpectStatus > 0 {
|
||||||
if !caddyhttp.StatusCodeMatches(resp.StatusCode, h.HealthChecks.Active.ExpectStatus) {
|
if !caddyhttp.StatusCodeMatches(resp.StatusCode, h.HealthChecks.Active.ExpectStatus) {
|
||||||
h.HealthChecks.Active.logger.Info("unexpected status code",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "unexpected status code"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Int("status_code", resp.StatusCode),
|
zap.Int("status_code", resp.StatusCode),
|
||||||
zap.String("host", hostAddr),
|
zap.String("host", hostAddr),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
markUnhealthy()
|
markUnhealthy()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
} else if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
h.HealthChecks.Active.logger.Info("status code out of tolerances",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "status code out of tolerances"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Int("status_code", resp.StatusCode),
|
zap.Int("status_code", resp.StatusCode),
|
||||||
zap.String("host", hostAddr),
|
zap.String("host", hostAddr),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
markUnhealthy()
|
markUnhealthy()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -475,24 +542,27 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
|
|||||||
if h.HealthChecks.Active.bodyRegexp != nil {
|
if h.HealthChecks.Active.bodyRegexp != nil {
|
||||||
bodyBytes, err := io.ReadAll(body)
|
bodyBytes, err := io.ReadAll(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Active.logger.Info("failed to read response body",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "failed to read response body"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("host", hostAddr),
|
zap.String("host", hostAddr),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
markUnhealthy()
|
markUnhealthy()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !h.HealthChecks.Active.bodyRegexp.Match(bodyBytes) {
|
if !h.HealthChecks.Active.bodyRegexp.Match(bodyBytes) {
|
||||||
h.HealthChecks.Active.logger.Info("response body failed expectations",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.InfoLevel, "response body failed expectations"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("host", hostAddr),
|
zap.String("host", hostAddr),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
markUnhealthy()
|
markUnhealthy()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// passed health check parameters, so mark as healthy
|
// passed health check parameters, so mark as healthy
|
||||||
h.HealthChecks.Active.logger.Info("host is up", zap.String("host", hostAddr))
|
|
||||||
markHealthy()
|
markHealthy()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -516,9 +586,12 @@ func (h *Handler) countFailure(upstream *Upstream) {
|
|||||||
// count failure immediately
|
// count failure immediately
|
||||||
err := upstream.Host.countFail(1)
|
err := upstream.Host.countFail(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Passive.logger.Error("could not count failure",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not count failure"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("host", upstream.Dial),
|
zap.String("host", upstream.Dial),
|
||||||
zap.Error(err))
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,9 +599,12 @@ func (h *Handler) countFailure(upstream *Upstream) {
|
|||||||
go func(host *Host, failDuration time.Duration) {
|
go func(host *Host, failDuration time.Duration) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
h.HealthChecks.Passive.logger.Error("passive health check failure forgetter panicked",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "passive health check failure forgetter panicked"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Any("error", err),
|
zap.Any("error", err),
|
||||||
zap.ByteString("stack", debug.Stack()))
|
zap.ByteString("stack", debug.Stack()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
timer := time.NewTimer(failDuration)
|
timer := time.NewTimer(failDuration)
|
||||||
@ -541,9 +617,12 @@ func (h *Handler) countFailure(upstream *Upstream) {
|
|||||||
}
|
}
|
||||||
err := host.countFail(-1)
|
err := host.countFail(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HealthChecks.Passive.logger.Error("could not forget failure",
|
if c := h.HealthChecks.Active.logger.Check(zapcore.ErrorLevel, "could not forget failure"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("host", upstream.Dial),
|
zap.String("host", upstream.Dial),
|
||||||
zap.Error(err))
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}(upstream.Host, failDuration)
|
}(upstream.Host, failDuration)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,6 +58,7 @@ type Upstream struct {
|
|||||||
// IPAffinity string
|
// IPAffinity string
|
||||||
|
|
||||||
activeHealthCheckPort int
|
activeHealthCheckPort int
|
||||||
|
activeHealthCheckUpstream string
|
||||||
healthCheckPolicy *PassiveHealthChecks
|
healthCheckPolicy *PassiveHealthChecks
|
||||||
cb CircuitBreaker
|
cb CircuitBreaker
|
||||||
unhealthy int32 // accessed atomically; status from active health checker
|
unhealthy int32 // accessed atomically; status from active health checker
|
||||||
|
|||||||
@ -27,12 +27,14 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pires/go-proxyproto"
|
"github.com/pires/go-proxyproto"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
@ -132,6 +134,10 @@ type HTTPTransport struct {
|
|||||||
// to change or removal while experimental.
|
// to change or removal while experimental.
|
||||||
Versions []string `json:"versions,omitempty"`
|
Versions []string `json:"versions,omitempty"`
|
||||||
|
|
||||||
|
// Specify the address to bind to when connecting to an upstream. In other words,
|
||||||
|
// it is the address the upstream sees as the remote address.
|
||||||
|
LocalAddress string `json:"local_address,omitempty"`
|
||||||
|
|
||||||
// The pre-configured underlying HTTP transport.
|
// The pre-configured underlying HTTP transport.
|
||||||
Transport *http.Transport `json:"-"`
|
Transport *http.Transport `json:"-"`
|
||||||
|
|
||||||
@ -185,6 +191,31 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
|||||||
FallbackDelay: time.Duration(h.FallbackDelay),
|
FallbackDelay: time.Duration(h.FallbackDelay),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.LocalAddress != "" {
|
||||||
|
netaddr, err := caddy.ParseNetworkAddressWithDefaults(h.LocalAddress, "tcp", 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if netaddr.PortRangeSize() > 1 {
|
||||||
|
return nil, fmt.Errorf("local_address must be a single address, not a port range")
|
||||||
|
}
|
||||||
|
switch netaddr.Network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
dialer.LocalAddr, err = net.ResolveTCPAddr(netaddr.Network, netaddr.JoinHostPort(0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "unix", "unixgram", "unixpacket":
|
||||||
|
dialer.LocalAddr, err = net.ResolveUnixAddr(netaddr.Network, netaddr.JoinHostPort(0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
return nil, fmt.Errorf("local_address must be a TCP address, not a UDP address")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported network")
|
||||||
|
}
|
||||||
|
}
|
||||||
if h.Resolver != nil {
|
if h.Resolver != nil {
|
||||||
err := h.Resolver.ParseAddresses()
|
err := h.Resolver.ParseAddresses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -351,7 +382,7 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
|||||||
rt.DisableCompression = !*h.Compression
|
rt.DisableCompression = !*h.Compression
|
||||||
}
|
}
|
||||||
|
|
||||||
if sliceContains(h.Versions, "2") {
|
if slices.Contains(h.Versions, "2") {
|
||||||
if err := http2.ConfigureTransport(rt); err != nil {
|
if err := http2.ConfigureTransport(rt); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -363,13 +394,20 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
|
|||||||
// site owners control the backends), so it must be exclusive
|
// site owners control the backends), so it must be exclusive
|
||||||
if len(h.Versions) == 1 && h.Versions[0] == "3" {
|
if len(h.Versions) == 1 && h.Versions[0] == "3" {
|
||||||
h.h3Transport = new(http3.RoundTripper)
|
h.h3Transport = new(http3.RoundTripper)
|
||||||
} else if len(h.Versions) > 1 && sliceContains(h.Versions, "3") {
|
if h.TLS != nil {
|
||||||
|
var err error
|
||||||
|
h.h3Transport.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("making TLS client config for HTTP/3 transport: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(h.Versions) > 1 && slices.Contains(h.Versions, "3") {
|
||||||
return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported")
|
return nil, fmt.Errorf("if HTTP/3 is enabled to the upstream, no other HTTP versions are supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if h2c is enabled, configure its transport (std lib http.Transport
|
// if h2c is enabled, configure its transport (std lib http.Transport
|
||||||
// does not "HTTP/2 over cleartext TCP")
|
// does not "HTTP/2 over cleartext TCP")
|
||||||
if sliceContains(h.Versions, "h2c") {
|
if slices.Contains(h.Versions, "h2c") {
|
||||||
// crafting our own http2.Transport doesn't allow us to utilize
|
// crafting our own http2.Transport doesn't allow us to utilize
|
||||||
// most of the customizations/preferences on the http.Transport,
|
// most of the customizations/preferences on the http.Transport,
|
||||||
// because, for some reason, only http2.ConfigureTransport()
|
// because, for some reason, only http2.ConfigureTransport()
|
||||||
@ -439,6 +477,9 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||||||
// if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is
|
// if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is
|
||||||
// HTTP without TLS, use the alternate H2C-capable transport instead
|
// HTTP without TLS, use the alternate H2C-capable transport instead
|
||||||
if req.URL.Scheme == "http" && h.h2cTransport != nil {
|
if req.URL.Scheme == "http" && h.h2cTransport != nil {
|
||||||
|
// There is no dedicated DisableKeepAlives field in *http2.Transport.
|
||||||
|
// This is an alternative way to disable keep-alive.
|
||||||
|
req.Close = h.Transport.DisableKeepAlives
|
||||||
return h.h2cTransport.RoundTrip(req)
|
return h.h2cTransport.RoundTrip(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -711,7 +752,9 @@ func (c *tcpRWTimeoutConn) Read(b []byte) (int, error) {
|
|||||||
if c.readTimeout > 0 {
|
if c.readTimeout > 0 {
|
||||||
err := c.TCPConn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
err := c.TCPConn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("failed to set read deadline", zap.Error(err))
|
if ce := c.logger.Check(zapcore.ErrorLevel, "failed to set read deadline"); ce != nil {
|
||||||
|
ce.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.TCPConn.Read(b)
|
return c.TCPConn.Read(b)
|
||||||
@ -721,7 +764,9 @@ func (c *tcpRWTimeoutConn) Write(b []byte) (int, error) {
|
|||||||
if c.writeTimeout > 0 {
|
if c.writeTimeout > 0 {
|
||||||
err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("failed to set write deadline", zap.Error(err))
|
if ce := c.logger.Check(zapcore.ErrorLevel, "failed to set write deadline"); ce != nil {
|
||||||
|
ce.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.TCPConn.Write(b)
|
return c.TCPConn.Write(b)
|
||||||
@ -739,16 +784,6 @@ func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
|
|||||||
return x509.ParseCertificate(derBytes)
|
return x509.ParseCertificate(derBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sliceContains returns true if needle is in haystack.
|
|
||||||
func sliceContains(haystack []string, needle string) bool {
|
|
||||||
for _, s := range haystack {
|
|
||||||
if s == needle {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ caddy.Provisioner = (*HTTPTransport)(nil)
|
_ caddy.Provisioner = (*HTTPTransport)(nil)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
var reverseProxyMetrics = struct {
|
var reverseProxyMetrics = struct {
|
||||||
@ -48,9 +49,12 @@ func (m *metricsUpstreamsHealthyUpdater) Init() {
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
reverseProxyMetrics.logger.Error("upstreams healthy metrics updater panicked",
|
if c := reverseProxyMetrics.logger.Check(zapcore.ErrorLevel, "upstreams healthy metrics updater panicked"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Any("error", err),
|
zap.Any("error", err),
|
||||||
zap.ByteString("stack", debug.Stack()))
|
zap.ByteString("stack", debug.Stack()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
"golang.org/x/net/http/httpguts"
|
"golang.org/x/net/http/httpguts"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
@ -68,6 +69,7 @@ func init() {
|
|||||||
// `{http.reverse_proxy.upstream.duration_ms}` | Same as 'upstream.duration', but in milliseconds.
|
// `{http.reverse_proxy.upstream.duration_ms}` | Same as 'upstream.duration', but in milliseconds.
|
||||||
// `{http.reverse_proxy.duration}` | Total time spent proxying, including selecting an upstream, retries, and writing response.
|
// `{http.reverse_proxy.duration}` | Total time spent proxying, including selecting an upstream, retries, and writing response.
|
||||||
// `{http.reverse_proxy.duration_ms}` | Same as 'duration', but in milliseconds.
|
// `{http.reverse_proxy.duration_ms}` | Same as 'duration', but in milliseconds.
|
||||||
|
// `{http.reverse_proxy.retries}` | The number of retries actually performed to communicate with an upstream.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
// Configures the method of transport for the proxy. A transport
|
// Configures the method of transport for the proxy. A transport
|
||||||
// is what performs the actual "round trip" to the backend.
|
// is what performs the actual "round trip" to the backend.
|
||||||
@ -439,11 +441,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
|||||||
if h.LoadBalancing != nil {
|
if h.LoadBalancing != nil {
|
||||||
lbWait = time.Duration(h.LoadBalancing.TryInterval)
|
lbWait = time.Duration(h.LoadBalancing.TryInterval)
|
||||||
}
|
}
|
||||||
h.logger.Debug("retrying", zap.Error(proxyErr), zap.Duration("after", lbWait))
|
if c := h.logger.Check(zapcore.DebugLevel, "retrying"); c != nil {
|
||||||
|
c.Write(zap.Error(proxyErr), zap.Duration("after", lbWait))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
retries++
|
retries++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// number of retries actually performed
|
||||||
|
repl.Set("http.reverse_proxy.retries", retries)
|
||||||
|
|
||||||
if proxyErr != nil {
|
if proxyErr != nil {
|
||||||
return statusError(proxyErr)
|
return statusError(proxyErr)
|
||||||
}
|
}
|
||||||
@ -463,13 +470,17 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
|
|||||||
if h.DynamicUpstreams != nil {
|
if h.DynamicUpstreams != nil {
|
||||||
dUpstreams, err := h.DynamicUpstreams.GetUpstreams(r)
|
dUpstreams, err := h.DynamicUpstreams.GetUpstreams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("failed getting dynamic upstreams; falling back to static upstreams", zap.Error(err))
|
if c := h.logger.Check(zapcore.ErrorLevel, "failed getting dynamic upstreams; falling back to static upstreams"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
upstreams = dUpstreams
|
upstreams = dUpstreams
|
||||||
for _, dUp := range dUpstreams {
|
for _, dUp := range dUpstreams {
|
||||||
h.provisionUpstream(dUp)
|
h.provisionUpstream(dUp)
|
||||||
}
|
}
|
||||||
h.logger.Debug("provisioned dynamic upstreams", zap.Int("count", len(dUpstreams)))
|
if c := h.logger.Check(zapcore.DebugLevel, "provisioned dynamic upstreams"); c != nil {
|
||||||
|
c.Write(zap.Int("count", len(dUpstreams)))
|
||||||
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
// these upstreams are dynamic, so they are only used for this iteration
|
// these upstreams are dynamic, so they are only used for this iteration
|
||||||
// of the proxy loop; be sure to let them go away when we're done with them
|
// of the proxy loop; be sure to let them go away when we're done with them
|
||||||
@ -500,9 +511,12 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
|
|||||||
return true, fmt.Errorf("making dial info: %v", err)
|
return true, fmt.Errorf("making dial info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logger.Debug("selected upstream",
|
if c := h.logger.Check(zapcore.DebugLevel, "selected upstream"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("dial", dialInfo.Address),
|
zap.String("dial", dialInfo.Address),
|
||||||
zap.Int("total_upstreams", len(upstreams)))
|
zap.Int("total_upstreams", len(upstreams)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// attach to the request information about how to dial the upstream;
|
// attach to the request information about how to dial the upstream;
|
||||||
// this is necessary because the information cannot be sufficiently
|
// this is necessary because the information cannot be sufficiently
|
||||||
@ -606,6 +620,18 @@ func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.
|
|||||||
req.Header.Set("User-Agent", "")
|
req.Header.Set("User-Agent", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Indicate if request has been conveyed in early data.
|
||||||
|
// RFC 8470: "An intermediary that forwards a request prior to the
|
||||||
|
// completion of the TLS handshake with its client MUST send it with
|
||||||
|
// the Early-Data header field set to “1” (i.e., it adds it if not
|
||||||
|
// present in the request). An intermediary MUST use the Early-Data
|
||||||
|
// header field if the request might have been subject to a replay and
|
||||||
|
// might already have been forwarded by it or another instance
|
||||||
|
// (see Section 6.2)."
|
||||||
|
if req.TLS != nil && !req.TLS.HandshakeComplete {
|
||||||
|
req.Header.Set("Early-Data", "1")
|
||||||
|
}
|
||||||
|
|
||||||
reqUpType := upgradeType(req.Header)
|
reqUpType := upgradeType(req.Header)
|
||||||
removeConnectionHeaders(req.Header)
|
removeConnectionHeaders(req.Header)
|
||||||
|
|
||||||
@ -798,16 +824,22 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
|
|||||||
ShouldLogCredentials: shouldLogCredentials,
|
ShouldLogCredentials: shouldLogCredentials,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug("upstream roundtrip", zap.Error(err))
|
if c := logger.Check(zapcore.DebugLevel, "upstream roundtrip"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Debug("upstream roundtrip",
|
if c := logger.Check(zapcore.DebugLevel, "upstream roundtrip"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Object("headers", caddyhttp.LoggableHTTPHeader{
|
zap.Object("headers", caddyhttp.LoggableHTTPHeader{
|
||||||
Header: res.Header,
|
Header: res.Header,
|
||||||
ShouldLogCredentials: shouldLogCredentials,
|
ShouldLogCredentials: shouldLogCredentials,
|
||||||
}),
|
}),
|
||||||
zap.Int("status", res.StatusCode))
|
zap.Int("status", res.StatusCode),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// duration until upstream wrote response headers (roundtrip duration)
|
// duration until upstream wrote response headers (roundtrip duration)
|
||||||
repl.Set("http.reverse_proxy.upstream.latency", duration)
|
repl.Set("http.reverse_proxy.upstream.latency", duration)
|
||||||
@ -866,7 +898,9 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
|
|||||||
repl.Set("http.reverse_proxy.status_code", res.StatusCode)
|
repl.Set("http.reverse_proxy.status_code", res.StatusCode)
|
||||||
repl.Set("http.reverse_proxy.status_text", res.Status)
|
repl.Set("http.reverse_proxy.status_text", res.Status)
|
||||||
|
|
||||||
logger.Debug("handling response", zap.Int("handler", i))
|
if c := logger.Check(zapcore.DebugLevel, "handling response"); c != nil {
|
||||||
|
c.Write(zap.Int("handler", i))
|
||||||
|
}
|
||||||
|
|
||||||
// we make some data available via request context to child routes
|
// we make some data available via request context to child routes
|
||||||
// so that they may inherit some options and functions from the
|
// so that they may inherit some options and functions from the
|
||||||
@ -962,7 +996,9 @@ func (h *Handler) finalizeResponse(
|
|||||||
err := h.copyResponse(rw, res.Body, h.flushInterval(req, res), logger)
|
err := h.copyResponse(rw, res.Body, h.flushInterval(req, res), logger)
|
||||||
errClose := res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
errClose := res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||||
if h.VerboseLogs || errClose != nil {
|
if h.VerboseLogs || errClose != nil {
|
||||||
logger.Debug("closed response body from upstream", zap.Error(errClose))
|
if c := logger.Check(zapcore.DebugLevel, "closed response body from upstream"); c != nil {
|
||||||
|
c.Write(zap.Error(errClose))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we're streaming the response and we've already written headers, so
|
// we're streaming the response and we've already written headers, so
|
||||||
@ -970,7 +1006,9 @@ func (h *Handler) finalizeResponse(
|
|||||||
// we'll just log the error and abort the stream here and panic just as
|
// we'll just log the error and abort the stream here and panic just as
|
||||||
// the standard lib's proxy to propagate the stream error.
|
// the standard lib's proxy to propagate the stream error.
|
||||||
// see issue https://github.com/caddyserver/caddy/issues/5951
|
// see issue https://github.com/caddyserver/caddy/issues/5951
|
||||||
logger.Error("aborting with incomplete response", zap.Error(err))
|
if c := logger.Check(zapcore.WarnLevel, "aborting with incomplete response"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
// no extra logging from stdlib
|
// no extra logging from stdlib
|
||||||
panic(http.ErrAbortHandler)
|
panic(http.ErrAbortHandler)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
"golang.org/x/net/http/httpguts"
|
"golang.org/x/net/http/httpguts"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,14 +42,18 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
|||||||
// Taken from https://github.com/golang/go/commit/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a
|
// Taken from https://github.com/golang/go/commit/5c489514bc5e61ad9b5b07bd7d8ec65d66a0512a
|
||||||
// We know reqUpType is ASCII, it's checked by the caller.
|
// We know reqUpType is ASCII, it's checked by the caller.
|
||||||
if !asciiIsPrint(resUpType) {
|
if !asciiIsPrint(resUpType) {
|
||||||
logger.Debug("backend tried to switch to invalid protocol",
|
if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to invalid protocol"); c != nil {
|
||||||
zap.String("backend_upgrade", resUpType))
|
c.Write(zap.String("backend_upgrade", resUpType))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !asciiEqualFold(reqUpType, resUpType) {
|
if !asciiEqualFold(reqUpType, resUpType) {
|
||||||
logger.Debug("backend tried to switch to unexpected protocol via Upgrade header",
|
if c := logger.Check(zapcore.DebugLevel, "backend tried to switch to unexpected protocol via Upgrade header"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("backend_upgrade", resUpType),
|
zap.String("backend_upgrade", resUpType),
|
||||||
zap.String("requested_upgrade", reqUpType))
|
zap.String("requested_upgrade", reqUpType),
|
||||||
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,12 +73,16 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
|||||||
//nolint:bodyclose
|
//nolint:bodyclose
|
||||||
conn, brw, hijackErr := http.NewResponseController(rw).Hijack()
|
conn, brw, hijackErr := http.NewResponseController(rw).Hijack()
|
||||||
if errors.Is(hijackErr, http.ErrNotSupported) {
|
if errors.Is(hijackErr, http.ErrNotSupported) {
|
||||||
h.logger.Error("can't switch protocols using non-Hijacker ResponseWriter", zap.String("type", fmt.Sprintf("%T", rw)))
|
if c := logger.Check(zapcore.ErrorLevel, "can't switch protocols using non-Hijacker ResponseWriter"); c != nil {
|
||||||
|
c.Write(zap.String("type", fmt.Sprintf("%T", rw)))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if hijackErr != nil {
|
if hijackErr != nil {
|
||||||
h.logger.Error("hijack failed on protocol switch", zap.Error(hijackErr))
|
if c := logger.Check(zapcore.ErrorLevel, "hijack failed on protocol switch"); c != nil {
|
||||||
|
c.Write(zap.Error(hijackErr))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,11 +102,15 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
logger.Debug("connection closed", zap.Duration("duration", time.Since(start)))
|
if c := logger.Check(zapcore.DebugLevel, "hijack failed on protocol switch"); c != nil {
|
||||||
|
c.Write(zap.Duration("duration", time.Since(start)))
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := brw.Flush(); err != nil {
|
if err := brw.Flush(); err != nil {
|
||||||
logger.Debug("response flush", zap.Error(err))
|
if c := logger.Check(zapcore.DebugLevel, "response flush"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +120,9 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
|||||||
data, _ := brw.Peek(buffered)
|
data, _ := brw.Peek(buffered)
|
||||||
_, err := backConn.Write(data)
|
_, err := backConn.Write(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug("backConn write failed", zap.Error(err))
|
if c := logger.Check(zapcore.DebugLevel, "backConn write failed"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,9 +163,13 @@ func (h *Handler) handleUpgradeResponse(logger *zap.Logger, wg *sync.WaitGroup,
|
|||||||
go spc.copyFromBackend(errc)
|
go spc.copyFromBackend(errc)
|
||||||
select {
|
select {
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
logger.Debug("streaming error", zap.Error(err))
|
if c := logger.Check(zapcore.DebugLevel, "streaming error"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
case time := <-timeoutc:
|
case time := <-timeoutc:
|
||||||
logger.Debug("stream timed out", zap.Time("timeout", time))
|
if c := logger.Check(zapcore.DebugLevel, "stream timed out"); c != nil {
|
||||||
|
c.Write(zap.Time("timeout", time))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +266,9 @@ func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, logger *za
|
|||||||
logger.Debug("waiting to read from upstream")
|
logger.Debug("waiting to read from upstream")
|
||||||
nr, rerr := src.Read(buf)
|
nr, rerr := src.Read(buf)
|
||||||
logger := logger.With(zap.Int("read", nr))
|
logger := logger.With(zap.Int("read", nr))
|
||||||
logger.Debug("read from upstream", zap.Error(rerr))
|
if c := logger.Check(zapcore.DebugLevel, "read from upstream"); c != nil {
|
||||||
|
c.Write(zap.Error(rerr))
|
||||||
|
}
|
||||||
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
|
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
|
||||||
// TODO: this could be useful to know (indeed, it revealed an error in our
|
// TODO: this could be useful to know (indeed, it revealed an error in our
|
||||||
// fastcgi PoC earlier; but it's this single error report here that necessitates
|
// fastcgi PoC earlier; but it's this single error report here that necessitates
|
||||||
@ -256,7 +277,9 @@ func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, logger *za
|
|||||||
// something we need to report to the client, but read errors are a problem on our
|
// something we need to report to the client, but read errors are a problem on our
|
||||||
// end for sure. so we need to decide what we want.)
|
// end for sure. so we need to decide what we want.)
|
||||||
// p.logf("copyBuffer: ReverseProxy read error during body copy: %v", rerr)
|
// p.logf("copyBuffer: ReverseProxy read error during body copy: %v", rerr)
|
||||||
h.logger.Error("reading from backend", zap.Error(rerr))
|
if c := logger.Check(zapcore.ErrorLevel, "reading from backend"); c != nil {
|
||||||
|
c.Write(zap.Error(rerr))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if nr > 0 {
|
if nr > 0 {
|
||||||
logger.Debug("writing to downstream")
|
logger.Debug("writing to downstream")
|
||||||
@ -264,10 +287,13 @@ func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte, logger *za
|
|||||||
if nw > 0 {
|
if nw > 0 {
|
||||||
written += int64(nw)
|
written += int64(nw)
|
||||||
}
|
}
|
||||||
logger.Debug("wrote to downstream",
|
if c := logger.Check(zapcore.DebugLevel, "wrote to downstream"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Int("written", nw),
|
zap.Int("written", nw),
|
||||||
zap.Int64("written_total", written),
|
zap.Int64("written_total", written),
|
||||||
zap.Error(werr))
|
zap.Error(werr),
|
||||||
|
)
|
||||||
|
}
|
||||||
if werr != nil {
|
if werr != nil {
|
||||||
return written, fmt.Errorf("writing: %w", werr)
|
return written, fmt.Errorf("writing: %w", werr)
|
||||||
}
|
}
|
||||||
@ -347,13 +373,17 @@ func (h *Handler) cleanupConnections() error {
|
|||||||
if len(h.connections) > 0 {
|
if len(h.connections) > 0 {
|
||||||
delay := time.Duration(h.StreamCloseDelay)
|
delay := time.Duration(h.StreamCloseDelay)
|
||||||
h.connectionsCloseTimer = time.AfterFunc(delay, func() {
|
h.connectionsCloseTimer = time.AfterFunc(delay, func() {
|
||||||
h.logger.Debug("closing streaming connections after delay",
|
if c := h.logger.Check(zapcore.DebugLevel, "closing streaming connections after delay"); c != nil {
|
||||||
zap.Duration("delay", delay))
|
c.Write(zap.Duration("delay", delay))
|
||||||
|
}
|
||||||
err := h.closeConnections()
|
err := h.closeConnections()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("failed to closed connections after delay",
|
if c := h.logger.Check(zapcore.ErrorLevel, "failed to closed connections after delay"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Duration("delay", delay))
|
zap.Duration("delay", delay),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -494,7 +524,9 @@ func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
|
|||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
n, err = m.dst.Write(p)
|
n, err = m.dst.Write(p)
|
||||||
m.logger.Debug("wrote bytes", zap.Int("n", n), zap.Error(err))
|
if c := m.logger.Check(zapcore.DebugLevel, "wrote bytes"); c != nil {
|
||||||
|
c.Write(zap.Int("n", n), zap.Error(err))
|
||||||
|
}
|
||||||
if m.latency < 0 {
|
if m.latency < 0 {
|
||||||
m.logger.Debug("flushing immediately")
|
m.logger.Debug("flushing immediately")
|
||||||
//nolint:errcheck
|
//nolint:errcheck
|
||||||
@ -510,7 +542,9 @@ func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
|
|||||||
} else {
|
} else {
|
||||||
m.t.Reset(m.latency)
|
m.t.Reset(m.latency)
|
||||||
}
|
}
|
||||||
m.logger.Debug("timer set for delayed flush", zap.Duration("duration", m.latency))
|
if c := m.logger.Check(zapcore.DebugLevel, "timer set for delayed flush"); c != nil {
|
||||||
|
c.Write(zap.Duration("duration", m.latency))
|
||||||
|
}
|
||||||
m.flushPending = true
|
m.flushPending = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
@ -136,10 +137,13 @@ func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
|
|||||||
return allNew(cached.upstreams), nil
|
return allNew(cached.upstreams), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
su.logger.Debug("refreshing SRV upstreams",
|
if c := su.logger.Check(zapcore.DebugLevel, "refreshing SRV upstreams"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("service", service),
|
zap.String("service", service),
|
||||||
zap.String("proto", proto),
|
zap.String("proto", proto),
|
||||||
zap.String("name", name))
|
zap.String("name", name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
_, records, err := su.resolver.LookupSRV(r.Context(), service, proto, name)
|
_, records, err := su.resolver.LookupSRV(r.Context(), service, proto, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -148,23 +152,30 @@ func (su SRVUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
|
|||||||
// only return an error if no records were also returned.
|
// only return an error if no records were also returned.
|
||||||
if len(records) == 0 {
|
if len(records) == 0 {
|
||||||
if su.GracePeriod > 0 {
|
if su.GracePeriod > 0 {
|
||||||
su.logger.Error("SRV lookup failed; using previously cached", zap.Error(err))
|
if c := su.logger.Check(zapcore.ErrorLevel, "SRV lookup failed; using previously cached"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
cached.freshness = time.Now().Add(time.Duration(su.GracePeriod) - time.Duration(su.Refresh))
|
cached.freshness = time.Now().Add(time.Duration(su.GracePeriod) - time.Duration(su.Refresh))
|
||||||
srvs[suAddr] = cached
|
srvs[suAddr] = cached
|
||||||
return allNew(cached.upstreams), nil
|
return allNew(cached.upstreams), nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
su.logger.Warn("SRV records filtered", zap.Error(err))
|
if c := su.logger.Check(zapcore.WarnLevel, "SRV records filtered"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
upstreams := make([]Upstream, len(records))
|
upstreams := make([]Upstream, len(records))
|
||||||
for i, rec := range records {
|
for i, rec := range records {
|
||||||
su.logger.Debug("discovered SRV record",
|
if c := su.logger.Check(zapcore.DebugLevel, "discovered SRV record"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("target", rec.Target),
|
zap.String("target", rec.Target),
|
||||||
zap.Uint16("port", rec.Port),
|
zap.Uint16("port", rec.Port),
|
||||||
zap.Uint16("priority", rec.Priority),
|
zap.Uint16("priority", rec.Priority),
|
||||||
zap.Uint16("weight", rec.Weight))
|
zap.Uint16("weight", rec.Weight),
|
||||||
|
)
|
||||||
|
}
|
||||||
addr := net.JoinHostPort(rec.Target, strconv.Itoa(int(rec.Port)))
|
addr := net.JoinHostPort(rec.Target, strconv.Itoa(int(rec.Port)))
|
||||||
upstreams[i] = Upstream{Dial: addr}
|
upstreams[i] = Upstream{Dial: addr}
|
||||||
}
|
}
|
||||||
@ -231,6 +242,19 @@ type IPVersions struct {
|
|||||||
IPv6 *bool `json:"ipv6,omitempty"`
|
IPv6 *bool `json:"ipv6,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveIpVersion(versions *IPVersions) string {
|
||||||
|
resolveIpv4 := versions == nil || (versions.IPv4 == nil && versions.IPv6 == nil) || (versions.IPv4 != nil && *versions.IPv4)
|
||||||
|
resolveIpv6 := versions == nil || (versions.IPv6 == nil && versions.IPv4 == nil) || (versions.IPv6 != nil && *versions.IPv6)
|
||||||
|
switch {
|
||||||
|
case resolveIpv4 && !resolveIpv6:
|
||||||
|
return "ip4"
|
||||||
|
case !resolveIpv4 && resolveIpv6:
|
||||||
|
return "ip6"
|
||||||
|
default:
|
||||||
|
return "ip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AUpstreams provides upstreams from A/AAAA lookups.
|
// AUpstreams provides upstreams from A/AAAA lookups.
|
||||||
// Results are cached and refreshed at the configured
|
// Results are cached and refreshed at the configured
|
||||||
// refresh interval.
|
// refresh interval.
|
||||||
@ -313,9 +337,6 @@ func (au *AUpstreams) Provision(ctx caddy.Context) error {
|
|||||||
func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
|
func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
|
||||||
resolveIpv4 := au.Versions == nil || au.Versions.IPv4 == nil || *au.Versions.IPv4
|
|
||||||
resolveIpv6 := au.Versions == nil || au.Versions.IPv6 == nil || *au.Versions.IPv6
|
|
||||||
|
|
||||||
// Map ipVersion early, so we can use it as part of the cache-key.
|
// Map ipVersion early, so we can use it as part of the cache-key.
|
||||||
// This should be fairly inexpensive and comes and the upside of
|
// This should be fairly inexpensive and comes and the upside of
|
||||||
// allowing the same dynamic upstream (name + port combination)
|
// allowing the same dynamic upstream (name + port combination)
|
||||||
@ -324,15 +345,7 @@ func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
|
|||||||
// It also forced a cache-miss if a previously cached dynamic
|
// It also forced a cache-miss if a previously cached dynamic
|
||||||
// upstream changes its ip version, e.g. after a config reload,
|
// upstream changes its ip version, e.g. after a config reload,
|
||||||
// while keeping the cache-invalidation as simple as it currently is.
|
// while keeping the cache-invalidation as simple as it currently is.
|
||||||
var ipVersion string
|
ipVersion := resolveIpVersion(au.Versions)
|
||||||
switch {
|
|
||||||
case resolveIpv4 && !resolveIpv6:
|
|
||||||
ipVersion = "ip4"
|
|
||||||
case !resolveIpv4 && resolveIpv6:
|
|
||||||
ipVersion = "ip6"
|
|
||||||
default:
|
|
||||||
ipVersion = "ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
auStr := repl.ReplaceAll(au.String()+ipVersion, "")
|
auStr := repl.ReplaceAll(au.String()+ipVersion, "")
|
||||||
|
|
||||||
@ -359,10 +372,13 @@ func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
|
|||||||
name := repl.ReplaceAll(au.Name, "")
|
name := repl.ReplaceAll(au.Name, "")
|
||||||
port := repl.ReplaceAll(au.Port, "")
|
port := repl.ReplaceAll(au.Port, "")
|
||||||
|
|
||||||
au.logger.Debug("refreshing A upstreams",
|
if c := au.logger.Check(zapcore.DebugLevel, "refreshing A upstreams"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("version", ipVersion),
|
zap.String("version", ipVersion),
|
||||||
zap.String("name", name),
|
zap.String("name", name),
|
||||||
zap.String("port", port))
|
zap.String("port", port),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ips, err := au.resolver.LookupIP(r.Context(), ipVersion, name)
|
ips, err := au.resolver.LookupIP(r.Context(), ipVersion, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -371,8 +387,9 @@ func (au AUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
|
|||||||
|
|
||||||
upstreams := make([]Upstream, len(ips))
|
upstreams := make([]Upstream, len(ips))
|
||||||
for i, ip := range ips {
|
for i, ip := range ips {
|
||||||
au.logger.Debug("discovered A record",
|
if c := au.logger.Check(zapcore.DebugLevel, "discovered A record"); c != nil {
|
||||||
zap.String("ip", ip.String()))
|
c.Write(zap.String("ip", ip.String()))
|
||||||
|
}
|
||||||
upstreams[i] = Upstream{
|
upstreams[i] = Upstream{
|
||||||
Dial: net.JoinHostPort(ip.String(), port),
|
Dial: net.JoinHostPort(ip.String(), port),
|
||||||
}
|
}
|
||||||
@ -465,11 +482,16 @@ func (mu MultiUpstreams) GetUpstreams(r *http.Request) ([]*Upstream, error) {
|
|||||||
|
|
||||||
up, err := src.GetUpstreams(r)
|
up, err := src.GetUpstreams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mu.logger.Error("upstream source returned error",
|
if c := mu.logger.Check(zapcore.ErrorLevel, "upstream source returned error"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.Int("source_idx", i),
|
zap.Int("source_idx", i),
|
||||||
zap.Error(err))
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
} else if len(up) == 0 {
|
} else if len(up) == 0 {
|
||||||
mu.logger.Warn("upstream source returned 0 upstreams", zap.Int("source_idx", i))
|
if c := mu.logger.Check(zapcore.WarnLevel, "upstream source returned 0 upstreams"); c != nil {
|
||||||
|
c.Write(zap.Int("source_idx", i))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
upstreams = append(upstreams, up...)
|
upstreams = append(upstreams, up...)
|
||||||
}
|
}
|
||||||
|
|||||||
56
modules/caddyhttp/reverseproxy/upstreams_test.go
Normal file
56
modules/caddyhttp/reverseproxy/upstreams_test.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package reverseproxy
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestResolveIpVersion(t *testing.T) {
|
||||||
|
falseBool := false
|
||||||
|
trueBool := true
|
||||||
|
tests := []struct {
|
||||||
|
Versions *IPVersions
|
||||||
|
expectedIpVersion string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{IPv4: &trueBool},
|
||||||
|
expectedIpVersion: "ip4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{IPv4: &falseBool},
|
||||||
|
expectedIpVersion: "ip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{IPv4: &trueBool, IPv6: &falseBool},
|
||||||
|
expectedIpVersion: "ip4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{IPv6: &trueBool},
|
||||||
|
expectedIpVersion: "ip6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{IPv6: &falseBool},
|
||||||
|
expectedIpVersion: "ip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{IPv6: &trueBool, IPv4: &falseBool},
|
||||||
|
expectedIpVersion: "ip6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{},
|
||||||
|
expectedIpVersion: "ip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{IPv4: &trueBool, IPv6: &trueBool},
|
||||||
|
expectedIpVersion: "ip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Versions: &IPVersions{IPv4: &falseBool, IPv6: &falseBool},
|
||||||
|
expectedIpVersion: "ip",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
ipVersion := resolveIpVersion(test.Versions)
|
||||||
|
if ipVersion != test.expectedIpVersion {
|
||||||
|
t.Errorf("resolveIpVersion(): Expected %s got %s", test.expectedIpVersion, ipVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -106,7 +106,7 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
|
|||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "strip_prefix":
|
case "strip_prefix":
|
||||||
if len(args) > 2 {
|
if len(args) != 2 {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
rewr.StripPathPrefix = args[1]
|
rewr.StripPathPrefix = args[1]
|
||||||
@ -115,7 +115,7 @@ func parseCaddyfileURI(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "strip_suffix":
|
case "strip_suffix":
|
||||||
if len(args) > 2 {
|
if len(args) != 2 {
|
||||||
return nil, h.ArgErr()
|
return nil, h.ArgErr()
|
||||||
}
|
}
|
||||||
rewr.StripPathSuffix = args[1]
|
rewr.StripPathSuffix = args[1]
|
||||||
|
|||||||
@ -131,15 +131,19 @@ func (rewr *Rewrite) Provision(ctx caddy.Context) error {
|
|||||||
|
|
||||||
func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
const message = "rewrote request"
|
||||||
|
|
||||||
logger := rewr.logger.With(
|
c := rewr.logger.Check(zap.DebugLevel, message)
|
||||||
zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}),
|
if c == nil {
|
||||||
)
|
rewr.Rewrite(r, repl)
|
||||||
|
return next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
changed := rewr.Rewrite(r, repl)
|
changed := rewr.Rewrite(r, repl)
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
logger.Debug("rewrote request",
|
c.Write(
|
||||||
|
zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}),
|
||||||
zap.String("method", r.Method),
|
zap.String("method", r.Method),
|
||||||
zap.String("uri", r.RequestURI),
|
zap.String("uri", r.RequestURI),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -159,6 +159,9 @@ func (r *Route) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error {
|
|||||||
r.Handlers = append(r.Handlers, handler.(MiddlewareHandler))
|
r.Handlers = append(r.Handlers, handler.(MiddlewareHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make ProvisionHandlers idempotent by clearing the middleware field
|
||||||
|
r.middleware = []Middleware{}
|
||||||
|
|
||||||
// pre-compile the middleware handler chain
|
// pre-compile the middleware handler chain
|
||||||
for _, midhandler := range r.Handlers {
|
for _, midhandler := range r.Handlers {
|
||||||
r.middleware = append(r.middleware, wrapMiddleware(ctx, midhandler, metrics))
|
r.middleware = append(r.middleware, wrapMiddleware(ctx, midhandler, metrics))
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -32,6 +33,7 @@ import (
|
|||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
|
"github.com/quic-go/quic-go/qlog"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
@ -218,6 +220,10 @@ type Server struct {
|
|||||||
// Default: `[h1 h2 h3]`
|
// Default: `[h1 h2 h3]`
|
||||||
Protocols []string `json:"protocols,omitempty"`
|
Protocols []string `json:"protocols,omitempty"`
|
||||||
|
|
||||||
|
// ListenProtocols overrides Protocols for each parallel address in Listen.
|
||||||
|
// A nil value or element indicates that Protocols will be used instead.
|
||||||
|
ListenProtocols [][]string `json:"listen_protocols,omitempty"`
|
||||||
|
|
||||||
// If set, metrics observations will be enabled.
|
// If set, metrics observations will be enabled.
|
||||||
// This setting is EXPERIMENTAL and subject to change.
|
// This setting is EXPERIMENTAL and subject to change.
|
||||||
Metrics *Metrics `json:"metrics,omitempty"`
|
Metrics *Metrics `json:"metrics,omitempty"`
|
||||||
@ -275,7 +281,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if r.ProtoMajor < 3 {
|
if r.ProtoMajor < 3 {
|
||||||
err := s.h3server.SetQUICHeaders(w.Header())
|
err := s.h3server.SetQUICHeaders(w.Header())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("setting HTTP/3 Alt-Svc header", zap.Error(err))
|
if c := s.logger.Check(zapcore.ErrorLevel, "setting HTTP/3 Alt-Svc header"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,9 +291,12 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// reject very long methods; probably a mistake or an attack
|
// reject very long methods; probably a mistake or an attack
|
||||||
if len(r.Method) > 32 {
|
if len(r.Method) > 32 {
|
||||||
if s.shouldLogRequest(r) {
|
if s.shouldLogRequest(r) {
|
||||||
s.accessLogger.Debug("rejecting request with long method",
|
if c := s.accessLogger.Check(zapcore.DebugLevel, "rejecting request with long method"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("method_trunc", r.Method[:32]),
|
zap.String("method_trunc", r.Method[:32]),
|
||||||
zap.String("remote_addr", r.RemoteAddr))
|
zap.String("remote_addr", r.RemoteAddr),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
@ -300,20 +311,24 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
//nolint:bodyclose
|
//nolint:bodyclose
|
||||||
err := http.NewResponseController(w).EnableFullDuplex()
|
err := http.NewResponseController(w).EnableFullDuplex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("failed to enable full duplex", zap.Error(err))
|
if c := s.logger.Check(zapcore.WarnLevel, "failed to enable full duplex"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// encode the request for logging purposes before
|
// clone the request for logging purposes before
|
||||||
// it enters any handler chain; this is necessary
|
// it enters any handler chain; this is necessary
|
||||||
// to capture the original request in case it gets
|
// to capture the original request in case it gets
|
||||||
// modified during handling
|
// modified during handling
|
||||||
|
// cloning the request and using .WithLazy is considerably faster
|
||||||
|
// than using .With, which will JSON encode the request immediately
|
||||||
shouldLogCredentials := s.Logs != nil && s.Logs.ShouldLogCredentials
|
shouldLogCredentials := s.Logs != nil && s.Logs.ShouldLogCredentials
|
||||||
loggableReq := zap.Object("request", LoggableHTTPRequest{
|
loggableReq := zap.Object("request", LoggableHTTPRequest{
|
||||||
Request: r,
|
Request: r.Clone(r.Context()),
|
||||||
ShouldLogCredentials: shouldLogCredentials,
|
ShouldLogCredentials: shouldLogCredentials,
|
||||||
})
|
})
|
||||||
errLog := s.errorLogger.With(loggableReq)
|
errLog := s.errorLogger.WithLazy(loggableReq)
|
||||||
|
|
||||||
var duration time.Duration
|
var duration time.Duration
|
||||||
|
|
||||||
@ -379,6 +394,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// add HTTP error information to request context
|
// add HTTP error information to request context
|
||||||
r = s.Errors.WithError(r, err)
|
r = s.Errors.WithError(r, err)
|
||||||
|
|
||||||
|
var fields []zapcore.Field
|
||||||
if s.Errors != nil && len(s.Errors.Routes) > 0 {
|
if s.Errors != nil && len(s.Errors.Routes) > 0 {
|
||||||
// execute user-defined error handling route
|
// execute user-defined error handling route
|
||||||
err2 := s.errorHandlerChain.ServeHTTP(w, r)
|
err2 := s.errorHandlerChain.ServeHTTP(w, r)
|
||||||
@ -386,17 +402,28 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// user's error route handled the error response
|
// user's error route handled the error response
|
||||||
// successfully, so now just log the error
|
// successfully, so now just log the error
|
||||||
for _, logger := range errLoggers {
|
for _, logger := range errLoggers {
|
||||||
logger.Debug(errMsg, errFields...)
|
if c := logger.Check(zapcore.DebugLevel, errMsg); c != nil {
|
||||||
|
if fields == nil {
|
||||||
|
fields = errFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Write(fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// well... this is awkward
|
// well... this is awkward
|
||||||
errFields = append([]zapcore.Field{
|
for _, logger := range errLoggers {
|
||||||
|
if c := logger.Check(zapcore.ErrorLevel, "error handling handler error"); c != nil {
|
||||||
|
if fields == nil {
|
||||||
|
fields = errFields()
|
||||||
|
fields = append([]zapcore.Field{
|
||||||
zap.String("error", err2.Error()),
|
zap.String("error", err2.Error()),
|
||||||
zap.Namespace("first_error"),
|
zap.Namespace("first_error"),
|
||||||
zap.String("msg", errMsg),
|
zap.String("msg", errMsg),
|
||||||
}, errFields...)
|
}, fields...)
|
||||||
for _, logger := range errLoggers {
|
}
|
||||||
logger.Error("error handling handler error", errFields...)
|
c.Write(fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if handlerErr, ok := err.(HandlerError); ok {
|
if handlerErr, ok := err.(HandlerError); ok {
|
||||||
w.WriteHeader(handlerErr.StatusCode)
|
w.WriteHeader(handlerErr.StatusCode)
|
||||||
@ -405,11 +432,17 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, logger := range errLoggers {
|
logLevel := zapcore.DebugLevel
|
||||||
if errStatus >= 500 {
|
if errStatus >= 500 {
|
||||||
logger.Error(errMsg, errFields...)
|
logLevel = zapcore.ErrorLevel
|
||||||
} else {
|
}
|
||||||
logger.Debug(errMsg, errFields...)
|
|
||||||
|
for _, logger := range errLoggers {
|
||||||
|
if c := logger.Check(logLevel, errMsg); c != nil {
|
||||||
|
if fields == nil {
|
||||||
|
fields = errFields()
|
||||||
|
}
|
||||||
|
c.Write(fields...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.WriteHeader(errStatus)
|
w.WriteHeader(errStatus)
|
||||||
@ -517,12 +550,9 @@ func (s *Server) hasListenerAddress(fullAddr string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) hasTLSClientAuth() bool {
|
func (s *Server) hasTLSClientAuth() bool {
|
||||||
for _, cp := range s.TLSConnPolicies {
|
return slices.ContainsFunc(s.TLSConnPolicies, func(cp *caddytls.ConnectionPolicy) bool {
|
||||||
if cp.ClientAuthentication != nil && cp.ClientAuthentication.Active() {
|
return cp.ClientAuthentication != nil && cp.ClientAuthentication.Active()
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findLastRouteWithHostMatcher returns the index of the last route
|
// findLastRouteWithHostMatcher returns the index of the last route
|
||||||
@ -571,7 +601,11 @@ func (s *Server) findLastRouteWithHostMatcher() int {
|
|||||||
// not already done, and then uses that server to serve HTTP/3 over
|
// not already done, and then uses that server to serve HTTP/3 over
|
||||||
// the listener, with Server s as the handler.
|
// the listener, with Server s as the handler.
|
||||||
func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error {
|
func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error {
|
||||||
addr.Network = getHTTP3Network(addr.Network)
|
h3net, err := getHTTP3Network(addr.Network)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
|
||||||
|
}
|
||||||
|
addr.Network = h3net
|
||||||
h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg)
|
h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
|
return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
|
||||||
@ -598,10 +632,11 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error
|
|||||||
}),
|
}),
|
||||||
TLSConfig: tlsCfg,
|
TLSConfig: tlsCfg,
|
||||||
MaxHeaderBytes: s.MaxHeaderBytes,
|
MaxHeaderBytes: s.MaxHeaderBytes,
|
||||||
// TODO: remove this config when draft versions are no longer supported (we have no need to support drafts)
|
|
||||||
QUICConfig: &quic.Config{
|
QUICConfig: &quic.Config{
|
||||||
Versions: []quic.Version{quic.Version1, quic.Version2},
|
Versions: []quic.Version{quic.Version1, quic.Version2},
|
||||||
|
Tracer: qlog.DefaultConnectionTracer,
|
||||||
},
|
},
|
||||||
|
IdleTimeout: time.Duration(s.IdleTimeout),
|
||||||
ConnContext: func(ctx context.Context, c quic.Connection) context.Context {
|
ConnContext: func(ctx context.Context, c quic.Connection) context.Context {
|
||||||
return context.WithValue(ctx, quicConnCtxKey, c)
|
return context.WithValue(ctx, quicConnCtxKey, c)
|
||||||
},
|
},
|
||||||
@ -745,7 +780,9 @@ func (s *Server) logTrace(mh MiddlewareHandler) {
|
|||||||
if s.Logs == nil || !s.Logs.Trace {
|
if s.Logs == nil || !s.Logs.Trace {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.traceLogger.Debug(caddy.GetModuleName(mh), zap.Any("module", mh))
|
if c := s.traceLogger.Check(zapcore.DebugLevel, caddy.GetModuleName(mh)); c != nil {
|
||||||
|
c.Write(zap.Any("module", mh))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// logRequest logs the request to access logs, unless skipped.
|
// logRequest logs the request to access logs, unless skipped.
|
||||||
@ -758,11 +795,37 @@ func (s *Server) logRequest(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repl.Set("http.response.status", wrec.Status()) // will be 0 if no response is written by us (Go will write 200 to client)
|
status := wrec.Status()
|
||||||
repl.Set("http.response.size", wrec.Size())
|
size := wrec.Size()
|
||||||
|
|
||||||
|
repl.Set("http.response.status", status) // will be 0 if no response is written by us (Go will write 200 to client)
|
||||||
|
repl.Set("http.response.size", size)
|
||||||
repl.Set("http.response.duration", duration)
|
repl.Set("http.response.duration", duration)
|
||||||
repl.Set("http.response.duration_ms", duration.Seconds()*1e3) // multiply seconds to preserve decimal (see #4666)
|
repl.Set("http.response.duration_ms", duration.Seconds()*1e3) // multiply seconds to preserve decimal (see #4666)
|
||||||
|
|
||||||
|
loggers := []*zap.Logger{accLog}
|
||||||
|
if s.Logs != nil {
|
||||||
|
loggers = s.Logs.wrapLogger(accLog, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "handled request"
|
||||||
|
if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop {
|
||||||
|
message = "NOP"
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevel := zapcore.InfoLevel
|
||||||
|
if status >= 500 {
|
||||||
|
logLevel = zapcore.ErrorLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields []zapcore.Field
|
||||||
|
for _, logger := range loggers {
|
||||||
|
c := logger.Check(logLevel, message)
|
||||||
|
if c == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fields == nil {
|
||||||
userID, _ := repl.GetString("http.auth.user.id")
|
userID, _ := repl.GetString("http.auth.user.id")
|
||||||
|
|
||||||
reqBodyLength := 0
|
reqBodyLength := 0
|
||||||
@ -773,45 +836,41 @@ func (s *Server) logRequest(
|
|||||||
extra := r.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields)
|
extra := r.Context().Value(ExtraLogFieldsCtxKey).(*ExtraLogFields)
|
||||||
|
|
||||||
fieldCount := 6
|
fieldCount := 6
|
||||||
fields := make([]zapcore.Field, 0, fieldCount+len(extra.fields))
|
fields = make([]zapcore.Field, 0, fieldCount+len(extra.fields))
|
||||||
fields = append(fields,
|
fields = append(fields,
|
||||||
zap.Int("bytes_read", reqBodyLength),
|
zap.Int("bytes_read", reqBodyLength),
|
||||||
zap.String("user_id", userID),
|
zap.String("user_id", userID),
|
||||||
zap.Duration("duration", *duration),
|
zap.Duration("duration", *duration),
|
||||||
zap.Int("size", wrec.Size()),
|
zap.Int("size", size),
|
||||||
zap.Int("status", wrec.Status()),
|
zap.Int("status", status),
|
||||||
zap.Object("resp_headers", LoggableHTTPHeader{
|
zap.Object("resp_headers", LoggableHTTPHeader{
|
||||||
Header: wrec.Header(),
|
Header: wrec.Header(),
|
||||||
ShouldLogCredentials: shouldLogCredentials,
|
ShouldLogCredentials: shouldLogCredentials,
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
fields = append(fields, extra.fields...)
|
fields = append(fields, extra.fields...)
|
||||||
|
|
||||||
loggers := []*zap.Logger{accLog}
|
|
||||||
if s.Logs != nil {
|
|
||||||
loggers = s.Logs.wrapLogger(accLog, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapping may return multiple loggers, so we log to all of them
|
c.Write(fields...)
|
||||||
for _, logger := range loggers {
|
|
||||||
logAtLevel := logger.Info
|
|
||||||
if wrec.Status() >= 500 {
|
|
||||||
logAtLevel = logger.Error
|
|
||||||
}
|
|
||||||
message := "handled request"
|
|
||||||
if nop, ok := GetVar(r.Context(), "unhandled").(bool); ok && nop {
|
|
||||||
message = "NOP"
|
|
||||||
}
|
|
||||||
logAtLevel(message, fields...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// protocol returns true if the protocol proto is configured/enabled.
|
// protocol returns true if the protocol proto is configured/enabled.
|
||||||
func (s *Server) protocol(proto string) bool {
|
func (s *Server) protocol(proto string) bool {
|
||||||
for _, p := range s.Protocols {
|
if s.ListenProtocols == nil {
|
||||||
if p == proto {
|
if slices.Contains(s.Protocols, proto) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, lnProtocols := range s.ListenProtocols {
|
||||||
|
for _, lnProtocol := range lnProtocols {
|
||||||
|
if lnProtocol == "" && slices.Contains(s.Protocols, proto) || lnProtocol == proto {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -917,12 +976,9 @@ func determineTrustedProxy(r *http.Request, s *Server) (bool, string) {
|
|||||||
// isTrustedClientIP returns true if the given IP address is
|
// isTrustedClientIP returns true if the given IP address is
|
||||||
// in the list of trusted IP ranges.
|
// in the list of trusted IP ranges.
|
||||||
func isTrustedClientIP(ipAddr netip.Addr, trusted []netip.Prefix) bool {
|
func isTrustedClientIP(ipAddr netip.Addr, trusted []netip.Prefix) bool {
|
||||||
for _, ipRange := range trusted {
|
return slices.ContainsFunc(trusted, func(prefix netip.Prefix) bool {
|
||||||
if ipRange.Contains(ipAddr) {
|
return prefix.Contains(ipAddr)
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// trustedRealClientIP finds the client IP from the request assuming it is
|
// trustedRealClientIP finds the client IP from the request assuming it is
|
||||||
@ -1055,9 +1111,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var networkTypesHTTP3 = map[string]string{
|
var networkTypesHTTP3 = map[string]string{
|
||||||
"unix": "unixgram",
|
"unixgram": "unixgram",
|
||||||
|
"udp": "udp",
|
||||||
|
"udp4": "udp4",
|
||||||
|
"udp6": "udp6",
|
||||||
|
"tcp": "udp",
|
||||||
"tcp4": "udp4",
|
"tcp4": "udp4",
|
||||||
"tcp6": "udp6",
|
"tcp6": "udp6",
|
||||||
|
"fdgram": "fdgram",
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterNetworkHTTP3 registers a mapping from non-HTTP/3 network to HTTP/3
|
// RegisterNetworkHTTP3 registers a mapping from non-HTTP/3 network to HTTP/3
|
||||||
@ -1072,11 +1133,10 @@ func RegisterNetworkHTTP3(originalNetwork, h3Network string) {
|
|||||||
networkTypesHTTP3[originalNetwork] = h3Network
|
networkTypesHTTP3[originalNetwork] = h3Network
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHTTP3Network(originalNetwork string) string {
|
func getHTTP3Network(originalNetwork string) (string, error) {
|
||||||
h3Network, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)]
|
h3Network, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)]
|
||||||
if !ok {
|
if !ok {
|
||||||
// TODO: Maybe a better default is to not enable HTTP/3 if we do not know the network?
|
return "", fmt.Errorf("network '%s' cannot handle HTTP/3 connections", originalNetwork)
|
||||||
return "udp"
|
|
||||||
}
|
}
|
||||||
return h3Network
|
return h3Network, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,6 +121,29 @@ func BenchmarkServer_LogRequest(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkServer_LogRequest_NopLogger(b *testing.B) {
|
||||||
|
s := &Server{}
|
||||||
|
|
||||||
|
extra := new(ExtraLogFields)
|
||||||
|
ctx := context.WithValue(context.Background(), ExtraLogFieldsCtxKey, extra)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
wrec := NewResponseRecorder(rec, nil, nil)
|
||||||
|
|
||||||
|
duration := 50 * time.Millisecond
|
||||||
|
repl := NewTestReplacer(req)
|
||||||
|
bodyReader := &lengthReader{Source: req.Body}
|
||||||
|
|
||||||
|
accLog := zap.NewNop()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s.logRequest(accLog, req, wrec, &duration, repl, bodyReader, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkServer_LogRequest_WithTraceID(b *testing.B) {
|
func BenchmarkServer_LogRequest_WithTraceID(b *testing.B) {
|
||||||
s := &Server{}
|
s := &Server{}
|
||||||
|
|
||||||
|
|||||||
@ -105,8 +105,7 @@ func (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler
|
|||||||
}
|
}
|
||||||
statusCode = intVal
|
statusCode = intVal
|
||||||
}
|
}
|
||||||
|
return Error(statusCode, fmt.Errorf("%s", repl.ReplaceKnown(e.Error, "")))
|
||||||
return Error(statusCode, fmt.Errorf("%s", e.Error))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guard
|
// Interface guard
|
||||||
|
|||||||
@ -387,7 +387,7 @@ func cmdRespond(fl caddycmd.Flags) (int, error) {
|
|||||||
return caddy.ExitCodeFailedStartup, err
|
return caddy.ExitCodeFailedStartup, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !listenAddr.IsUnixNetwork() {
|
if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() {
|
||||||
listenAddrs := make([]string, 0, listenAddr.PortRangeSize())
|
listenAddrs := make([]string, 0, listenAddr.PortRangeSize())
|
||||||
for offset := uint(0); offset < listenAddr.PortRangeSize(); offset++ {
|
for offset := uint(0); offset < listenAddr.PortRangeSize(); offset++ {
|
||||||
listenAddrs = append(listenAddrs, listenAddr.JoinHostPort(offset))
|
listenAddrs = append(listenAddrs, listenAddr.JoinHostPort(offset))
|
||||||
|
|||||||
@ -40,6 +40,7 @@ func extractFrontMatter(input string) (map[string]any, string, error) {
|
|||||||
if firstLine == fmType.FenceOpen {
|
if firstLine == fmType.FenceOpen {
|
||||||
closingFence = fmType.FenceClose
|
closingFence = fmType.FenceClose
|
||||||
fmParser = fmType.ParseFunc
|
fmParser = fmType.ParseFunc
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -81,6 +81,12 @@ func init() {
|
|||||||
// {{placeholder "http.error.status_code"}}
|
// {{placeholder "http.error.status_code"}}
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
|
// As a shortcut, `ph` is an alias for `placeholder`.
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// {{ph "http.request.method"}}
|
||||||
|
// ```
|
||||||
|
//
|
||||||
// ##### `.Host`
|
// ##### `.Host`
|
||||||
//
|
//
|
||||||
// Returns the hostname portion (no port) of the Host header of the HTTP request.
|
// Returns the hostname portion (no port) of the Host header of the HTTP request.
|
||||||
|
|||||||
@ -88,6 +88,7 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
|
|||||||
"fileStat": c.funcFileStat,
|
"fileStat": c.funcFileStat,
|
||||||
"env": c.funcEnv,
|
"env": c.funcEnv,
|
||||||
"placeholder": c.funcPlaceholder,
|
"placeholder": c.funcPlaceholder,
|
||||||
|
"ph": c.funcPlaceholder, // shortcut
|
||||||
"fileExists": c.funcFileExists,
|
"fileExists": c.funcFileExists,
|
||||||
"httpError": c.funcHTTPError,
|
"httpError": c.funcHTTPError,
|
||||||
"humanize": c.funcHumanize,
|
"humanize": c.funcHumanize,
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// globalTracerProvider stores global tracer provider and is responsible for graceful shutdown when nobody is using it.
|
// globalTracerProvider stores global tracer provider and is responsible for graceful shutdown when nobody is using it.
|
||||||
@ -47,7 +48,9 @@ func (t *tracerProvider) cleanupTracerProvider(logger *zap.Logger) error {
|
|||||||
if t.tracerProvider != nil {
|
if t.tracerProvider != nil {
|
||||||
// tracerProvider.ForceFlush SHOULD be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#forceflush
|
// tracerProvider.ForceFlush SHOULD be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#forceflush
|
||||||
if err := t.tracerProvider.ForceFlush(context.Background()); err != nil {
|
if err := t.tracerProvider.ForceFlush(context.Background()); err != nil {
|
||||||
logger.Error("forcing flush", zap.Error(err))
|
if c := logger.Check(zapcore.ErrorLevel, "forcing flush"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tracerProvider.Shutdown MUST be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#shutdown
|
// tracerProvider.Shutdown MUST be invoked according to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#shutdown
|
||||||
|
|||||||
@ -18,8 +18,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/cel-go/cel"
|
||||||
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
)
|
)
|
||||||
@ -203,6 +207,28 @@ func (m VarsMatcher) Match(r *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
// expression matchers.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// expression vars({'{magic_number}': ['3', '5']})
|
||||||
|
// expression vars({'{foo}': 'single_value'})
|
||||||
|
func (VarsMatcher) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||||
|
return CELMatcherImpl(
|
||||||
|
"vars",
|
||||||
|
"vars_matcher_request_map",
|
||||||
|
[]*cel.Type{CELTypeJSON},
|
||||||
|
func(data ref.Val) (RequestMatcher, error) {
|
||||||
|
mapStrListStr, err := CELValueToMapStrList(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return VarsMatcher(mapStrListStr), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// MatchVarsRE matches the value of the context variables by a given regular expression.
|
// MatchVarsRE matches the value of the context variables by a given regular expression.
|
||||||
//
|
//
|
||||||
// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
|
// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
|
||||||
@ -302,6 +328,69 @@ func (m MatchVarsRE) Match(r *http.Request) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CELLibrary produces options that expose this matcher for use in CEL
|
||||||
|
// expression matchers.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// expression vars_regexp('foo', '{magic_number}', '[0-9]+')
|
||||||
|
// expression vars_regexp('{magic_number}', '[0-9]+')
|
||||||
|
func (MatchVarsRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||||
|
unnamedPattern, err := CELMatcherImpl(
|
||||||
|
"vars_regexp",
|
||||||
|
"vars_regexp_request_string_string",
|
||||||
|
[]*cel.Type{cel.StringType, cel.StringType},
|
||||||
|
func(data ref.Val) (RequestMatcher, error) {
|
||||||
|
refStringList := reflect.TypeOf([]string{})
|
||||||
|
params, err := data.ConvertToNative(refStringList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
strParams := params.([]string)
|
||||||
|
matcher := MatchVarsRE{}
|
||||||
|
matcher[strParams[0]] = &MatchRegexp{
|
||||||
|
Pattern: strParams[1],
|
||||||
|
Name: ctx.Value(MatcherNameCtxKey).(string),
|
||||||
|
}
|
||||||
|
err = matcher.Provision(ctx)
|
||||||
|
return matcher, err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
namedPattern, err := CELMatcherImpl(
|
||||||
|
"vars_regexp",
|
||||||
|
"vars_regexp_request_string_string_string",
|
||||||
|
[]*cel.Type{cel.StringType, cel.StringType, cel.StringType},
|
||||||
|
func(data ref.Val) (RequestMatcher, error) {
|
||||||
|
refStringList := reflect.TypeOf([]string{})
|
||||||
|
params, err := data.ConvertToNative(refStringList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
strParams := params.([]string)
|
||||||
|
name := strParams[0]
|
||||||
|
if name == "" {
|
||||||
|
name = ctx.Value(MatcherNameCtxKey).(string)
|
||||||
|
}
|
||||||
|
matcher := MatchVarsRE{}
|
||||||
|
matcher[strParams[1]] = &MatchRegexp{
|
||||||
|
Pattern: strParams[2],
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
err = matcher.Provision(ctx)
|
||||||
|
return matcher, err
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
envOpts := append(unnamedPattern.CompileOptions(), namedPattern.CompileOptions()...)
|
||||||
|
prgOpts := append(unnamedPattern.ProgramOptions(), namedPattern.ProgramOptions()...)
|
||||||
|
return NewMatcherCELLibrary(envOpts, prgOpts), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validate validates m's regular expressions.
|
// Validate validates m's regular expressions.
|
||||||
func (m MatchVarsRE) Validate() error {
|
func (m MatchVarsRE) Validate() error {
|
||||||
for _, rm := range m {
|
for _, rm := range m {
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/smallstep/certificates/db"
|
"github.com/smallstep/certificates/db"
|
||||||
"github.com/smallstep/nosql"
|
"github.com/smallstep/nosql"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||||
@ -243,10 +244,14 @@ func (ash Handler) Cleanup() error {
|
|||||||
key := ash.getDatabaseKey()
|
key := ash.getDatabaseKey()
|
||||||
deleted, err := databasePool.Delete(key)
|
deleted, err := databasePool.Delete(key)
|
||||||
if deleted {
|
if deleted {
|
||||||
ash.logger.Debug("unloading unused CA database", zap.String("db_key", key))
|
if c := ash.logger.Check(zapcore.DebugLevel, "unloading unused CA database"); c != nil {
|
||||||
|
c.Write(zap.String("db_key", key))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ash.logger.Error("closing CA database", zap.String("db_key", key), zap.Error(err))
|
if c := ash.logger.Check(zapcore.ErrorLevel, "closing CA database"); c != nil {
|
||||||
|
c.Write(zap.String("db_key", key), zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -271,7 +276,9 @@ func (ash Handler) openDatabase() (*db.AuthDB, error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if loaded {
|
if loaded {
|
||||||
ash.logger.Debug("loaded preexisting CA database", zap.String("db_key", key))
|
if c := ash.logger.Check(zapcore.DebugLevel, "loaded preexisting CA database"); c != nil {
|
||||||
|
c.Write(zap.String("db_key", key))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return database.(databaseCloser).DB, err
|
return database.(databaseCloser).DB, err
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/caddyserver/zerossl"
|
"github.com/caddyserver/zerossl"
|
||||||
"github.com/mholt/acmez/v2/acme"
|
"github.com/mholt/acmez/v2/acme"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
@ -321,7 +322,9 @@ func (iss *ACMEIssuer) generateZeroSSLEABCredentials(ctx context.Context, acct a
|
|||||||
return nil, acct, fmt.Errorf("failed getting EAB credentials: HTTP %d", resp.StatusCode)
|
return nil, acct, fmt.Errorf("failed getting EAB credentials: HTTP %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
iss.logger.Info("generated EAB credentials", zap.String("key_id", result.EABKID))
|
if c := iss.logger.Check(zapcore.InfoLevel, "generated EAB credentials"); c != nil {
|
||||||
|
c.Write(zap.String("key_id", result.EABKID))
|
||||||
|
}
|
||||||
|
|
||||||
return &acme.EAB{
|
return &acme.EAB{
|
||||||
KeyID: result.EABKID,
|
KeyID: result.EABKID,
|
||||||
|
|||||||
@ -21,11 +21,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/mholt/acmez/v2"
|
"github.com/mholt/acmez/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
@ -292,21 +294,30 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
|
|||||||
remoteIP, _, _ = net.SplitHostPort(remote.String())
|
remoteIP, _, _ = net.SplitHostPort(remote.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsApp.logger.Debug("asking for permission for on-demand certificate",
|
if c := tlsApp.logger.Check(zapcore.DebugLevel, "asking for permission for on-demand certificate"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("remote_ip", remoteIP),
|
zap.String("remote_ip", remoteIP),
|
||||||
zap.String("domain", name))
|
zap.String("domain", name),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ask the permission module if this cert is allowed
|
// ask the permission module if this cert is allowed
|
||||||
if err := tlsApp.Automation.OnDemand.permission.CertificateAllowed(ctx, name); err != nil {
|
if err := tlsApp.Automation.OnDemand.permission.CertificateAllowed(ctx, name); err != nil {
|
||||||
// distinguish true errors from denials, because it's important to elevate actual errors
|
// distinguish true errors from denials, because it's important to elevate actual errors
|
||||||
if errors.Is(err, ErrPermissionDenied) {
|
if errors.Is(err, ErrPermissionDenied) {
|
||||||
tlsApp.logger.Debug("on-demand certificate issuance denied",
|
if c := tlsApp.logger.Check(zapcore.DebugLevel, "on-demand certificate issuance denied"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("domain", name),
|
zap.String("domain", name),
|
||||||
zap.Error(err))
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tlsApp.logger.Error("failed to get permission for on-demand certificate",
|
if c := tlsApp.logger.Check(zapcore.ErrorLevel, "failed to get permission for on-demand certificate"); c != nil {
|
||||||
|
c.Write(
|
||||||
zap.String("domain", name),
|
zap.String("domain", name),
|
||||||
zap.Error(err))
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -363,12 +374,9 @@ func (ap *AutomationPolicy) Subjects() []string {
|
|||||||
|
|
||||||
// AllInternalSubjects returns true if all the subjects on this policy are internal.
|
// AllInternalSubjects returns true if all the subjects on this policy are internal.
|
||||||
func (ap *AutomationPolicy) AllInternalSubjects() bool {
|
func (ap *AutomationPolicy) AllInternalSubjects() bool {
|
||||||
for _, subj := range ap.subjects {
|
return !slices.ContainsFunc(ap.subjects, func(s string) bool {
|
||||||
if !certmagic.SubjectIsInternal(subj) {
|
return !certmagic.SubjectIsInternal(s)
|
||||||
return false
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ap *AutomationPolicy) onlyInternalIssuer() bool {
|
func (ap *AutomationPolicy) onlyInternalIssuer() bool {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/tailscale/tscert"
|
"github.com/tailscale/tscert"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
@ -46,7 +47,9 @@ func (ts Tailscale) GetCertificate(ctx context.Context, hello *tls.ClientHelloIn
|
|||||||
return nil, nil // pass-thru: Tailscale can't offer a cert for this name
|
return nil, nil // pass-thru: Tailscale can't offer a cert for this name
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ts.logger.Warn("could not get status; will try to get certificate anyway", zap.Error(err))
|
if c := ts.logger.Check(zapcore.WarnLevel, "could not get status; will try to get certificate anyway"); c != nil {
|
||||||
|
c.Write(zap.Error(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tscert.GetCertificateWithContext(ctx, hello)
|
return tscert.GetCertificateWithContext(ctx, hello)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,8 +20,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CustomCertSelectionPolicy represents a policy for selecting the certificate
|
// CustomCertSelectionPolicy represents a policy for selecting the certificate
|
||||||
@ -70,15 +73,9 @@ nextChoice:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(p.SubjectOrganization) > 0 {
|
if len(p.SubjectOrganization) > 0 {
|
||||||
var found bool
|
found := slices.ContainsFunc(p.SubjectOrganization, func(s string) bool {
|
||||||
for _, subjOrg := range p.SubjectOrganization {
|
return slices.Contains(cert.Leaf.Subject.Organization, s)
|
||||||
for _, org := range cert.Leaf.Subject.Organization {
|
})
|
||||||
if subjOrg == org {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -122,6 +119,79 @@ nextChoice:
|
|||||||
return certmagic.DefaultCertificateSelector(hello, viable)
|
return certmagic.DefaultCertificateSelector(hello, viable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the CustomCertSelectionPolicy from Caddyfile tokens. Syntax:
|
||||||
|
//
|
||||||
|
// cert_selection {
|
||||||
|
// all_tags <values...>
|
||||||
|
// any_tag <values...>
|
||||||
|
// public_key_algorithm <dsa|ecdsa|rsa>
|
||||||
|
// serial_number <big_integers...>
|
||||||
|
// subject_organization <values...>
|
||||||
|
// }
|
||||||
|
func (p *CustomCertSelectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
_, wrapper := d.Next(), d.Val() // consume wrapper name
|
||||||
|
|
||||||
|
// No same-line options are supported
|
||||||
|
if d.CountRemainingArgs() > 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasPublicKeyAlgorithm bool
|
||||||
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
|
optionName := d.Val()
|
||||||
|
switch optionName {
|
||||||
|
case "all_tags":
|
||||||
|
if d.CountRemainingArgs() == 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
p.AllTags = append(p.AllTags, d.RemainingArgs()...)
|
||||||
|
case "any_tag":
|
||||||
|
if d.CountRemainingArgs() == 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
p.AnyTag = append(p.AnyTag, d.RemainingArgs()...)
|
||||||
|
case "public_key_algorithm":
|
||||||
|
if hasPublicKeyAlgorithm {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
if d.CountRemainingArgs() != 1 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
d.NextArg()
|
||||||
|
if err := p.PublicKeyAlgorithm.UnmarshalJSON([]byte(d.Val())); err != nil {
|
||||||
|
return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
|
||||||
|
}
|
||||||
|
hasPublicKeyAlgorithm = true
|
||||||
|
case "serial_number":
|
||||||
|
if d.CountRemainingArgs() == 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
for d.NextArg() {
|
||||||
|
val, bi := d.Val(), bigInt{}
|
||||||
|
_, ok := bi.SetString(val, 10)
|
||||||
|
if !ok {
|
||||||
|
return d.Errf("parsing %s option '%s': invalid big.int value %s", wrapper, optionName, val)
|
||||||
|
}
|
||||||
|
p.SerialNumber = append(p.SerialNumber, bi)
|
||||||
|
}
|
||||||
|
case "subject_organization":
|
||||||
|
if d.CountRemainingArgs() == 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
p.SubjectOrganization = append(p.SubjectOrganization, d.RemainingArgs()...)
|
||||||
|
default:
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// No nested blocks are supported
|
||||||
|
if d.NextBlock(nesting + 1) {
|
||||||
|
return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// bigInt is a big.Int type that interops with JSON encodings as a string.
|
// bigInt is a big.Int type that interops with JSON encodings as a string.
|
||||||
type bigInt struct{ big.Int }
|
type bigInt struct{ big.Int }
|
||||||
|
|
||||||
@ -144,3 +214,6 @@ func (bi *bigInt) UnmarshalJSON(p []byte) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interface guard
|
||||||
|
var _ caddyfile.Unmarshaler = (*CustomCertSelectionPolicy)(nil)
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
//go:build cfgo
|
|
||||||
|
|
||||||
package caddytls
|
|
||||||
|
|
||||||
// This file adds support for X25519Kyber768Draft00, a post-quantum
|
|
||||||
// key agreement that is currently being rolled out by Chrome [1]
|
|
||||||
// and Cloudflare [2,3]. For more context, see the PR [4].
|
|
||||||
//
|
|
||||||
// [1] https://blog.chromium.org/2023/08/protecting-chrome-traffic-with-hybrid.html
|
|
||||||
// [2] https://blog.cloudflare.com/post-quantum-for-all/
|
|
||||||
// [3] https://blog.cloudflare.com/post-quantum-to-origins/
|
|
||||||
// [4] https://github.com/caddyserver/caddy/pull/5852
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
SupportedCurves["X25519Kyber768Draft00"] = tls.X25519Kyber768Draft00
|
|
||||||
defaultCurves = append(
|
|
||||||
[]tls.CurveID{tls.X25519Kyber768Draft00},
|
|
||||||
defaultCurves...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -15,6 +15,7 @@
|
|||||||
package caddytls
|
package caddytls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@ -28,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"github.com/mholt/acmez/v2"
|
"github.com/mholt/acmez/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||||
@ -77,6 +79,14 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
|
|||||||
cp[i].ClientAuthentication.verifiers = append(cp[i].ClientAuthentication.verifiers, validator.(ClientCertificateVerifier))
|
cp[i].ClientAuthentication.verifiers = append(cp[i].ClientAuthentication.verifiers, validator.(ClientCertificateVerifier))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(pol.HandshakeContextRaw) > 0 {
|
||||||
|
modIface, err := ctx.LoadModule(pol, "HandshakeContextRaw")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading handshake context module: %v", err)
|
||||||
|
}
|
||||||
|
cp[i].handshakeContext = modIface.(HandshakeContext)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -136,6 +146,7 @@ type ConnectionPolicy struct {
|
|||||||
// How to match this policy with a TLS ClientHello. If
|
// How to match this policy with a TLS ClientHello. If
|
||||||
// this policy is the first to match, it will be used.
|
// this policy is the first to match, it will be used.
|
||||||
MatchersRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=tls.handshake_match"`
|
MatchersRaw caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=tls.handshake_match"`
|
||||||
|
matchers []ConnectionMatcher
|
||||||
|
|
||||||
// How to choose a certificate if more than one matched
|
// How to choose a certificate if more than one matched
|
||||||
// the given ServerName (SNI) value.
|
// the given ServerName (SNI) value.
|
||||||
@ -191,6 +202,12 @@ type ConnectionPolicy struct {
|
|||||||
// This feature is EXPERIMENTAL and subject to change or removal.
|
// This feature is EXPERIMENTAL and subject to change or removal.
|
||||||
InsecureSecretsLog string `json:"insecure_secrets_log,omitempty"`
|
InsecureSecretsLog string `json:"insecure_secrets_log,omitempty"`
|
||||||
|
|
||||||
|
// A module that can manipulate the context passed into CertMagic's
|
||||||
|
// certificate management functions during TLS handshakes.
|
||||||
|
// EXPERIMENTAL - subject to change or removal.
|
||||||
|
HandshakeContextRaw json.RawMessage `json:"handshake_context,omitempty" caddy:"namespace=tls.context inline_key=module"`
|
||||||
|
handshakeContext HandshakeContext
|
||||||
|
|
||||||
// TLSConfig is the fully-formed, standard lib TLS config
|
// TLSConfig is the fully-formed, standard lib TLS config
|
||||||
// used to serve TLS connections. Provision all
|
// used to serve TLS connections. Provision all
|
||||||
// ConnectionPolicies to populate this. It is exported only
|
// ConnectionPolicies to populate this. It is exported only
|
||||||
@ -198,8 +215,15 @@ type ConnectionPolicy struct {
|
|||||||
// if necessary (like to adjust NextProtos to disable HTTP/2),
|
// if necessary (like to adjust NextProtos to disable HTTP/2),
|
||||||
// and may be unexported in the future.
|
// and may be unexported in the future.
|
||||||
TLSConfig *tls.Config `json:"-"`
|
TLSConfig *tls.Config `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
matchers []ConnectionMatcher
|
type HandshakeContext interface {
|
||||||
|
// HandshakeContext returns a context to pass into CertMagic's
|
||||||
|
// GetCertificate function used to serve, load, and manage certs
|
||||||
|
// during TLS handshakes. Generally you'll start with the context
|
||||||
|
// from the ClientHelloInfo, but you may use other information
|
||||||
|
// from it as well. Return an error to abort the handshake.
|
||||||
|
HandshakeContext(*tls.ClientHelloInfo) (context.Context, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
|
func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
|
||||||
@ -239,7 +263,18 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
|
|||||||
}
|
}
|
||||||
cfg.DefaultServerName = p.DefaultSNI
|
cfg.DefaultServerName = p.DefaultSNI
|
||||||
cfg.FallbackServerName = p.FallbackSNI
|
cfg.FallbackServerName = p.FallbackSNI
|
||||||
return cfg.GetCertificate(hello)
|
|
||||||
|
// TODO: experimental: if a handshake context module is configured, allow it
|
||||||
|
// to modify the context before passing it into CertMagic's GetCertificate
|
||||||
|
ctx := hello.Context()
|
||||||
|
if p.handshakeContext != nil {
|
||||||
|
ctx, err = p.handshakeContext.HandshakeContext(hello)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("handshake context: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.GetCertificateWithContext(ctx, hello)
|
||||||
},
|
},
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
MaxVersion: tls.VersionTLS13,
|
MaxVersion: tls.VersionTLS13,
|
||||||
@ -338,8 +373,9 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
|
|||||||
|
|
||||||
cfg.KeyLogWriter = logFile.(io.Writer)
|
cfg.KeyLogWriter = logFile.(io.Writer)
|
||||||
|
|
||||||
tlsApp.logger.Warn("TLS SECURITY COMPROMISED: secrets logging is enabled!",
|
if c := tlsApp.logger.Check(zapcore.WarnLevel, "TLS SECURITY COMPROMISED: secrets logging is enabled!"); c != nil {
|
||||||
zap.String("log_filename", filename))
|
c.Write(zap.String("log_filename", filename))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultTLSParams(cfg)
|
setDefaultTLSParams(cfg)
|
||||||
@ -363,6 +399,136 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
|
|||||||
p.InsecureSecretsLog == ""
|
p.InsecureSecretsLog == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalCaddyfile sets up the ConnectionPolicy from Caddyfile tokens. Syntax:
|
||||||
|
//
|
||||||
|
// connection_policy {
|
||||||
|
// alpn <values...>
|
||||||
|
// cert_selection {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// ciphers <cipher_suites...>
|
||||||
|
// client_auth {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// curves <curves...>
|
||||||
|
// default_sni <server_name>
|
||||||
|
// match {
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// protocols <min> [<max>]
|
||||||
|
// # EXPERIMENTAL:
|
||||||
|
// drop
|
||||||
|
// fallback_sni <server_name>
|
||||||
|
// insecure_secrets_log <log_file>
|
||||||
|
// }
|
||||||
|
func (cp *ConnectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
|
_, wrapper := d.Next(), d.Val()
|
||||||
|
|
||||||
|
// No same-line options are supported
|
||||||
|
if d.CountRemainingArgs() > 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasCertSelection, hasClientAuth, hasDefaultSNI, hasDrop,
|
||||||
|
hasFallbackSNI, hasInsecureSecretsLog, hasMatch, hasProtocols bool
|
||||||
|
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||||
|
optionName := d.Val()
|
||||||
|
switch optionName {
|
||||||
|
case "alpn":
|
||||||
|
if d.CountRemainingArgs() == 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
cp.ALPN = append(cp.ALPN, d.RemainingArgs()...)
|
||||||
|
case "cert_selection":
|
||||||
|
if hasCertSelection {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
p := &CustomCertSelectionPolicy{}
|
||||||
|
if err := p.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cp.CertSelection, hasCertSelection = p, true
|
||||||
|
case "client_auth":
|
||||||
|
if hasClientAuth {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
ca := &ClientAuthentication{}
|
||||||
|
if err := ca.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cp.ClientAuthentication, hasClientAuth = ca, true
|
||||||
|
case "ciphers":
|
||||||
|
if d.CountRemainingArgs() == 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
cp.CipherSuites = append(cp.CipherSuites, d.RemainingArgs()...)
|
||||||
|
case "curves":
|
||||||
|
if d.CountRemainingArgs() == 0 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
cp.Curves = append(cp.Curves, d.RemainingArgs()...)
|
||||||
|
case "default_sni":
|
||||||
|
if hasDefaultSNI {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
if d.CountRemainingArgs() != 1 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
_, cp.DefaultSNI, hasDefaultSNI = d.NextArg(), d.Val(), true
|
||||||
|
case "drop": // EXPERIMENTAL
|
||||||
|
if hasDrop {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
cp.Drop, hasDrop = true, true
|
||||||
|
case "fallback_sni": // EXPERIMENTAL
|
||||||
|
if hasFallbackSNI {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
if d.CountRemainingArgs() != 1 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
_, cp.FallbackSNI, hasFallbackSNI = d.NextArg(), d.Val(), true
|
||||||
|
case "insecure_secrets_log": // EXPERIMENTAL
|
||||||
|
if hasInsecureSecretsLog {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
if d.CountRemainingArgs() != 1 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
_, cp.InsecureSecretsLog, hasInsecureSecretsLog = d.NextArg(), d.Val(), true
|
||||||
|
case "match":
|
||||||
|
if hasMatch {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cp.MatchersRaw, hasMatch = matcherSet, true
|
||||||
|
case "protocols":
|
||||||
|
if hasProtocols {
|
||||||
|
return d.Errf("duplicate %s option '%s'", wrapper, optionName)
|
||||||
|
}
|
||||||
|
if d.CountRemainingArgs() == 0 || d.CountRemainingArgs() > 2 {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
_, cp.ProtocolMin, hasProtocols = d.NextArg(), d.Val(), true
|
||||||
|
if d.NextArg() {
|
||||||
|
cp.ProtocolMax = d.Val()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// No nested blocks are supported
|
||||||
|
if d.NextBlock(nesting + 1) {
|
||||||
|
return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ClientAuthentication configures TLS client auth.
|
// ClientAuthentication configures TLS client auth.
|
||||||
type ClientAuthentication struct {
|
type ClientAuthentication struct {
|
||||||
// Certificate authority module which provides the certificate pool of trusted certificates
|
// Certificate authority module which provides the certificate pool of trusted certificates
|
||||||
@ -423,16 +589,10 @@ type ClientAuthentication struct {
|
|||||||
// trust_pool <module> {
|
// trust_pool <module> {
|
||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
// trusted_leaf_cert <base64_der>
|
|
||||||
// trusted_leaf_cert_file <filename>
|
|
||||||
// verifier <module>
|
// verifier <module>
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// If `mode` is not provided, it defaults to `require_and_verify` if any of the following are provided:
|
// If `mode` is not provided, it defaults to `require_and_verify` if `trust_pool` is provided.
|
||||||
// - `trusted_leaf_certs`
|
|
||||||
// - `trusted_leaf_cert_file`
|
|
||||||
// - `trust_pool`
|
|
||||||
//
|
|
||||||
// Otherwise, it defaults to `require`.
|
// Otherwise, it defaults to `require`.
|
||||||
func (ca *ClientAuthentication) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
func (ca *ClientAuthentication) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
for d.NextArg() {
|
for d.NextArg() {
|
||||||
@ -636,7 +796,7 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
|
|||||||
if len(clientauth.TrustedCACerts) > 0 ||
|
if len(clientauth.TrustedCACerts) > 0 ||
|
||||||
len(clientauth.TrustedCACertPEMFiles) > 0 ||
|
len(clientauth.TrustedCACertPEMFiles) > 0 ||
|
||||||
len(clientauth.TrustedLeafCerts) > 0 ||
|
len(clientauth.TrustedLeafCerts) > 0 ||
|
||||||
clientauth.CARaw != nil {
|
clientauth.CARaw != nil || clientauth.ca != nil {
|
||||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
} else {
|
} else {
|
||||||
cfg.ClientAuth = tls.RequireAnyClientCert
|
cfg.ClientAuth = tls.RequireAnyClientCert
|
||||||
@ -711,7 +871,15 @@ func setDefaultTLSParams(cfg *tls.Config) {
|
|||||||
cfg.CipherSuites = append([]uint16{tls.TLS_FALLBACK_SCSV}, cfg.CipherSuites...)
|
cfg.CipherSuites = append([]uint16{tls.TLS_FALLBACK_SCSV}, cfg.CipherSuites...)
|
||||||
|
|
||||||
if len(cfg.CurvePreferences) == 0 {
|
if len(cfg.CurvePreferences) == 0 {
|
||||||
cfg.CurvePreferences = defaultCurves
|
// We would want to write
|
||||||
|
//
|
||||||
|
// cfg.CurvePreferences = defaultCurves
|
||||||
|
//
|
||||||
|
// but that would disable the post-quantum key agreement X25519Kyber768
|
||||||
|
// supported in Go 1.23, for which the CurveID is not exported.
|
||||||
|
// Instead, we'll set CurvePreferences to nil, which will enable PQC.
|
||||||
|
// See https://github.com/caddyserver/caddy/issues/6540
|
||||||
|
cfg.CurvePreferences = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.MinVersion == 0 {
|
if cfg.MinVersion == 0 {
|
||||||
@ -819,4 +987,46 @@ func (d destructableWriter) Destruct() error { return d.Close() }
|
|||||||
|
|
||||||
var secretsLogPool = caddy.NewUsagePool()
|
var secretsLogPool = caddy.NewUsagePool()
|
||||||
|
|
||||||
var _ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
|
// Interface guards
|
||||||
|
var (
|
||||||
|
_ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
|
||||||
|
_ caddyfile.Unmarshaler = (*ConnectionPolicy)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested
|
||||||
|
// matcher set, and returns its raw module map value.
|
||||||
|
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
|
||||||
|
matcherMap := make(map[string]ConnectionMatcher)
|
||||||
|
|
||||||
|
tokensByMatcherName := make(map[string][]caddyfile.Token)
|
||||||
|
for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
|
||||||
|
matcherName := d.Val()
|
||||||
|
tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for matcherName, tokens := range tokensByMatcherName {
|
||||||
|
dd := caddyfile.NewDispenser(tokens)
|
||||||
|
dd.Next() // consume wrapper name
|
||||||
|
|
||||||
|
unm, err := caddyfile.UnmarshalModule(dd, "tls.handshake_match."+matcherName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cm, ok := unm.(ConnectionMatcher)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("matcher module '%s' is not a connection matcher", matcherName)
|
||||||
|
}
|
||||||
|
matcherMap[matcherName] = cm
|
||||||
|
}
|
||||||
|
|
||||||
|
matcherSet := make(caddy.ModuleMap)
|
||||||
|
for name, matcher := range matcherMap {
|
||||||
|
jsonBytes, err := json.Marshal(matcher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
|
||||||
|
}
|
||||||
|
matcherSet[name] = jsonBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
return matcherSet, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
)
|
)
|
||||||
@ -92,8 +93,16 @@ func (fl FileLoader) LoadCertificates() ([]Certificate, error) {
|
|||||||
switch pair.Format {
|
switch pair.Format {
|
||||||
case "":
|
case "":
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case "pem":
|
case "pem":
|
||||||
|
// if the start of the key file looks like an encrypted private key,
|
||||||
|
// reject it with a helpful error message
|
||||||
|
if strings.Contains(string(keyData[:40]), "ENCRYPTED") {
|
||||||
|
return nil, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first")
|
||||||
|
}
|
||||||
|
|
||||||
cert, err = tls.X509KeyPair(certData, keyData)
|
cert, err = tls.X509KeyPair(certData, keyData)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format)
|
return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user