mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-26 00:32:31 -04:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e6eed42bd | |||
| 98cd4333a1 | |||
| aaf6794b31 | |||
| 1498132ea3 | |||
| 7f9b1f43c9 | |||
| 5e729c1e85 | |||
| 0a14f97e49 | |||
| 9864b138fb | |||
| 3d18bc56b9 | |||
| 886ba84baa | |||
| a9267791c4 | |||
| ef0aaca0d6 | |||
| 6891f7f421 | |||
| 499ad6d182 | |||
| 8e6bc36084 | |||
| 58970cae92 | |||
| 9e760e2e0c | |||
| 4b4e99bdb2 | |||
| 57d27c1b58 | |||
| 693e9b5283 | |||
| b687d7b967 |
@@ -78,12 +78,20 @@ jobs:
|
||||
printf "Git version: $(git version)\n\n"
|
||||
# Calculate the short SHA1 hash of the git commit
|
||||
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.vars.outputs.go_cache }}
|
||||
# In order:
|
||||
# * Module download cache
|
||||
# * Build cache (Linux)
|
||||
# * Build cache (Mac)
|
||||
# * Build cache (Windows)
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
~/Library/Caches/go-build
|
||||
~\AppData\Local\go-build
|
||||
key: ${{ runner.os }}-${{ matrix.go }}-go-ci-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.go }}-go-ci
|
||||
|
||||
@@ -42,12 +42,16 @@ jobs:
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.vars.outputs.go_cache }}
|
||||
# In order:
|
||||
# * Module download cache
|
||||
# * Build cache (Linux)
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: cross-build-go${{ matrix.go }}-${{ matrix.goos }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
cross-build-go${{ matrix.go }}-${{ matrix.goos }}
|
||||
|
||||
@@ -56,7 +56,6 @@ jobs:
|
||||
env
|
||||
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
|
||||
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||
|
||||
# Add "pip install" CLI tools to PATH
|
||||
echo ~/.local/bin >> $GITHUB_PATH
|
||||
@@ -91,7 +90,12 @@ jobs:
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.vars.outputs.go_cache }}
|
||||
# In order:
|
||||
# * Module download cache
|
||||
# * Build cache (Linux)
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-go${{ matrix.go }}-release-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go${{ matrix.go }}-release
|
||||
|
||||
@@ -442,7 +442,7 @@ func run(newCfg *Config, start bool) error {
|
||||
|
||||
// Start
|
||||
err = func() error {
|
||||
var started []string
|
||||
started := make([]string, 0, len(newCfg.apps))
|
||||
for name, a := range newCfg.apps {
|
||||
err := a.Start()
|
||||
if err != nil {
|
||||
|
||||
@@ -102,12 +102,20 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
|
||||
}
|
||||
}
|
||||
|
||||
// make a slice of the map keys so we can iterate in sorted order
|
||||
addrs := make([]string, 0, len(addrToKeys))
|
||||
for k := range addrToKeys {
|
||||
addrs = append(addrs, k)
|
||||
}
|
||||
sort.Strings(addrs)
|
||||
|
||||
// now that we know which addresses serve which keys of this
|
||||
// server block, we iterate that mapping and create a list of
|
||||
// new server blocks for each address where the keys of the
|
||||
// server block are only the ones which use the address; but
|
||||
// the contents (tokens) are of course the same
|
||||
for addr, keys := range addrToKeys {
|
||||
for _, addr := range addrs {
|
||||
keys := addrToKeys[addr]
|
||||
// parse keys so that we only have to do it once
|
||||
parsedKeys := make([]Address, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
@@ -161,6 +169,7 @@ func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]se
|
||||
delete(addrToServerBlocks, otherAddr)
|
||||
}
|
||||
}
|
||||
sort.Strings(a.addresses)
|
||||
|
||||
sbaddrs = append(sbaddrs, a)
|
||||
}
|
||||
@@ -208,13 +217,13 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
||||
}
|
||||
|
||||
// the bind directive specifies hosts, but is optional
|
||||
lnHosts := make([]string, 0, len(sblock.pile))
|
||||
lnHosts := make([]string, 0, len(sblock.pile["bind"]))
|
||||
for _, cfgVal := range sblock.pile["bind"] {
|
||||
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
|
||||
}
|
||||
if len(lnHosts) == 0 {
|
||||
if defaultBind, ok := options["default_bind"].(string); ok {
|
||||
lnHosts = []string{defaultBind}
|
||||
if defaultBind, ok := options["default_bind"].([]string); ok {
|
||||
lnHosts = defaultBind
|
||||
} else {
|
||||
lnHosts = []string{""}
|
||||
}
|
||||
@@ -236,6 +245,7 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
||||
for lnStr := range listeners {
|
||||
listenersList = append(listenersList, lnStr)
|
||||
}
|
||||
sort.Strings(listenersList)
|
||||
|
||||
return listenersList, nil
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||
{regexp.MustCompile(`{re\.([\w-]*)\.([\w-]*)}`), "{http.regexp.$1.$2}"},
|
||||
{regexp.MustCompile(`{vars\.([\w-]*)}`), "{http.vars.$1}"},
|
||||
{regexp.MustCompile(`{rp\.([\w-\.]*)}`), "{http.reverse_proxy.$1}"},
|
||||
{regexp.MustCompile(`{err\.([\w-\.]*)}`), "{http.error.$1}"},
|
||||
}
|
||||
|
||||
for _, sb := range originalServerBlocks {
|
||||
|
||||
@@ -29,7 +29,7 @@ func init() {
|
||||
RegisterGlobalOption("debug", parseOptTrue)
|
||||
RegisterGlobalOption("http_port", parseOptHTTPPort)
|
||||
RegisterGlobalOption("https_port", parseOptHTTPSPort)
|
||||
RegisterGlobalOption("default_bind", parseOptSingleString)
|
||||
RegisterGlobalOption("default_bind", parseOptStringList)
|
||||
RegisterGlobalOption("grace_period", parseOptDuration)
|
||||
RegisterGlobalOption("default_sni", parseOptSingleString)
|
||||
RegisterGlobalOption("order", parseOptOrder)
|
||||
@@ -279,6 +279,15 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ interface{}) (interface{}, e
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func parseOptStringList(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||
d.Next() // consume parameter name
|
||||
val := d.RemainingArgs()
|
||||
if len(val) == 0 {
|
||||
return "", d.ArgErr()
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func parseOptAdmin(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
||||
adminCfg := new(caddy.AdminConfig)
|
||||
for d.Next() {
|
||||
|
||||
@@ -350,7 +350,6 @@ func (st ServerType) buildTLSApp(
|
||||
globalPreferredChains := options["preferred_chains"]
|
||||
hasGlobalACMEDefaults := globalEmail != nil || globalACMECA != nil || globalACMECARoot != nil || globalACMEDNS != nil || globalACMEEAB != nil || globalPreferredChains != nil
|
||||
if hasGlobalACMEDefaults {
|
||||
// for _, ap := range tlsApp.Automation.Policies {
|
||||
for i := 0; i < len(tlsApp.Automation.Policies); i++ {
|
||||
ap := tlsApp.Automation.Policies[i]
|
||||
if len(ap.Issuers) == 0 && automationPolicyHasAllPublicNames(ap) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
default_bind tcp4/0.0.0.0
|
||||
default_bind tcp4/0.0.0.0 tcp6/[::]
|
||||
}
|
||||
|
||||
example.com {
|
||||
@@ -14,7 +14,8 @@ example.org:12345 {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
"tcp4/0.0.0.0:12345"
|
||||
"tcp4/0.0.0.0:12345",
|
||||
"tcp6/[::]:12345"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
@@ -31,7 +32,8 @@ example.org:12345 {
|
||||
},
|
||||
"srv1": {
|
||||
"listen": [
|
||||
"tcp4/0.0.0.0:443"
|
||||
"tcp4/0.0.0.0:443",
|
||||
"tcp6/[::]:443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
|
||||
@@ -24,6 +24,7 @@ https://example.com {
|
||||
max_conns_per_host 5
|
||||
keepalive_idle_conns_per_host 2
|
||||
keepalive_interval 30s
|
||||
renegotiation freely
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +92,9 @@ https://example.com {
|
||||
]
|
||||
},
|
||||
"response_header_timeout": 8000000000,
|
||||
"tls": {},
|
||||
"tls": {
|
||||
"renegotiation": "freely"
|
||||
},
|
||||
"versions": [
|
||||
"h2c",
|
||||
"2"
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/caddyserver/caddy/v2
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.0.0
|
||||
github.com/BurntSushi/toml v1.1.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b
|
||||
@@ -12,17 +12,17 @@ require (
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/google/cel-go v0.7.3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/klauspost/compress v1.15.0
|
||||
github.com/klauspost/cpuid/v2 v2.0.11
|
||||
github.com/lucas-clemente/quic-go v0.26.0
|
||||
github.com/klauspost/compress v1.15.4
|
||||
github.com/klauspost/cpuid/v2 v2.0.12
|
||||
github.com/lucas-clemente/quic-go v0.27.1
|
||||
github.com/mholt/acmez v1.0.2
|
||||
github.com/prometheus/client_golang v1.12.1
|
||||
github.com/smallstep/certificates v0.19.0
|
||||
github.com/smallstep/cli v0.18.0
|
||||
github.com/smallstep/nosql v0.4.0
|
||||
github.com/smallstep/truststore v0.11.0
|
||||
github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74
|
||||
github.com/yuin/goldmark v1.4.8
|
||||
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2
|
||||
github.com/yuin/goldmark v1.4.12
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0
|
||||
go.opentelemetry.io/otel v1.4.0
|
||||
@@ -35,7 +35,7 @@ require (
|
||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf
|
||||
google.golang.org/protobuf v1.27.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -104,8 +104,8 @@ github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYX
|
||||
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
|
||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo=
|
||||
@@ -689,10 +689,11 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
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.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/cpuid/v2 v2.0.11 h1:i2lw1Pm7Yi/4O6XCSyJWqEHI2MDw2FzUK6o/D21xn2A=
|
||||
github.com/klauspost/compress v1.15.4 h1:1kn4/7MepF/CHmYub99/nNX8az0IJjfSOU/jbnTVfqQ=
|
||||
github.com/klauspost/compress v1.15.4/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/cpuid/v2 v2.0.11/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -719,8 +720,8 @@ github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lucas-clemente/quic-go v0.26.0 h1:ALBQXr9UJ8A1LyzvceX4jd9QFsHvlI0RR6BkV16o00A=
|
||||
github.com/lucas-clemente/quic-go v0.26.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
|
||||
github.com/lucas-clemente/quic-go v0.27.1 h1:sOw+4kFSVrdWOYmUjufQ9GBVPqZ+tu+jMtXxXNmRJyk=
|
||||
github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
@@ -1059,8 +1060,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74 h1:uFx5aih29p2IaRUF0lJwtVViCXStlvnPPE3NEmM4Ivs=
|
||||
github.com/tailscale/tscert v0.0.0-20220125204807-4509a5fbaf74/go.mod h1:hL4gB6APAasMR2NNi/JHzqKkxW3EPQlFgLEq9PMi2t0=
|
||||
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2 h1:xwMw7LFhV9dbvot9A7NLClP9udqbjrQlIwWMH8e7uiQ=
|
||||
github.com/tailscale/tscert v0.0.0-20220316030059-54bbcb9f74e2/go.mod h1:hL4gB6APAasMR2NNi/JHzqKkxW3EPQlFgLEq9PMi2t0=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
|
||||
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
|
||||
@@ -1101,8 +1102,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark v1.4.8 h1:zHPiabbIRssZOI0MAzJDHsyvG4MXCGqVaMOwR+HeoQQ=
|
||||
github.com/yuin/goldmark v1.4.8/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0=
|
||||
github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
@@ -1849,8 +1850,9 @@ 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/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-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
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=
|
||||
|
||||
+1
-1
@@ -184,7 +184,7 @@ type fakeCloseQuicListener struct {
|
||||
// server on which Accept would be called with non-empty contexts
|
||||
// (mind that the default net listeners' Accept doesn't take a context argument)
|
||||
// sounds way too rare for us to sacrifice efficiency here.
|
||||
func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlySession, error) {
|
||||
func (fcql *fakeCloseQuicListener) Accept(_ context.Context) (quic.EarlyConnection, error) {
|
||||
conn, err := fcql.sharedQuicListener.Accept(fcql.context)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
|
||||
@@ -111,6 +111,8 @@ type App struct {
|
||||
// be forcefully closed.
|
||||
GracePeriod caddy.Duration `json:"grace_period,omitempty"`
|
||||
|
||||
Strict *StrictOptions `json:"strict,omitempty"`
|
||||
|
||||
// Servers is the list of servers, keyed by arbitrary names chosen
|
||||
// at your discretion for your own convenience; the keys do not
|
||||
// affect functionality.
|
||||
@@ -127,6 +129,13 @@ type App struct {
|
||||
allCertDomains []string
|
||||
}
|
||||
|
||||
type StrictOptions struct {
|
||||
Disabled bool `json:"disable,omitempty"`
|
||||
LenientQueryStrings bool `json:"lenient_query_strings,omitempty"`
|
||||
LenientPaths bool `json:"lenient_paths,omitempty"`
|
||||
LenientHeaders bool `json:"lenient_headers,omitempty"`
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (App) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
@@ -162,6 +171,7 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
srv.tlsApp = app.tlsApp
|
||||
srv.logger = app.logger.Named("log")
|
||||
srv.errorLogger = app.logger.Named("log.error")
|
||||
srv.strict = app.Strict
|
||||
|
||||
// only enable access logs if configured
|
||||
if srv.Logs != nil {
|
||||
|
||||
@@ -152,9 +152,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",
|
||||
srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err)
|
||||
}
|
||||
// only include domain if it's not explicitly skipped and it's not a Tailscale domain
|
||||
// (the implicit Tailscale manager module will get those certs at run-time)
|
||||
if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) && !isTailscaleDomain(d) {
|
||||
if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
|
||||
serverDomainSet[d] = struct{}{}
|
||||
}
|
||||
}
|
||||
@@ -181,6 +179,11 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
app.logger.Warn("skipping automated certificate management for server because it is disabled", zap.String("server_name", srvName))
|
||||
} else {
|
||||
for d := range serverDomainSet {
|
||||
// the implicit Tailscale manager module will get its own certs at run-time
|
||||
if isTailscaleDomain(d) {
|
||||
continue
|
||||
}
|
||||
|
||||
if certmagic.SubjectQualifiesForCert(d) &&
|
||||
!srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.SkipCerts) {
|
||||
// if a certificate for this name is already loaded,
|
||||
@@ -436,7 +439,7 @@ func (app *App) makeRedirRoute(redirToPort uint, matcherSet MatcherSet) Route {
|
||||
}
|
||||
}
|
||||
|
||||
// createAutomationPolicy ensures that automated certificates for this
|
||||
// createAutomationPolicies ensures that automated certificates for this
|
||||
// app are managed properly. This adds up to two automation policies:
|
||||
// one for the public names, and one for the internal names. If a catch-all
|
||||
// automation policy exists, it will be shallow-copied and used as the
|
||||
@@ -485,6 +488,12 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
|
||||
return err
|
||||
}
|
||||
ap.Managers = []certmagic.Manager{ts}
|
||||
|
||||
// must reprovision the automation policy so that the underlying
|
||||
// CertMagic config knows about the updated Managers
|
||||
if err := ap.Provision(app.tlsApp); err != nil {
|
||||
return fmt.Errorf("re-provisioning automation policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// while we're here, is this the catch-all/base policy?
|
||||
@@ -495,14 +504,17 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, internalNames []stri
|
||||
}
|
||||
|
||||
if basePolicy == nil {
|
||||
// no base policy found, we will make one! (with implicit Tailscale integration)
|
||||
// no base policy found; we will make one
|
||||
basePolicy = new(caddytls.AutomationPolicy)
|
||||
}
|
||||
|
||||
if basePolicy.Managers == nil {
|
||||
// add implicit Tailscale integration, for harmless convenience
|
||||
ts, err := implicitTailscale(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
basePolicy = &caddytls.AutomationPolicy{
|
||||
Managers: []certmagic.Manager{ts},
|
||||
}
|
||||
basePolicy.Managers = []certmagic.Manager{ts}
|
||||
}
|
||||
|
||||
// if the basePolicy has an existing ACMEIssuer (particularly to
|
||||
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"github.com/google/cel-go/ext"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
"go.uber.org/zap"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -62,6 +63,8 @@ type MatchExpression struct {
|
||||
expandedExpr string
|
||||
prg cel.Program
|
||||
ta ref.TypeAdapter
|
||||
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -83,7 +86,9 @@ func (m *MatchExpression) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
// Provision sets ups m.
|
||||
func (m *MatchExpression) Provision(_ caddy.Context) error {
|
||||
func (m *MatchExpression) Provision(ctx caddy.Context) error {
|
||||
m.log = ctx.Logger(m)
|
||||
|
||||
// replace placeholders with a function call - this is just some
|
||||
// light (and possibly naïve) syntactic sugar
|
||||
m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
|
||||
@@ -137,9 +142,13 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
|
||||
|
||||
// Match returns true if r matches m.
|
||||
func (m MatchExpression) Match(r *http.Request) bool {
|
||||
out, _, _ := m.prg.Eval(map[string]interface{}{
|
||||
out, _, err := m.prg.Eval(map[string]interface{}{
|
||||
"request": celHTTPRequest{r},
|
||||
})
|
||||
if err != nil {
|
||||
m.log.Error("evaluating expression", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
if outBool, ok := out.Value().(bool); ok {
|
||||
return outBool
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
||||
}
|
||||
|
||||
if tt.expression.Match(req) != tt.wantResult {
|
||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tt.wantResult, tt.expression)
|
||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tt.wantResult, tt.expression.Expr)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
@@ -503,15 +503,20 @@ func (m *MatchQuery) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
// Match returns true if r matches m. An empty m matches an empty query string.
|
||||
func (m MatchQuery) Match(r *http.Request) bool {
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
reqQuery, err := url.ParseQuery(r.URL.RawQuery)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for param, vals := range m {
|
||||
param = repl.ReplaceAll(param, "")
|
||||
paramVal, found := r.URL.Query()[param]
|
||||
if found {
|
||||
for _, v := range vals {
|
||||
v = repl.ReplaceAll(v, "")
|
||||
if paramVal[0] == v || v == "*" {
|
||||
return true
|
||||
}
|
||||
paramVal, found := reqQuery[param]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
for _, v := range vals {
|
||||
v = repl.ReplaceAll(v, "")
|
||||
if paramVal[0] == v || v == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,9 +624,18 @@ func TestQueryMatcher(t *testing.T) {
|
||||
input: "/?somekey=1",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
scenario: "invalid query string",
|
||||
match: MatchQuery{"test": []string{"*"}},
|
||||
input: "/?test=1;",
|
||||
expect: false,
|
||||
},
|
||||
} {
|
||||
|
||||
u, _ := url.Parse(tc.input)
|
||||
u, err := url.Parse(tc.input)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: Parsing URL: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
req := &http.Request{URL: u}
|
||||
repl := caddy.NewReplacer()
|
||||
|
||||
@@ -35,7 +35,6 @@ type adminUpstreams struct{}
|
||||
// upstreamResults holds the status of a particular upstream
|
||||
type upstreamStatus struct {
|
||||
Address string `json:"address"`
|
||||
Healthy bool `json:"healthy"`
|
||||
NumRequests int `json:"num_requests"`
|
||||
Fails int `json:"fails"`
|
||||
}
|
||||
|
||||
@@ -922,6 +922,20 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "renegotiation":
|
||||
if h.TLS == nil {
|
||||
h.TLS = new(TLSConfig)
|
||||
}
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
switch renegotiation := d.Val(); renegotiation {
|
||||
case "never", "once", "freely":
|
||||
h.TLS.Renegotiation = renegotiation
|
||||
default:
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "tls":
|
||||
if h.TLS == nil {
|
||||
h.TLS = new(TLSConfig)
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -59,6 +60,7 @@ default, all incoming headers are passed through unmodified.)
|
||||
fs.String("to", "", "Upstream address to which traffic should be sent")
|
||||
fs.Bool("change-host-header", false, "Set upstream Host header to address of upstream")
|
||||
fs.Bool("insecure", false, "Disable TLS verification (WARNING: DISABLES SECURITY BY NOT VERIFYING SSL CERTIFICATES!)")
|
||||
fs.Bool("internal-certs", false, "Use internal CA for issuing certs")
|
||||
return fs
|
||||
}(),
|
||||
})
|
||||
@@ -71,6 +73,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
|
||||
to := fs.String("to")
|
||||
changeHost := fs.Bool("change-host-header")
|
||||
insecure := fs.Bool("insecure")
|
||||
internalCerts := fs.Bool("internal-certs")
|
||||
|
||||
httpPort := strconv.Itoa(caddyhttp.DefaultHTTPPort)
|
||||
httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
|
||||
@@ -154,11 +157,24 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
|
||||
Servers: map[string]*caddyhttp.Server{"proxy": server},
|
||||
}
|
||||
|
||||
appsRaw := caddy.ModuleMap{
|
||||
"http": caddyconfig.JSON(httpApp, nil),
|
||||
}
|
||||
if internalCerts && fromAddr.Host != "" {
|
||||
tlsApp := caddytls.TLS{
|
||||
Automation: &caddytls.AutomationConfig{
|
||||
Policies: []*caddytls.AutomationPolicy{{
|
||||
Subjects: []string{fromAddr.Host},
|
||||
IssuersRaw: []json.RawMessage{json.RawMessage(`{"module":"internal"}`)},
|
||||
}},
|
||||
},
|
||||
}
|
||||
appsRaw["tls"] = caddyconfig.JSON(tlsApp, nil)
|
||||
}
|
||||
|
||||
cfg := &caddy.Config{
|
||||
Admin: &caddy.AdminConfig{Disabled: true},
|
||||
AppsRaw: caddy.ModuleMap{
|
||||
"http": caddyconfig.JSON(httpApp, nil),
|
||||
},
|
||||
Admin: &caddy.AdminConfig{Disabled: true},
|
||||
AppsRaw: appsRaw,
|
||||
}
|
||||
|
||||
err = caddy.Run(cfg)
|
||||
|
||||
@@ -247,8 +247,8 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
h.SetScheme(req)
|
||||
|
||||
// if H2C ("HTTP/2 over cleartext") is enabled and the upstream request is
|
||||
// HTTP/2 without TLS, use the alternate H2C-capable transport instead
|
||||
if req.ProtoMajor == 2 && req.URL.Scheme == "http" && h.h2cTransport != nil {
|
||||
// HTTP without TLS, use the alternate H2C-capable transport instead
|
||||
if req.URL.Scheme == "http" && h.h2cTransport != nil {
|
||||
return h.h2cTransport.RoundTrip(req)
|
||||
}
|
||||
|
||||
@@ -315,8 +315,23 @@ type TLSConfig struct {
|
||||
// The duration to allow a TLS handshake to a server. Default: No timeout.
|
||||
HandshakeTimeout caddy.Duration `json:"handshake_timeout,omitempty"`
|
||||
|
||||
// The server name (SNI) to use in TLS handshakes.
|
||||
// The server name used when verifying the certificate received in the TLS
|
||||
// handshake. By default, this will use the upstream address' host part.
|
||||
// You only need to override this if your upstream address does not match the
|
||||
// certificate the upstream is likely to use. For example if the upstream
|
||||
// address is an IP address, then you would need to configure this to the
|
||||
// hostname being served by the upstream server. Currently, this does not
|
||||
// support placeholders because the TLS config is not provisioned on each
|
||||
// connection, so a static value must be used.
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
|
||||
// TLS renegotiation level. TLS renegotiation is the act of performing
|
||||
// subsequent handshakes on a connection after the first.
|
||||
// The level can be:
|
||||
// - "never": (the default) disables renegotiation.
|
||||
// - "once": allows a remote server to request renegotiation once per connection.
|
||||
// - "freely": allows a remote server to repeatedly request renegotiation.
|
||||
Renegotiation string `json:"renegotiation,omitempty"`
|
||||
}
|
||||
|
||||
// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
|
||||
@@ -386,7 +401,19 @@ func (t TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
cfg.RootCAs = rootPool
|
||||
}
|
||||
|
||||
// custom SNI
|
||||
// Renegotiation
|
||||
switch t.Renegotiation {
|
||||
case "never":
|
||||
cfg.Renegotiation = tls.RenegotiateNever
|
||||
case "once":
|
||||
cfg.Renegotiation = tls.RenegotiateOnceAsClient
|
||||
case "freely":
|
||||
cfg.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid TLS renegotiation level: %v", t.Renegotiation)
|
||||
}
|
||||
|
||||
// override for the server name used verify the TLS handshake
|
||||
cfg.ServerName = t.ServerName
|
||||
|
||||
// throw all security out the window
|
||||
@@ -402,16 +429,16 @@ func (t TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
|
||||
// KeepAlive holds configuration pertaining to HTTP Keep-Alive.
|
||||
type KeepAlive struct {
|
||||
// Whether HTTP Keep-Alive is enabled. Default: true
|
||||
// Whether HTTP Keep-Alive is enabled. Default: `true`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
|
||||
// How often to probe for liveness. Default: `30s`.
|
||||
ProbeInterval caddy.Duration `json:"probe_interval,omitempty"`
|
||||
|
||||
// Maximum number of idle connections. Default: 0, which means no limit.
|
||||
// Maximum number of idle connections. Default: `0`, which means no limit.
|
||||
MaxIdleConns int `json:"max_idle_conns,omitempty"`
|
||||
|
||||
// Maximum number of idle connections per host. Default: 32.
|
||||
// Maximum number of idle connections per host. Default: `32`.
|
||||
MaxIdleConnsPerHost int `json:"max_idle_conns_per_host,omitempty"`
|
||||
|
||||
// How long connections should be kept alive when idle. Default: `2m`.
|
||||
|
||||
@@ -524,7 +524,7 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
|
||||
|
||||
// proxy the request to that upstream
|
||||
proxyErr = h.reverseProxy(w, r, origReq, repl, dialInfo, next)
|
||||
if proxyErr == nil || proxyErr == context.Canceled {
|
||||
if proxyErr == nil || errors.Is(proxyErr, context.Canceled) {
|
||||
// context.Canceled happens when the downstream client
|
||||
// cancels the request, which is not our failure
|
||||
return true, nil
|
||||
@@ -1180,6 +1180,11 @@ func statusError(err error) error {
|
||||
// errors proxying usually mean there is a problem with the upstream(s)
|
||||
statusCode := http.StatusBadGateway
|
||||
|
||||
// timeout errors have a standard status code (see issue #4823)
|
||||
if err, ok := err.(net.Error); ok && err.Timeout() {
|
||||
statusCode = http.StatusGatewayTimeout
|
||||
}
|
||||
|
||||
// if the client canceled the request (usually this means they closed
|
||||
// the connection, so they won't see any response), we can report it
|
||||
// as a client error (4xx) and not a server error (5xx); unfortunately
|
||||
|
||||
@@ -135,13 +135,20 @@ func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool {
|
||||
// find the bounds of each part of the URI that exist
|
||||
pathStart, qsStart, fragStart := -1, -1, -1
|
||||
pathEnd, qsEnd := -1, -1
|
||||
loop:
|
||||
for i, ch := range uri {
|
||||
switch {
|
||||
case ch == '?' && qsStart < 0:
|
||||
pathEnd, qsStart = i, i+1
|
||||
case ch == '#' && fragStart < 0:
|
||||
qsEnd, fragStart = i, i+1
|
||||
case pathStart < 0 && qsStart < 0 && fragStart < 0:
|
||||
case ch == '#' && fragStart < 0: // everything after fragment is fragment (very clear in RFC 3986 section 4.2)
|
||||
if qsStart < 0 {
|
||||
pathEnd = i
|
||||
} else {
|
||||
qsEnd = i
|
||||
}
|
||||
fragStart = i + 1
|
||||
break loop
|
||||
case pathStart < 0 && qsStart < 0:
|
||||
pathStart = i
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,16 @@ func TestRewrite(t *testing.T) {
|
||||
input: newRequest(t, "GET", "/%C2%B7%E2%88%B5.png?a=b"),
|
||||
expect: newRequest(t, "GET", "/i/%C2%B7%E2%88%B5.png?a=b"),
|
||||
},
|
||||
{
|
||||
rule: Rewrite{URI: "/bar#?"},
|
||||
input: newRequest(t, "GET", "/foo#fragFirst?c=d"), // not a valid query string (is part of fragment)
|
||||
expect: newRequest(t, "GET", "/bar#?"), // I think this is right? but who knows; std lib drops fragment when parsing
|
||||
},
|
||||
{
|
||||
rule: Rewrite{URI: "/bar"},
|
||||
input: newRequest(t, "GET", "/foo#fragFirst?c=d"),
|
||||
expect: newRequest(t, "GET", "/bar#fragFirst?c=d"),
|
||||
},
|
||||
|
||||
{
|
||||
rule: Rewrite{StripPathPrefix: "/prefix"},
|
||||
@@ -271,10 +281,11 @@ func TestRewrite(t *testing.T) {
|
||||
} {
|
||||
// copy the original input just enough so that we can
|
||||
// compare it after the rewrite to see if it changed
|
||||
urlCopy := *tc.input.URL
|
||||
originalInput := &http.Request{
|
||||
Method: tc.input.Method,
|
||||
RequestURI: tc.input.RequestURI,
|
||||
URL: &*tc.input.URL,
|
||||
URL: &urlCopy,
|
||||
}
|
||||
|
||||
// populate the replacer just enough for our tests
|
||||
|
||||
@@ -132,6 +132,7 @@ type Server struct {
|
||||
primaryHandlerChain Handler
|
||||
errorHandlerChain Handler
|
||||
listenerWrappers []caddy.ListenerWrapper
|
||||
strict *StrictOptions
|
||||
|
||||
tlsApp *caddytls.TLS
|
||||
logger *zap.Logger
|
||||
@@ -315,9 +316,46 @@ func (s *Server) enforcementHandler(w http.ResponseWriter, r *http.Request, next
|
||||
return Error(http.StatusMisdirectedRequest, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.strict.enforce(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (strict *StrictOptions) enforce(r *http.Request) error {
|
||||
if strict != nil && strict.Disabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reject query strings with unencoded ;
|
||||
if strict == nil || !strict.LenientQueryStrings {
|
||||
_, err := url.ParseQuery(r.URL.RawQuery)
|
||||
if err != nil {
|
||||
return Error(http.StatusBadRequest, fmt.Errorf("invalid query string: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Reject header fields with _ in name (#4830)
|
||||
if strict == nil || !strict.LenientHeaders {
|
||||
for field := range r.Header {
|
||||
if strings.Contains(field, "_") {
|
||||
return Error(http.StatusBadRequest, fmt.Errorf("invalid header field name: %s", field))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reject paths with // or ..
|
||||
if strict == nil || !strict.LenientPaths {
|
||||
if strings.Contains(r.URL.Path, "//") || strings.Contains(r.URL.Path, "..") || strings.Contains(r.URL.Path, "\x00") {
|
||||
return Error(http.StatusBadRequest, fmt.Errorf("invalid request path: %s", r.URL.RawPath))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenersUseAnyPortOtherThan returns true if there are any
|
||||
// listeners in s that use a port which is not otherPort.
|
||||
func (s *Server) listenersUseAnyPortOtherThan(otherPort int) bool {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func extractFrontMatter(input string) (map[string]interface{}, string, error) {
|
||||
|
||||
@@ -107,7 +107,7 @@ func init() {
|
||||
//
|
||||
// **index.html**
|
||||
// ```
|
||||
// {{ import "/path/to/file.html" }}
|
||||
// {{ import "/path/to/filename.html" }}
|
||||
// {{ template "main" }}
|
||||
// ```
|
||||
//
|
||||
@@ -238,6 +238,29 @@ func init() {
|
||||
// {{stripHTML "Shows <b>only</b> text content"}}
|
||||
// ```
|
||||
//
|
||||
// ##### `humanize`
|
||||
//
|
||||
// Transforms size and time inputs to a human readable format.
|
||||
// This uses the [go-humanize](https://github.com/dustin/go-humanize) library.
|
||||
//
|
||||
// The first argument must be a format type, and the last argument
|
||||
// is the input, or the input can be piped in. The supported format
|
||||
// types are:
|
||||
// - **size** which turns an integer amount of bytes into a string like `2.3 MB`
|
||||
// - **time** which turns a time string into a relative time string like `2 weeks ago`
|
||||
//
|
||||
// For the `time` format, the layout for parsing the input can be configured
|
||||
// by appending a colon `:` followed by the desired time layout. You can
|
||||
// find the documentation on time layouts [in Go's docs](https://pkg.go.dev/time#pkg-constants).
|
||||
// The default time layout is `RFC1123Z`, i.e. `Mon, 02 Jan 2006 15:04:05 -0700`.
|
||||
//
|
||||
// ```
|
||||
// {{humanize "size" "2048000"}}
|
||||
// {{placeholder "http.response.header.Content-Length" | humanize "size"}}
|
||||
// {{humanize "time" "Fri, 05 May 2022 15:04:05 +0200"}}
|
||||
// {{humanize "time:2006-Jan-02" "2022-May-05"}}
|
||||
// ```
|
||||
|
||||
type Templates struct {
|
||||
// The root path from which to load files. Required if template functions
|
||||
// accessing the file system are used (such as include). Default is
|
||||
|
||||
@@ -26,11 +26,13 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/alecthomas/chroma/formatters/html"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/yuin/goldmark"
|
||||
highlighting "github.com/yuin/goldmark-highlighting"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
@@ -81,6 +83,7 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
|
||||
"placeholder": c.funcPlaceholder,
|
||||
"fileExists": c.funcFileExists,
|
||||
"httpError": c.funcHTTPError,
|
||||
"humanize": c.funcHumanize,
|
||||
})
|
||||
return c.tpl
|
||||
}
|
||||
@@ -398,6 +401,43 @@ func (c TemplateContext) funcHTTPError(statusCode int) (bool, error) {
|
||||
return false, caddyhttp.Error(statusCode, nil)
|
||||
}
|
||||
|
||||
// funcHumanize transforms size and time inputs to a human readable format.
|
||||
//
|
||||
// Size inputs are expected to be integers, and are formatted as a
|
||||
// byte size, such as "83 MB".
|
||||
//
|
||||
// Time inputs are parsed using the given layout (default layout is RFC1123Z)
|
||||
// and are formatted as a relative time, such as "2 weeks ago".
|
||||
// See https://pkg.go.dev/time#pkg-constants for time layout docs.
|
||||
func (c TemplateContext) funcHumanize(formatType, data string) (string, error) {
|
||||
// The format type can optionally be followed
|
||||
// by a colon to provide arguments for the format
|
||||
parts := strings.Split(formatType, ":")
|
||||
|
||||
switch parts[0] {
|
||||
case "size":
|
||||
dataint, dataerr := strconv.ParseUint(data, 10, 64)
|
||||
if dataerr != nil {
|
||||
return "", fmt.Errorf("humanize: size cannot be parsed: %s", dataerr.Error())
|
||||
}
|
||||
return humanize.Bytes(dataint), nil
|
||||
|
||||
case "time":
|
||||
timelayout := time.RFC1123Z
|
||||
if len(parts) > 1 {
|
||||
timelayout = parts[1]
|
||||
}
|
||||
|
||||
dataint, dataerr := time.Parse(timelayout, data)
|
||||
if dataerr != nil {
|
||||
return "", fmt.Errorf("humanize: time cannot be parsed: %s", dataerr.Error())
|
||||
}
|
||||
return humanize.Time(dataint), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no know function was given")
|
||||
}
|
||||
|
||||
// WrappedHeader wraps niladic functions so that they
|
||||
// can be used in templates. (Template functions must
|
||||
// return a value.)
|
||||
|
||||
@@ -606,6 +606,55 @@ title = "Welcome"
|
||||
|
||||
}
|
||||
|
||||
func TestHumanize(t *testing.T) {
|
||||
tplContext := getContextOrFail(t)
|
||||
for i, test := range []struct {
|
||||
format string
|
||||
inputData string
|
||||
expect string
|
||||
errorCase bool
|
||||
verifyErr func(actual_string, substring string) bool
|
||||
}{
|
||||
{
|
||||
format: "size",
|
||||
inputData: "2048000",
|
||||
expect: "2.0 MB",
|
||||
errorCase: false,
|
||||
verifyErr: strings.Contains,
|
||||
},
|
||||
{
|
||||
format: "time",
|
||||
inputData: "Fri, 05 May 2022 15:04:05 +0200",
|
||||
expect: "ago",
|
||||
errorCase: false,
|
||||
verifyErr: strings.HasSuffix,
|
||||
},
|
||||
{
|
||||
format: "time:2006-Jan-02",
|
||||
inputData: "2022-May-05",
|
||||
expect: "ago",
|
||||
errorCase: false,
|
||||
verifyErr: strings.HasSuffix,
|
||||
},
|
||||
{
|
||||
format: "time",
|
||||
inputData: "Fri, 05 May 2022 15:04:05 GMT+0200",
|
||||
expect: "error:",
|
||||
errorCase: true,
|
||||
verifyErr: strings.HasPrefix,
|
||||
},
|
||||
} {
|
||||
if actual, err := tplContext.funcHumanize(test.format, test.inputData); !test.verifyErr(actual, test.expect) {
|
||||
if !test.errorCase {
|
||||
t.Errorf("Test %d: Expected '%s' but got '%s'", i, test.expect, actual)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: error: %s", i, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getContextOrFail(t *testing.T) TemplateContext {
|
||||
tplContext, err := initTestContext()
|
||||
t.Cleanup(func() {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -26,6 +27,10 @@ import (
|
||||
"github.com/mholt/acmez"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(LeafCertClientAuth{})
|
||||
}
|
||||
|
||||
// ConnectionPolicies govern the establishment of TLS connections. It is
|
||||
// an ordered group of connection policies; the first matching policy will
|
||||
// be used to configure TLS connections at handshake-time.
|
||||
@@ -55,6 +60,16 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
|
||||
}
|
||||
|
||||
if pol.ClientAuthentication != nil && len(pol.ClientAuthentication.VerifiersRaw) > 0 {
|
||||
clientCertValidations, err := ctx.LoadModule(pol.ClientAuthentication, "VerifiersRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading client cert verifiers: %v", err)
|
||||
}
|
||||
for _, validator := range clientCertValidations.([]interface{}) {
|
||||
cp[i].ClientAuthentication.verifiers = append(cp[i].ClientAuthentication.verifiers, validator.(ClientCertificateVerifier))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -62,7 +77,7 @@ func (cp ConnectionPolicies) Provision(ctx caddy.Context) error {
|
||||
|
||||
// TLSConfig returns a standard-lib-compatible TLS configuration which
|
||||
// selects the first matching policy based on the ClientHello.
|
||||
func (cp ConnectionPolicies) TLSConfig(ctx caddy.Context) *tls.Config {
|
||||
func (cp ConnectionPolicies) TLSConfig(_ caddy.Context) *tls.Config {
|
||||
// using ServerName to match policies is extremely common, especially in configs
|
||||
// with lots and lots of different policies; we can fast-track those by indexing
|
||||
// them by SNI, so we don't have to iterate potentially thousands of policies
|
||||
@@ -293,11 +308,22 @@ type ClientAuthentication struct {
|
||||
// these CA certificates will be rejected.
|
||||
TrustedCACertPEMFiles []string `json:"trusted_ca_certs_pem_files,omitempty"`
|
||||
|
||||
// DEPRECATED: This field is deprecated and will be removed in
|
||||
// a future version. Please use the `validators` field instead
|
||||
// with the tls.client_auth.leaf module instead.
|
||||
//
|
||||
// A list of base64 DER-encoded client leaf certs
|
||||
// to accept. If this list is not empty, client certs
|
||||
// which are not in this list will be rejected.
|
||||
TrustedLeafCerts []string `json:"trusted_leaf_certs,omitempty"`
|
||||
|
||||
// Client certificate verification modules. These can perform
|
||||
// custom client authentication checks, such as ensuring the
|
||||
// certificate is not revoked.
|
||||
VerifiersRaw []json.RawMessage `json:"verifiers,omitempty" caddy:"namespace=tls.client_auth inline_key=verifier"`
|
||||
|
||||
verifiers []ClientCertificateVerifier
|
||||
|
||||
// The mode for authenticating the client. Allowed values are:
|
||||
//
|
||||
// Mode | Description
|
||||
@@ -312,8 +338,6 @@ type ClientAuthentication struct {
|
||||
// are provided; otherwise, the default mode is `require`.
|
||||
Mode string `json:"mode,omitempty"`
|
||||
|
||||
// state established with the last call to ConfigureTLSConfig
|
||||
trustedLeafCerts []*x509.Certificate
|
||||
existingVerifyPeerCert func([][]byte, [][]*x509.Certificate) error
|
||||
}
|
||||
|
||||
@@ -321,7 +345,8 @@ type ClientAuthentication struct {
|
||||
func (clientauth ClientAuthentication) Active() bool {
|
||||
return len(clientauth.TrustedCACerts) > 0 ||
|
||||
len(clientauth.TrustedCACertPEMFiles) > 0 ||
|
||||
len(clientauth.TrustedLeafCerts) > 0 ||
|
||||
len(clientauth.TrustedLeafCerts) > 0 || // TODO: DEPRECATED
|
||||
len(clientauth.VerifiersRaw) > 0 ||
|
||||
len(clientauth.Mode) > 0
|
||||
}
|
||||
|
||||
@@ -378,28 +403,31 @@ func (clientauth *ClientAuthentication) ConfigureTLSConfig(cfg *tls.Config) erro
|
||||
cfg.ClientCAs = caPool
|
||||
}
|
||||
|
||||
// enforce leaf verification by writing our own verify function
|
||||
// TODO: DEPRECATED: Only here for backwards compatibility.
|
||||
// If leaf cert is specified, enforce by adding a client auth module
|
||||
if len(clientauth.TrustedLeafCerts) > 0 {
|
||||
clientauth.trustedLeafCerts = []*x509.Certificate{}
|
||||
caddy.Log().Named("tls.connection_policy").Warn("trusted_leaf_certs is deprecated; use leaf verifier module instead")
|
||||
var trustedLeafCerts []*x509.Certificate
|
||||
for _, clientCertString := range clientauth.TrustedLeafCerts {
|
||||
clientCert, err := decodeBase64DERCert(clientCertString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing certificate: %v", err)
|
||||
}
|
||||
clientauth.trustedLeafCerts = append(clientauth.trustedLeafCerts, clientCert)
|
||||
trustedLeafCerts = append(trustedLeafCerts, clientCert)
|
||||
}
|
||||
// if a custom verification function already exists, wrap it
|
||||
clientauth.existingVerifyPeerCert = cfg.VerifyPeerCertificate
|
||||
cfg.VerifyPeerCertificate = clientauth.verifyPeerCertificate
|
||||
clientauth.verifiers = append(clientauth.verifiers, LeafCertClientAuth{TrustedLeafCerts: trustedLeafCerts})
|
||||
}
|
||||
|
||||
// if a custom verification function already exists, wrap it
|
||||
clientauth.existingVerifyPeerCert = cfg.VerifyPeerCertificate
|
||||
cfg.VerifyPeerCertificate = clientauth.verifyPeerCertificate
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyPeerCertificate is for use as a tls.Config.VerifyPeerCertificate
|
||||
// callback to do custom client certificate verification. It is intended
|
||||
// for installation only by clientauth.ConfigureTLSConfig().
|
||||
func (clientauth ClientAuthentication) verifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
func (clientauth *ClientAuthentication) verifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
// first use any pre-existing custom verification function
|
||||
if clientauth.existingVerifyPeerCert != nil {
|
||||
err := clientauth.existingVerifyPeerCert(rawCerts, verifiedChains)
|
||||
@@ -407,23 +435,13 @@ func (clientauth ClientAuthentication) verifyPeerCertificate(rawCerts [][]byte,
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(rawCerts) == 0 {
|
||||
return fmt.Errorf("no client certificate provided")
|
||||
}
|
||||
|
||||
remoteLeafCert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse the given certificate: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, trustedLeafCert := range clientauth.trustedLeafCerts {
|
||||
if remoteLeafCert.Equal(trustedLeafCert) {
|
||||
return nil
|
||||
for _, verifier := range clientauth.verifiers {
|
||||
err := verifier.VerifyClientCertificate(rawCerts, verifiedChains)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("client leaf certificate failed validation")
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
|
||||
@@ -461,6 +479,38 @@ func setDefaultTLSParams(cfg *tls.Config) {
|
||||
cfg.PreferServerCipherSuites = true
|
||||
}
|
||||
|
||||
// LeafCertClientAuth verifies the client's leaf certificate.
|
||||
type LeafCertClientAuth struct {
|
||||
TrustedLeafCerts []*x509.Certificate
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (LeafCertClientAuth) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "tls.client_auth.leaf",
|
||||
New: func() caddy.Module { return new(LeafCertClientAuth) },
|
||||
}
|
||||
}
|
||||
|
||||
func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
if len(rawCerts) == 0 {
|
||||
return fmt.Errorf("no client certificate provided")
|
||||
}
|
||||
|
||||
remoteLeafCert, err := x509.ParseCertificate(rawCerts[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse the given certificate: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, trustedLeafCert := range l.TrustedLeafCerts {
|
||||
if remoteLeafCert.Equal(trustedLeafCert) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("client leaf certificate failed validation")
|
||||
}
|
||||
|
||||
// PublicKeyAlgorithm is a JSON-unmarshalable wrapper type.
|
||||
type PublicKeyAlgorithm x509.PublicKeyAlgorithm
|
||||
|
||||
@@ -481,4 +531,10 @@ type ConnectionMatcher interface {
|
||||
Match(*tls.ClientHelloInfo) bool
|
||||
}
|
||||
|
||||
// ClientCertificateVerifier is a type which verifies client certificates.
|
||||
// It is called during verifyPeerCertificate in the TLS handshake.
|
||||
type ClientCertificateVerifier interface {
|
||||
VerifyClientCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
|
||||
}
|
||||
|
||||
var defaultALPN = []string{"h2", "http/1.1"}
|
||||
|
||||
Reference in New Issue
Block a user