mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-26 08:42:31 -04:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 755c22af13 | |||
| f259ed52bb | |||
| 8bac134f26 | |||
| 412dcc07d3 | |||
| 660c59b6f3 | |||
| 58e05cab15 | |||
| 10f85558ea | |||
| 98468af8b6 | |||
| 25f10511e7 | |||
| b6e96fa3c5 | |||
| 56013934a4 | |||
| 0b6f764356 | |||
| 050d6e0aeb | |||
| 0bcd02d5f6 | |||
| c82fe91104 | |||
| f9b42c3772 | |||
| aaf6794b31 | |||
| 1498132ea3 | |||
| 7f9b1f43c9 | |||
| 5e729c1e85 | |||
| 0a14f97e49 | |||
| 9864b138fb | |||
| 3d18bc56b9 | |||
| 886ba84baa | |||
| a9267791c4 | |||
| ef0aaca0d6 | |||
| 6891f7f421 | |||
| 499ad6d182 | |||
| 8e6bc36084 | |||
| 58970cae92 | |||
| 9e760e2e0c | |||
| 4b4e99bdb2 | |||
| 57d27c1b58 | |||
| 693e9b5283 | |||
| b687d7b967 | |||
| f7be0ee101 | |||
| f6900fcf53 | |||
| ec86a2f7a3 | |||
| e7fbee8c82 | |||
| e84e19a04e | |||
| 4a223f5203 | |||
| af7321511c | |||
| 0be3d99543 | |||
| 3017b245c9 | |||
| 2e4c09155a | |||
| dcc98da4d2 | |||
| 3ab648382d | |||
| 40b193fb79 | |||
| d543ad1ffd | |||
| a8bb4a665a | |||
| 3a1e0dbf47 | |||
| 77a77c0219 | |||
| db62942d63 | |||
| dadd4b59b0 | |||
| d230b33007 | |||
| 0d13173071 | |||
| c3a82f53d5 |
@@ -21,10 +21,18 @@ jobs:
|
||||
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
go: [ '1.17', '1.18' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.17'
|
||||
GO_SEMVER: '~1.17.9'
|
||||
|
||||
- go: '1.18'
|
||||
GO_SEMVER: '~1.18.1'
|
||||
|
||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
||||
# SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True')
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
CADDY_BIN_PATH: ./cmd/caddy/caddy
|
||||
SUCCESS: 0
|
||||
@@ -43,7 +51,8 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
go-version: ${{ matrix.GO_SEMVER }}
|
||||
check-latest: true
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
@@ -69,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
|
||||
|
||||
@@ -17,13 +17,21 @@ jobs:
|
||||
matrix:
|
||||
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
|
||||
go: [ '1.18' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.18'
|
||||
GO_SEMVER: '~1.18.1'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
go-version: ${{ matrix.GO_SEMVER }}
|
||||
check-latest: true
|
||||
|
||||
- name: Print Go version and environment
|
||||
id: vars
|
||||
@@ -34,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 }}
|
||||
|
||||
@@ -19,7 +19,8 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.17
|
||||
go-version: '~1.17.9'
|
||||
check-latest: true
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
|
||||
@@ -12,13 +12,21 @@ jobs:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
go: [ '1.18' ]
|
||||
|
||||
include:
|
||||
# Set the minimum Go patch version for the given Go minor
|
||||
# Usable via ${{ matrix.GO_SEMVER }}
|
||||
- go: '1.18'
|
||||
GO_SEMVER: '~1.18.1'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
go-version: ${{ matrix.GO_SEMVER }}
|
||||
check-latest: true
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
@@ -48,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
|
||||
@@ -83,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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
_gitignore/
|
||||
*.log
|
||||
Caddyfile
|
||||
Caddyfile.*
|
||||
!caddyfile/
|
||||
|
||||
# artifacts from pprof tooling
|
||||
|
||||
@@ -21,10 +21,13 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -40,7 +43,6 @@ import (
|
||||
|
||||
"github.com/caddyserver/caddy/v2/notify"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
@@ -206,53 +208,26 @@ func (admin *AdminConfig) newAdminHandler(addr NetworkAddress, remote bool) admi
|
||||
muxWrap.enforceOrigin = admin.EnforceOrigin
|
||||
}
|
||||
|
||||
addRouteWithMetrics := func(pattern string, handlerLabel string, h http.Handler) {
|
||||
labels := prometheus.Labels{"path": pattern, "handler": handlerLabel}
|
||||
h = instrumentHandlerCounter(
|
||||
adminMetrics.requestCount.MustCurryWith(labels),
|
||||
h,
|
||||
)
|
||||
muxWrap.mux.Handle(pattern, h)
|
||||
}
|
||||
// addRoute just calls muxWrap.mux.Handle after
|
||||
// wrapping the handler with error handling
|
||||
addRoute := func(pattern string, handlerLabel string, h AdminHandler) {
|
||||
wrapper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
err := h.ServeHTTP(w, r)
|
||||
if err != nil {
|
||||
labels := prometheus.Labels{
|
||||
"path": pattern,
|
||||
"handler": handlerLabel,
|
||||
"method": strings.ToUpper(r.Method),
|
||||
}
|
||||
adminMetrics.requestErrors.With(labels).Inc()
|
||||
}
|
||||
muxWrap.handleError(w, r, err)
|
||||
})
|
||||
addRouteWithMetrics(pattern, handlerLabel, wrapper)
|
||||
}
|
||||
|
||||
const handlerLabel = "admin"
|
||||
|
||||
// register standard config control endpoints
|
||||
addRoute("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig))
|
||||
addRoute("/id/", handlerLabel, AdminHandlerFunc(handleConfigID))
|
||||
addRoute("/stop", handlerLabel, AdminHandlerFunc(handleStop))
|
||||
muxWrap.Handle("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig))
|
||||
muxWrap.Handle("/id/", handlerLabel, AdminHandlerFunc(handleConfigID))
|
||||
muxWrap.Handle("/stop", handlerLabel, AdminHandlerFunc(handleStop))
|
||||
|
||||
// register debugging endpoints
|
||||
addRouteWithMetrics("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index))
|
||||
addRouteWithMetrics("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline))
|
||||
addRouteWithMetrics("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile))
|
||||
addRouteWithMetrics("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol))
|
||||
addRouteWithMetrics("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace))
|
||||
addRouteWithMetrics("/debug/vars", handlerLabel, expvar.Handler())
|
||||
muxWrap.HandleStd("/debug/pprof/", handlerLabel, http.HandlerFunc(pprof.Index))
|
||||
muxWrap.HandleStd("/debug/pprof/cmdline", handlerLabel, http.HandlerFunc(pprof.Cmdline))
|
||||
muxWrap.HandleStd("/debug/pprof/profile", handlerLabel, http.HandlerFunc(pprof.Profile))
|
||||
muxWrap.HandleStd("/debug/pprof/symbol", handlerLabel, http.HandlerFunc(pprof.Symbol))
|
||||
muxWrap.HandleStd("/debug/pprof/trace", handlerLabel, http.HandlerFunc(pprof.Trace))
|
||||
muxWrap.HandleStd("/debug/vars", handlerLabel, expvar.Handler())
|
||||
|
||||
// register third-party module endpoints
|
||||
for _, m := range GetModules("admin.api") {
|
||||
router := m.New().(AdminRouter)
|
||||
handlerLabel := m.ID.Name()
|
||||
for _, route := range router.Routes() {
|
||||
addRoute(route.Pattern, handlerLabel, route.Handler)
|
||||
muxWrap.Handle(route.Pattern, handlerLabel, route.Handler)
|
||||
}
|
||||
admin.routers = append(admin.routers, router)
|
||||
}
|
||||
@@ -709,6 +684,21 @@ type adminHandler struct {
|
||||
remoteControl *RemoteAdmin
|
||||
}
|
||||
|
||||
// Handle registers an AdminHandler-type handler for the given pattern.
|
||||
func (h adminHandler) Handle(pattern, label string, handler AdminHandler) {
|
||||
h.mux.Handle(pattern, instrumentAdminHandler(pattern, label, handler, h.handleError))
|
||||
}
|
||||
|
||||
// HandleStd registers an http.Handler-type handler for the given pattern.
|
||||
func (h adminHandler) HandleStd(pattern, label string, handler http.Handler) {
|
||||
h.Handle(pattern, label, AdminHandlerFunc(func(
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) error {
|
||||
handler.ServeHTTP(w, r)
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
// ServeHTTP is the external entry point for API requests.
|
||||
// It will only be called once per request.
|
||||
func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -894,16 +884,30 @@ func (h adminHandler) originAllowed(origin *url.URL) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// etagHasher returns a the hasher we used on the config to both
|
||||
// produce and verify ETags.
|
||||
func etagHasher() hash.Hash32 { return fnv.New32a() }
|
||||
|
||||
func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
// Set the ETag as a trailer header.
|
||||
// The alternative is to write the config to a buffer, and
|
||||
// then hash that.
|
||||
w.Header().Set("Trailer", "ETag")
|
||||
|
||||
err := readConfig(r.URL.Path, w)
|
||||
hash := etagHasher()
|
||||
configWriter := io.MultiWriter(w, hash)
|
||||
err := readConfig(r.URL.Path, configWriter)
|
||||
if err != nil {
|
||||
return APIError{HTTPStatus: http.StatusBadRequest, Err: err}
|
||||
}
|
||||
|
||||
// we could consider setting up a sync.Pool for the summed
|
||||
// hashes to reduce GC pressure.
|
||||
w.Header().Set("ETag", r.URL.Path+" "+hex.EncodeToString(hash.Sum(nil)))
|
||||
|
||||
return nil
|
||||
|
||||
case http.MethodPost,
|
||||
@@ -937,7 +941,7 @@ func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
||||
|
||||
forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
|
||||
|
||||
err := changeConfig(r.Method, r.URL.Path, body, forceReload)
|
||||
err := changeConfig(r.Method, r.URL.Path, body, r.Header.Get("If-Match"), forceReload)
|
||||
if err != nil && !errors.Is(err, errSameConfig) {
|
||||
return err
|
||||
}
|
||||
@@ -1277,7 +1281,7 @@ var (
|
||||
// will get deleted before the process gracefully exits.
|
||||
func PIDFile(filename string) error {
|
||||
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
|
||||
err := os.WriteFile(filename, pid, 0600)
|
||||
err := os.WriteFile(filename, pid, 0o600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+50
-1
@@ -15,7 +15,9 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -139,10 +141,57 @@ func TestLoadConcurrent(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
type fooModule struct {
|
||||
IntField int
|
||||
StrField string
|
||||
}
|
||||
|
||||
func (fooModule) CaddyModule() ModuleInfo {
|
||||
return ModuleInfo{
|
||||
ID: "foo",
|
||||
New: func() Module { return new(fooModule) },
|
||||
}
|
||||
}
|
||||
func (fooModule) Start() error { return nil }
|
||||
func (fooModule) Stop() error { return nil }
|
||||
|
||||
func TestETags(t *testing.T) {
|
||||
RegisterModule(fooModule{})
|
||||
|
||||
if err := Load([]byte(`{"apps": {"foo": {"strField": "abc", "intField": 0}}}`), true); err != nil {
|
||||
t.Fatalf("loading: %s", err)
|
||||
}
|
||||
|
||||
const key = "/" + rawConfigKey + "/apps/foo"
|
||||
|
||||
// try update the config with the wrong etag
|
||||
err := changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}}`), "/"+rawConfigKey+" not_an_etag", false)
|
||||
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
|
||||
t.Fatalf("expected precondition failed; got %v", err)
|
||||
}
|
||||
|
||||
// get the etag
|
||||
hash := etagHasher()
|
||||
if err := readConfig(key, hash); err != nil {
|
||||
t.Fatalf("reading: %s", err)
|
||||
}
|
||||
|
||||
// do the same update with the correct key
|
||||
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 1}`), key+" "+hex.EncodeToString(hash.Sum(nil)), false)
|
||||
if err != nil {
|
||||
t.Fatalf("expected update to work; got %v", err)
|
||||
}
|
||||
|
||||
// now try another update. The hash should no longer match and we should get precondition failed
|
||||
err = changeConfig(http.MethodPost, key, []byte(`{"strField": "abc", "intField": 2}`), key+" "+hex.EncodeToString(hash.Sum(nil)), false)
|
||||
if apiErr, ok := err.(APIError); !ok || apiErr.HTTPStatus != http.StatusPreconditionFailed {
|
||||
t.Fatalf("expected precondition failed; got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLoad(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Load(testCfg, true)
|
||||
|
||||
@@ -17,6 +17,7 @@ package caddy
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -111,7 +112,7 @@ func Load(cfgJSON []byte, forceReload bool) error {
|
||||
}
|
||||
}()
|
||||
|
||||
err := changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, forceReload)
|
||||
err := changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, "", forceReload)
|
||||
if errors.Is(err, errSameConfig) {
|
||||
err = nil // not really an error
|
||||
}
|
||||
@@ -125,7 +126,12 @@ func Load(cfgJSON []byte, forceReload bool) error {
|
||||
// occur unless forceReload is true. If the config is unchanged and not
|
||||
// forcefully reloaded, then errConfigUnchanged This function is safe for
|
||||
// concurrent use.
|
||||
func changeConfig(method, path string, input []byte, forceReload bool) error {
|
||||
// The ifMatchHeader can optionally be given a string of the format:
|
||||
// "<path> <hash>"
|
||||
// where <path> is the absolute path in the config and <hash> is the expected hash of
|
||||
// the config at that path. If the hash in the ifMatchHeader doesn't match
|
||||
// the hash of the config, then an APIError with status 412 will be returned.
|
||||
func changeConfig(method, path string, input []byte, ifMatchHeader string, forceReload bool) error {
|
||||
switch method {
|
||||
case http.MethodGet,
|
||||
http.MethodHead,
|
||||
@@ -138,6 +144,32 @@ func changeConfig(method, path string, input []byte, forceReload bool) error {
|
||||
currentCfgMu.Lock()
|
||||
defer currentCfgMu.Unlock()
|
||||
|
||||
if ifMatchHeader != "" {
|
||||
// read out the parts
|
||||
parts := strings.Fields(ifMatchHeader)
|
||||
if len(parts) != 2 {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("malformed If-Match header; expect format \"<path> <hash>\""),
|
||||
}
|
||||
}
|
||||
|
||||
// get the current hash of the config
|
||||
// at the given path
|
||||
hash := etagHasher()
|
||||
err := unsyncedConfigAccess(http.MethodGet, parts[0], nil, hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hex.EncodeToString(hash.Sum(nil)) != parts[1] {
|
||||
return APIError{
|
||||
HTTPStatus: http.StatusPreconditionFailed,
|
||||
Err: fmt.Errorf("If-Match header did not match current config hash"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := unsyncedConfigAccess(method, path, input, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -442,7 +474,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 {
|
||||
@@ -500,7 +532,7 @@ func finishSettingUp(ctx Context, cfg *Config) error {
|
||||
|
||||
runLoadedConfig := func(config []byte) error {
|
||||
logger.Info("applying dynamically-loaded config")
|
||||
err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, false)
|
||||
err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, "", false)
|
||||
if errors.Is(err, errSameConfig) {
|
||||
return err
|
||||
}
|
||||
|
||||
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
Executable → Regular
+2
-2
@@ -18,13 +18,13 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Parse parses the input just enough to group tokens, in
|
||||
@@ -393,7 +393,7 @@ func (p *parser) doImport() error {
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
if strings.ContainsAny(globPattern, "*?[]") {
|
||||
log.Printf("[WARNING] No files matching import glob pattern: %s", importPattern)
|
||||
caddy.Log().Warn("No files matching import glob pattern", zap.String("pattern", importPattern))
|
||||
} else {
|
||||
return p.Errf("File to import not found: %s", importPattern)
|
||||
}
|
||||
|
||||
Executable → Regular
@@ -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
|
||||
}
|
||||
|
||||
@@ -580,12 +580,24 @@ func parseRedir(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
body = fmt.Sprintf(metaRedir, safeTo, safeTo, safeTo, safeTo)
|
||||
code = "302"
|
||||
default:
|
||||
// Allow placeholders for the code
|
||||
if strings.HasPrefix(code, "{") {
|
||||
break
|
||||
}
|
||||
// Try to validate as an integer otherwise
|
||||
codeInt, err := strconv.Atoi(code)
|
||||
if err != nil {
|
||||
return nil, h.Errf("Not a supported redir code type or not valid integer: '%s'", code)
|
||||
}
|
||||
if codeInt < 300 || codeInt > 399 {
|
||||
return nil, h.Errf("Redir code not in the 3xx range: '%v'", codeInt)
|
||||
// Sometimes, a 401 with Location header is desirable because
|
||||
// requests made with XHR will "eat" the 3xx redirect; so if
|
||||
// the intent was to redirect to an auth page, a 3xx won't
|
||||
// work. Responding with 401 allows JS code to read the
|
||||
// Location header and do a window.location redirect manually.
|
||||
// see https://stackoverflow.com/a/2573589/846934
|
||||
// see https://github.com/oauth2-proxy/oauth2-proxy/issues/1522
|
||||
if codeInt < 300 || (codeInt > 399 && codeInt != 401) {
|
||||
return nil, h.Errf("Redir code not in the 3xx range or 401: '%v'", codeInt)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,6 +148,27 @@ func TestRedirDirectiveSyntax(t *testing.T) {
|
||||
}`,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
// this is now allowed so a Location header
|
||||
// can be written and consumed by JS
|
||||
// in the case of XHR requests
|
||||
input: `:8080 {
|
||||
redir * :8081 401
|
||||
}`,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
redir * :8081 402
|
||||
}`,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
redir * :8081 {http.reverse_proxy.status_code}
|
||||
}`,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
redir /old.html /new.html htlm
|
||||
@@ -160,12 +181,6 @@ func TestRedirDirectiveSyntax(t *testing.T) {
|
||||
}`,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
redir * :8081 400
|
||||
}`,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
redir * :8081 temp
|
||||
|
||||
@@ -57,6 +57,7 @@ var directiveOrder = []string{
|
||||
|
||||
// middleware handlers; some wrap responses
|
||||
"basicauth",
|
||||
"forward_auth",
|
||||
"request_header",
|
||||
"encode",
|
||||
"push",
|
||||
@@ -424,14 +425,29 @@ func sortRoutes(routes []ConfigValue) {
|
||||
jPathLen = len(jPM[0])
|
||||
}
|
||||
|
||||
// if both directives have no path matcher, use whichever one
|
||||
// has any kind of matcher defined first.
|
||||
if iPathLen == 0 && jPathLen == 0 {
|
||||
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
|
||||
}
|
||||
// some directives involve setting values which can overwrite
|
||||
// eachother, so it makes most sense to reverse the order so
|
||||
// that the lease specific matcher is first; everything else
|
||||
// has most-specific matcher first
|
||||
if iDir == "vars" {
|
||||
// if both directives have no path matcher, use whichever one
|
||||
// has no matcher first.
|
||||
if iPathLen == 0 && jPathLen == 0 {
|
||||
return len(iRoute.MatcherSetsRaw) == 0 && len(jRoute.MatcherSetsRaw) > 0
|
||||
}
|
||||
|
||||
// sort with the most-specific (longest) path first
|
||||
return iPathLen > jPathLen
|
||||
// sort with the least-specific (shortest) path first
|
||||
return iPathLen < jPathLen
|
||||
} else {
|
||||
// if both directives have no path matcher, use whichever one
|
||||
// has any kind of matcher defined first.
|
||||
if iPathLen == 0 && jPathLen == 0 {
|
||||
return len(iRoute.MatcherSetsRaw) > 0 && len(jRoute.MatcherSetsRaw) == 0
|
||||
}
|
||||
|
||||
// sort with the most-specific (longest) path first
|
||||
return iPathLen > jPathLen
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ package httpcaddyfile
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -88,34 +88,10 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
// replace shorthand placeholders (which are
|
||||
// convenient when writing a Caddyfile) with
|
||||
// their actual placeholder identifiers or
|
||||
// variable names
|
||||
replacer := strings.NewReplacer(
|
||||
"{dir}", "{http.request.uri.path.dir}",
|
||||
"{file}", "{http.request.uri.path.file}",
|
||||
"{host}", "{http.request.host}",
|
||||
"{hostport}", "{http.request.hostport}",
|
||||
"{port}", "{http.request.port}",
|
||||
"{method}", "{http.request.method}",
|
||||
"{path}", "{http.request.uri.path}",
|
||||
"{query}", "{http.request.uri.query}",
|
||||
"{remote}", "{http.request.remote}",
|
||||
"{remote_host}", "{http.request.remote.host}",
|
||||
"{remote_port}", "{http.request.remote.port}",
|
||||
"{scheme}", "{http.request.scheme}",
|
||||
"{uri}", "{http.request.uri}",
|
||||
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
||||
"{tls_version}", "{http.request.tls.version}",
|
||||
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
|
||||
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
|
||||
"{tls_client_serial}", "{http.request.tls.client.serial}",
|
||||
"{tls_client_subject}", "{http.request.tls.client.subject}",
|
||||
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
||||
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
||||
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
||||
)
|
||||
// replace shorthand placeholders (which are convenient
|
||||
// when writing a Caddyfile) with their actual placeholder
|
||||
// identifiers or variable names
|
||||
replacer := strings.NewReplacer(placeholderShorthands()...)
|
||||
|
||||
// these are placeholders that allow a user-defined final
|
||||
// parameters, but we still want to provide a shorthand
|
||||
@@ -129,6 +105,9 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||
{regexp.MustCompile(`{header\.([\w-]*)}`), "{http.request.header.$1}"},
|
||||
{regexp.MustCompile(`{path\.([\w-]*)}`), "{http.request.uri.path.$1}"},
|
||||
{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 {
|
||||
@@ -253,20 +232,13 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||
}
|
||||
customLogs = append(customLogs, ncl)
|
||||
}
|
||||
|
||||
// Apply global log options, when set
|
||||
if options["log"] != nil {
|
||||
for _, logValue := range options["log"].([]ConfigValue) {
|
||||
addCustomLog(logValue.Value.(namedCustomLog))
|
||||
}
|
||||
}
|
||||
// Apply server-specific log options
|
||||
for _, p := range pairings {
|
||||
for _, sb := range p.serverBlocks {
|
||||
for _, clVal := range sb.pile["custom_log"] {
|
||||
addCustomLog(clVal.Value.(namedCustomLog))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasDefaultLog {
|
||||
// if the default log was not customized, ensure we
|
||||
@@ -279,6 +251,15 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||
}
|
||||
}
|
||||
|
||||
// Apply server-specific log options
|
||||
for _, p := range pairings {
|
||||
for _, sb := range p.serverBlocks {
|
||||
for _, clVal := range sb.pile["custom_log"] {
|
||||
addCustomLog(clVal.Value.(namedCustomLog))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// annnd the top-level config, then we're done!
|
||||
cfg := &caddy.Config{AppsRaw: make(caddy.ModuleMap)}
|
||||
|
||||
@@ -458,6 +439,17 @@ func (st *ServerType) serversFromPairings(
|
||||
}
|
||||
}
|
||||
|
||||
// Using paths in site addresses is deprecated
|
||||
// See ParseAddress() where parsing should later reject paths
|
||||
// See https://github.com/caddyserver/caddy/pull/4728 for a full explanation
|
||||
for _, sblock := range p.serverBlocks {
|
||||
for _, addr := range sblock.keys {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort server blocks by their keys; this is important because
|
||||
// only the first matching site should be evaluated, and we should
|
||||
// attempt to match most specific site first (host and path), in
|
||||
@@ -545,7 +537,7 @@ func (st *ServerType) serversFromPairings(
|
||||
// emit warnings if user put unspecified IP addresses; they probably want the bind directive
|
||||
for _, h := range hosts {
|
||||
if h == "0.0.0.0" || h == "::" {
|
||||
log.Printf("[WARNING] Site block has unspecified IP address %s which only matches requests having that Host header; you probably want the 'bind' directive to configure the socket", h)
|
||||
caddy.Log().Named("caddyfile").Warn("Site block has an unspecified IP address which only matches requests having that Host header; you probably want the 'bind' directive to configure the socket", zap.String("address", h))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1250,6 +1242,58 @@ func encodeMatcherSet(matchers map[string]caddyhttp.RequestMatcher) (caddy.Modul
|
||||
return msEncoded, nil
|
||||
}
|
||||
|
||||
// placeholderShorthands returns a slice of old-new string pairs,
|
||||
// where the left of the pair is a placeholder shorthand that may
|
||||
// be used in the Caddyfile, and the right is the replacement.
|
||||
func placeholderShorthands() []string {
|
||||
return []string{
|
||||
"{dir}", "{http.request.uri.path.dir}",
|
||||
"{file}", "{http.request.uri.path.file}",
|
||||
"{host}", "{http.request.host}",
|
||||
"{hostport}", "{http.request.hostport}",
|
||||
"{port}", "{http.request.port}",
|
||||
"{method}", "{http.request.method}",
|
||||
"{path}", "{http.request.uri.path}",
|
||||
"{query}", "{http.request.uri.query}",
|
||||
"{remote}", "{http.request.remote}",
|
||||
"{remote_host}", "{http.request.remote.host}",
|
||||
"{remote_port}", "{http.request.remote.port}",
|
||||
"{scheme}", "{http.request.scheme}",
|
||||
"{uri}", "{http.request.uri}",
|
||||
"{tls_cipher}", "{http.request.tls.cipher_suite}",
|
||||
"{tls_version}", "{http.request.tls.version}",
|
||||
"{tls_client_fingerprint}", "{http.request.tls.client.fingerprint}",
|
||||
"{tls_client_issuer}", "{http.request.tls.client.issuer}",
|
||||
"{tls_client_serial}", "{http.request.tls.client.serial}",
|
||||
"{tls_client_subject}", "{http.request.tls.client.subject}",
|
||||
"{tls_client_certificate_pem}", "{http.request.tls.client.certificate_pem}",
|
||||
"{tls_client_certificate_der_base64}", "{http.request.tls.client.certificate_der_base64}",
|
||||
"{upstream_hostport}", "{http.reverse_proxy.upstream.hostport}",
|
||||
}
|
||||
}
|
||||
|
||||
// WasReplacedPlaceholderShorthand checks if a token string was
|
||||
// likely a replaced shorthand of the known Caddyfile placeholder
|
||||
// replacement outputs. Useful to prevent some user-defined map
|
||||
// output destinations from overlapping with one of the
|
||||
// predefined shorthands.
|
||||
func WasReplacedPlaceholderShorthand(token string) string {
|
||||
prev := ""
|
||||
for i, item := range placeholderShorthands() {
|
||||
// only look at every 2nd item, which is the replacement
|
||||
if i%2 == 0 {
|
||||
prev = item
|
||||
continue
|
||||
}
|
||||
if strings.Trim(token, "{}") == strings.Trim(item, "{}") {
|
||||
// we return the original shorthand so it
|
||||
// can be used for an error message
|
||||
return prev
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// tryInt tries to convert val to an integer. If it fails,
|
||||
// it downgrades the error to a warning and returns 0.
|
||||
func tryInt(val interface{}, warnings *[]caddyconfig.Warning) int {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+46
-1
@@ -58,6 +58,10 @@ func (al adminLoad) Routes() []caddy.AdminRoute {
|
||||
Pattern: "/load",
|
||||
Handler: caddy.AdminHandlerFunc(al.handleLoad),
|
||||
},
|
||||
{
|
||||
Pattern: "/adapt",
|
||||
Handler: caddy.AdminHandlerFunc(al.handleAdapt),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +126,48 @@ func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// adaptByContentType adapts body to Caddy JSON using the adapter specified by contenType.
|
||||
// handleAdapt adapts the given Caddy config to JSON and responds with the result.
|
||||
func (adminLoad) handleAdapt(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodPost {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusMethodNotAllowed,
|
||||
Err: fmt.Errorf("method not allowed"),
|
||||
}
|
||||
}
|
||||
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
_, err := io.Copy(buf, r.Body)
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("reading request body: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
result, warnings, err := adaptByContentType(r.Header.Get("Content-Type"), buf.Bytes())
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
out := struct {
|
||||
Warnings []Warning `json:"warnings,omitempty"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
}{
|
||||
Warnings: warnings,
|
||||
Result: result,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
return json.NewEncoder(w).Encode(out)
|
||||
}
|
||||
|
||||
// adaptByContentType adapts body to Caddy JSON using the adapter specified by contentType.
|
||||
// If contentType is empty or ends with "/json", the input will be returned, as a no-op.
|
||||
func adaptByContentType(contentType string, body []byte) ([]byte, []Warning, error) {
|
||||
// assume JSON as the default
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
app.example.com {
|
||||
forward_auth authelia:9091 {
|
||||
uri /api/verify?rd=https://authelia.example.com
|
||||
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
|
||||
}
|
||||
|
||||
reverse_proxy backend:8080
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"app.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handle_response": [
|
||||
{
|
||||
"match": {
|
||||
"status_code": [
|
||||
2
|
||||
]
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "headers",
|
||||
"request": {
|
||||
"set": {
|
||||
"Remote-Email": [
|
||||
"{http.reverse_proxy.header.Remote-Email}"
|
||||
],
|
||||
"Remote-Groups": [
|
||||
"{http.reverse_proxy.header.Remote-Groups}"
|
||||
],
|
||||
"Remote-Name": [
|
||||
"{http.reverse_proxy.header.Remote-Name}"
|
||||
],
|
||||
"Remote-User": [
|
||||
"{http.reverse_proxy.header.Remote-User}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"handler": "reverse_proxy",
|
||||
"headers": {
|
||||
"request": {
|
||||
"set": {
|
||||
"X-Forwarded-Method": [
|
||||
"{http.request.method}"
|
||||
],
|
||||
"X-Forwarded-Uri": [
|
||||
"{http.request.uri}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"rewrite": {
|
||||
"method": "GET",
|
||||
"uri": "/api/verify?rd=https://authelia.example.com"
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "authelia:9091"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "backend:8080"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
:8881
|
||||
|
||||
forward_auth localhost:9000 {
|
||||
uri /auth
|
||||
copy_headers A>1 B C>3 {
|
||||
D
|
||||
E>5
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handle_response": [
|
||||
{
|
||||
"match": {
|
||||
"status_code": [
|
||||
2
|
||||
]
|
||||
},
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "headers",
|
||||
"request": {
|
||||
"set": {
|
||||
"1": [
|
||||
"{http.reverse_proxy.header.A}"
|
||||
],
|
||||
"3": [
|
||||
"{http.reverse_proxy.header.C}"
|
||||
],
|
||||
"5": [
|
||||
"{http.reverse_proxy.header.E}"
|
||||
],
|
||||
"B": [
|
||||
"{http.reverse_proxy.header.B}"
|
||||
],
|
||||
"D": [
|
||||
"{http.reverse_proxy.header.D}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"handler": "reverse_proxy",
|
||||
"headers": {
|
||||
"request": {
|
||||
"set": {
|
||||
"X-Forwarded-Method": [
|
||||
"{http.request.method}"
|
||||
],
|
||||
"X-Forwarded-Uri": [
|
||||
"{http.request.uri}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"rewrite": {
|
||||
"method": "GET",
|
||||
"uri": "/auth"
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:9000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
debug
|
||||
}
|
||||
|
||||
:8881 {
|
||||
log {
|
||||
format console
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "DEBUG",
|
||||
"exclude": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
},
|
||||
"log0": {
|
||||
"encoder": {
|
||||
"format": "console"
|
||||
},
|
||||
"level": "DEBUG",
|
||||
"include": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8881"
|
||||
],
|
||||
"logs": {
|
||||
"default_logger_name": "log0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
header Bar foo
|
||||
}
|
||||
respond @matcher9 "header matcher with null field matcher"
|
||||
|
||||
@matcher10 remote_ip private_ranges
|
||||
respond @matcher10 "remote_ip matcher with private ranges"
|
||||
}
|
||||
----------
|
||||
{
|
||||
@@ -209,6 +212,28 @@
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"remote_ip": {
|
||||
"ranges": [
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.1/8",
|
||||
"fd00::/8",
|
||||
"::1"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "remote_ip matcher with private ranges",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
:8884
|
||||
|
||||
reverse_proxy 127.0.0.1:65535 {
|
||||
@changeStatus status 500
|
||||
replace_status @changeStatus 400
|
||||
@500 status 500
|
||||
replace_status @500 400
|
||||
|
||||
@all status 2xx 3xx 4xx 5xx
|
||||
replace_status @all {http.error.status_code}
|
||||
|
||||
replace_status {http.error.status_code}
|
||||
|
||||
@accel header X-Accel-Redirect *
|
||||
handle_response @accel {
|
||||
@@ -78,6 +83,17 @@ reverse_proxy 127.0.0.1:65535 {
|
||||
},
|
||||
"status_code": 400
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"status_code": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
},
|
||||
"status_code": "{http.error.status_code}"
|
||||
},
|
||||
{
|
||||
"match": {
|
||||
"headers": {
|
||||
@@ -228,6 +244,9 @@ reverse_proxy 127.0.0.1:65535 {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"status_code": "{http.error.status_code}"
|
||||
},
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
https://example.com {
|
||||
reverse_proxy /path http://localhost:54321 {
|
||||
header_up Host {host}
|
||||
header_up X-Real-IP {remote}
|
||||
header_up X-Forwarded-For {remote}
|
||||
header_up X-Forwarded-Port {server_port}
|
||||
header_up X-Forwarded-Proto "http"
|
||||
reverse_proxy /path https://localhost:54321 {
|
||||
header_up Host {upstream_hostport}
|
||||
header_up Foo bar
|
||||
|
||||
method GET
|
||||
rewrite /rewritten?uri={uri}
|
||||
|
||||
buffer_requests
|
||||
|
||||
@@ -24,6 +24,8 @@ https://example.com {
|
||||
max_conns_per_host 5
|
||||
keepalive_idle_conns_per_host 2
|
||||
keepalive_interval 30s
|
||||
renegotiation freely
|
||||
except_ports 8181 8182
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,24 +60,19 @@ https://example.com {
|
||||
"headers": {
|
||||
"request": {
|
||||
"set": {
|
||||
"Foo": [
|
||||
"bar"
|
||||
],
|
||||
"Host": [
|
||||
"{http.request.host}"
|
||||
],
|
||||
"X-Forwarded-For": [
|
||||
"{http.request.remote}"
|
||||
],
|
||||
"X-Forwarded-Port": [
|
||||
"{server_port}"
|
||||
],
|
||||
"X-Forwarded-Proto": [
|
||||
"http"
|
||||
],
|
||||
"X-Real-Ip": [
|
||||
"{http.request.remote}"
|
||||
"{http.reverse_proxy.upstream.hostport}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"rewrite": {
|
||||
"method": "GET",
|
||||
"uri": "/rewritten?uri={http.request.uri}"
|
||||
},
|
||||
"transport": {
|
||||
"compression": false,
|
||||
"dial_fallback_delay": 5000000000,
|
||||
@@ -96,6 +93,13 @@ https://example.com {
|
||||
]
|
||||
},
|
||||
"response_header_timeout": 8000000000,
|
||||
"tls": {
|
||||
"except_ports": [
|
||||
"8181",
|
||||
"8182"
|
||||
],
|
||||
"renegotiation": "freely"
|
||||
},
|
||||
"versions": [
|
||||
"h2c",
|
||||
"2"
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
:80
|
||||
|
||||
vars /foobar foo last
|
||||
vars /foo foo middle
|
||||
vars * foo first
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"foo": "first",
|
||||
"handler": "vars"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/foo"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"foo": "middle",
|
||||
"handler": "vars"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/foobar"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"foo": "last",
|
||||
"handler": "vars"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,12 @@ localhost
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
issuer acme {
|
||||
propagation_timeout "10m0s"
|
||||
propagation_delay 5m10s
|
||||
propagation_timeout 10m20s
|
||||
}
|
||||
issuer zerossl {
|
||||
propagation_delay 5m30s
|
||||
propagation_timeout -1
|
||||
}
|
||||
}
|
||||
----------
|
||||
@@ -56,10 +61,20 @@ tls {
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"propagation_timeout": 600000000000
|
||||
"propagation_delay": 310000000000,
|
||||
"propagation_timeout": 620000000000
|
||||
}
|
||||
},
|
||||
"module": "acme"
|
||||
},
|
||||
{
|
||||
"challenges": {
|
||||
"dns": {
|
||||
"propagation_delay": 330000000000,
|
||||
"propagation_timeout": -1
|
||||
}
|
||||
},
|
||||
"module": "zerossl"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
// 3. Run `go mod init caddy`
|
||||
// 4. Run `go install` or `go build` - you now have a custom binary!
|
||||
//
|
||||
// Or you can use xcaddy which does it all for you as a command:
|
||||
// https://github.com/caddyserver/xcaddy
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
+4
-2
@@ -496,7 +496,9 @@ func cmdAdaptConfig(fl Flags) (int, error) {
|
||||
if warn.Directive != "" {
|
||||
msg = fmt.Sprintf("%s: %s", warn.Directive, warn.Message)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "[WARNING][%s] %s:%d: %s\n", adaptCmdAdapterFlag, warn.File, warn.Line, msg)
|
||||
caddy.Log().Named(adaptCmdAdapterFlag).Warn(msg,
|
||||
zap.String("file", warn.File),
|
||||
zap.Int("line", warn.Line))
|
||||
}
|
||||
|
||||
// validate output if requested
|
||||
@@ -667,7 +669,7 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
|
||||
}
|
||||
origin := "http://" + parsedAddr.JoinHostPort(0)
|
||||
if parsedAddr.IsUnixNetwork() {
|
||||
origin = "unixsocket" // hack so that http.NewRequest() is happy
|
||||
origin = "http://unixsocket" // hack so that http.NewRequest() is happy
|
||||
}
|
||||
|
||||
// form the request
|
||||
|
||||
@@ -3,26 +3,26 @@ 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
|
||||
github.com/caddyserver/certmagic v0.16.0
|
||||
github.com/caddyserver/certmagic v0.16.1
|
||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/google/cel-go v0.7.3
|
||||
github.com/google/cel-go v0.11.4
|
||||
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.6
|
||||
github.com/klauspost/cpuid/v2 v2.0.13
|
||||
github.com/lucas-clemente/quic-go v0.28.0
|
||||
github.com/mholt/acmez v1.0.2
|
||||
github.com/prometheus/client_golang v1.12.1
|
||||
github.com/smallstep/certificates v0.18.3-0.20220329212333-b42c1dfe64cf
|
||||
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
|
||||
@@ -30,12 +30,12 @@ require (
|
||||
go.opentelemetry.io/otel/sdk v1.4.0
|
||||
go.uber.org/zap v1.21.0
|
||||
golang.org/x/crypto v0.0.0-20220210151621-f4118a5b28e2
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf
|
||||
google.golang.org/protobuf v1.27.1
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21
|
||||
google.golang.org/protobuf v1.28.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -43,7 +43,7 @@ require (
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
@@ -65,7 +65,7 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
@@ -81,8 +81,9 @@ require (
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
@@ -116,16 +117,16 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.4.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
go.step.sm/cli-utils v0.7.0 // indirect
|
||||
go.step.sm/crypto v0.15.3 // indirect
|
||||
go.step.sm/linkedca v0.11.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.step.sm/crypto v0.16.1 // indirect
|
||||
go.step.sm/linkedca v0.15.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
|
||||
golang.org/x/tools v0.1.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/grpc v1.44.0 // indirect
|
||||
google.golang.org/grpc v1.46.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
|
||||
@@ -104,9 +104,10 @@ 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=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
|
||||
@@ -148,8 +149,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f h1:0cEys61Sr2hUBEXfNV8eyQP01oZuBgoMeHunebPirK8=
|
||||
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed h1:ue9pVfIcP+QMEjfgo/Ez4ZjNZfonGgR6NgjMaJMu1Cg=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
|
||||
github.com/apache/beam v2.28.0+incompatible/go.mod h1:/8NX3Qi8vGstDLLaeaU7+lzVEu/ACaQhYjeefzQ0y1o=
|
||||
github.com/apache/beam v2.30.0+incompatible/go.mod h1:/8NX3Qi8vGstDLLaeaU7+lzVEu/ACaQhYjeefzQ0y1o=
|
||||
@@ -162,6 +163,7 @@ github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3st
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
@@ -197,13 +199,14 @@ github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl
|
||||
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/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw=
|
||||
github.com/caddyserver/certmagic v0.16.0 h1:nM6Fm+OSnTpx/uRWmN++I2fYq006uhi6m6m3rD1Jjtg=
|
||||
github.com/caddyserver/certmagic v0.16.0/go.mod h1:jKQ5n+ViHAr6DbPwEGLTSM2vDwTO6EvCKBblBRUvvuQ=
|
||||
github.com/caddyserver/certmagic v0.16.1 h1:rdSnjcUVJojmL4M0efJ+yHXErrrijS4YYg3FuwRdJkI=
|
||||
github.com/caddyserver/certmagic v0.16.1/go.mod h1:jKQ5n+ViHAr6DbPwEGLTSM2vDwTO6EvCKBblBRUvvuQ=
|
||||
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
|
||||
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -224,6 +227,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@@ -233,6 +238,7 @@ github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XP
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
@@ -306,11 +312,14 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
|
||||
github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
|
||||
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
@@ -322,6 +331,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
|
||||
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
@@ -332,6 +343,7 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
@@ -346,6 +358,7 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
||||
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
|
||||
@@ -366,6 +379,7 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
@@ -378,6 +392,7 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -415,14 +430,14 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
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/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/cel-go v0.7.3 h1:8v9BSN0avuGwrHFKNCjfiQ/CE6+D6sW+BDyOVoEeP6o=
|
||||
github.com/google/cel-go v0.7.3/go.mod h1:4EtyFAHT5xNr0Msu0MJjyGxPUgdr9DlcaPyzLt/kkt8=
|
||||
github.com/google/cel-spec v0.5.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA=
|
||||
github.com/google/cel-go v0.11.4 h1:wWOnKmLxALl3l9Av221MfIOWRiR01sDVljzg6LZ6Zn0=
|
||||
github.com/google/cel-go v0.11.4/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.1.2-0.20210422104406-9f33727a7a18/go.mod h1:6CKh9dscIRoqc2kC6YUFICHZMT9NrClyPrRVFrdw1QQ=
|
||||
github.com/google/certificate-transparency-go v1.1.2-0.20210512142713-bed466244fa6/go.mod h1:aF2dp7Dh81mY8Y/zpzyXps4fQW5zQbDu2CxfpJB6NkI=
|
||||
@@ -524,26 +539,52 @@ github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoP
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||
github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I=
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
|
||||
github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo=
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
|
||||
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/vault/api v1.3.0/go.mod h1:EabNQLI0VWbWoGlA+oBLC8PXmR9D60aUVgQGvangFWQ=
|
||||
github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw=
|
||||
github.com/hashicorp/vault/api/auth/approle v0.1.1/go.mod h1:mHOLgh//xDx4dpqXoq6tS8Ob0FoCFWLU2ibJ26Lfmag=
|
||||
github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
@@ -615,6 +656,7 @@ github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
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/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||
github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
|
||||
github.com/jhump/protoreflect v1.8.2/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
@@ -649,10 +691,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.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY=
|
||||
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/cpuid/v2 v2.0.11/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.0.13 h1:1XxvOiqXZ8SULZUKim/wncr3wZ38H4yCuVDvKdK9OGs=
|
||||
github.com/klauspost/cpuid/v2 v2.0.13/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=
|
||||
@@ -679,8 +722,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.28.0 h1:9eXVRgIkMQQyiyorz/dAaOYIx3TFzXsIFkNFz4cxuJM=
|
||||
github.com/lucas-clemente/quic-go v0.28.0/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
|
||||
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=
|
||||
@@ -694,10 +737,12 @@ github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0O
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
@@ -711,6 +756,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
@@ -745,11 +791,15 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
@@ -818,12 +868,14 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/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=
|
||||
@@ -840,6 +892,7 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
|
||||
@@ -895,6 +948,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I=
|
||||
@@ -945,8 +1000,8 @@ github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+R
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
|
||||
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||
github.com/smallstep/certificates v0.18.0/go.mod h1:8eHwHNg/bRWvNZo9S0uWFVMkS+LSpDYxM4//EgBhkFM=
|
||||
github.com/smallstep/certificates v0.18.3-0.20220329212333-b42c1dfe64cf h1:Ky0hrHTjd/sXrLZj1rKEBYRo/3nmOUIk3xyqnrs04jY=
|
||||
github.com/smallstep/certificates v0.18.3-0.20220329212333-b42c1dfe64cf/go.mod h1:K4gQuZo7j4kzQb0zF0A2s6/kiPh3ZhmFThlHh2B+Gwg=
|
||||
github.com/smallstep/certificates v0.19.0 h1:wW344Q/QpupjKKFKa4PqzEXfwgeq/54dkU/HNvGnwQQ=
|
||||
github.com/smallstep/certificates v0.19.0/go.mod h1:nkG4c+1HLnCmIBAgZ/bKaBPAsN7ePtyzkDWzPSqcObQ=
|
||||
github.com/smallstep/certinfo v1.5.2/go.mod h1:gA7HBbue0Wwr3kD60P2UtgTIFfMAOC66D3rzYhI0GZ4=
|
||||
github.com/smallstep/cli v0.18.0 h1:BslbUHuMfj/LbVHxuZ4Hv1sL+vAHHidqia4JRoCBwXs=
|
||||
github.com/smallstep/cli v0.18.0/go.mod h1:C8ZSfMm/pKdCHnN1C3Pc44bjIOkBbyuoyq6XjS/K9lI=
|
||||
@@ -1005,11 +1060,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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=
|
||||
@@ -1021,6 +1077,7 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
@@ -1049,8 +1106,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=
|
||||
@@ -1139,17 +1196,18 @@ go.step.sm/cli-utils v0.7.0 h1:2GvY5Muid1yzp7YQbfCCS+gK3q7zlHjjLL5Z0DXz8ds=
|
||||
go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
|
||||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||
go.step.sm/crypto v0.13.0/go.mod h1:5YzQ85BujYBu6NH18jw7nFjwuRnDch35nLzH0ES5sKg=
|
||||
go.step.sm/crypto v0.15.3 h1:f3GMl+aCydt294BZRjTYwpaXRqwwndvoTY2NLN4wu10=
|
||||
go.step.sm/crypto v0.15.3/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
||||
go.step.sm/crypto v0.16.1 h1:4mnZk21cSxyMGxsEpJwZKKvJvDu1PN09UVrWWFNUBdk=
|
||||
go.step.sm/crypto v0.16.1/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
||||
go.step.sm/linkedca v0.7.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||
go.step.sm/linkedca v0.11.0 h1:jkG5XDQz9VSz2PH+cGjDvJTwiIziN0SWExTnicWpb8o=
|
||||
go.step.sm/linkedca v0.11.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||
go.step.sm/linkedca v0.15.0 h1:lEkGRDY+u7FudGKt8yEo7nBy5OzceO9s3rl+/sZVL5M=
|
||||
go.step.sm/linkedca v0.15.0/go.mod h1:W59ucS4vFpuR0g4PtkGbbtXAwxbDEnNCg+ovkej1ANM=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
@@ -1243,6 +1301,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20170726083632-f5079bd7f6f7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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=
|
||||
@@ -1308,8 +1367,9 @@ golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -1380,6 +1440,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1450,8 +1511,9 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
@@ -1472,6 +1534,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -1657,7 +1720,6 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
@@ -1697,8 +1759,9 @@ google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ6
|
||||
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf h1:SVYXkUz2yZS9FWb2Gm8ivSlbNQzL2Z/NpPKE3RG2jWk=
|
||||
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I=
|
||||
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
@@ -1734,9 +1797,11 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
|
||||
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg=
|
||||
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -1751,8 +1816,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1792,8 +1858,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
|
||||
|
||||
+7
-1
@@ -661,9 +661,15 @@ func newDefaultProductionLog() (*defaultCustomLog, error) {
|
||||
|
||||
cl.buildCore()
|
||||
|
||||
logger := zap.New(cl.core)
|
||||
|
||||
// capture logs from other libraries which
|
||||
// may not be using zap logging directly
|
||||
_ = zap.RedirectStdLog(logger)
|
||||
|
||||
return &defaultCustomLog{
|
||||
CustomLog: cl,
|
||||
logger: zap.New(cl.core),
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
+27
-23
@@ -3,7 +3,7 @@ package caddy
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/internal/metrics"
|
||||
internal "github.com/caddyserver/caddy/v2/internal/metrics"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/collectors"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
@@ -15,13 +15,13 @@ func init() {
|
||||
|
||||
const ns, sub = "caddy", "admin"
|
||||
|
||||
adminMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
adminMetrics.requests = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "http_requests_total",
|
||||
Help: "Counter of requests made to the Admin API's HTTP endpoints.",
|
||||
}, []string{"handler", "path", "code", "method"})
|
||||
adminMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
adminMetrics.errors = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "http_request_errors_total",
|
||||
@@ -31,29 +31,33 @@ func init() {
|
||||
|
||||
// adminMetrics is a collection of metrics that can be tracked for the admin API.
|
||||
var adminMetrics = struct {
|
||||
requestCount *prometheus.CounterVec
|
||||
requestErrors *prometheus.CounterVec
|
||||
requests *prometheus.CounterVec
|
||||
errors *prometheus.CounterVec
|
||||
}{}
|
||||
|
||||
// Similar to promhttp.InstrumentHandlerCounter, but upper-cases method names
|
||||
// instead of lower-casing them.
|
||||
//
|
||||
// Unlike promhttp.InstrumentHandlerCounter, this assumes a "code" and "method"
|
||||
// label is present, and will panic otherwise.
|
||||
func instrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
d := newDelegator(w)
|
||||
next.ServeHTTP(d, r)
|
||||
counter.With(prometheus.Labels{
|
||||
"code": metrics.SanitizeCode(d.status),
|
||||
"method": metrics.SanitizeMethod(r.Method),
|
||||
}).Inc()
|
||||
})
|
||||
}
|
||||
// instrumentAdminHandler wraps the handler with total and errored-out request count
|
||||
// in a manner similar to promhttp.InstrumentHandlerCounter. All errors are handled
|
||||
// using the passed error handler.
|
||||
func instrumentAdminHandler(pattern, handlerLabel string,
|
||||
h AdminHandler, errorHandler func(http.ResponseWriter, *http.Request, error),
|
||||
) http.HandlerFunc {
|
||||
labels := prometheus.Labels{"path": pattern, "handler": handlerLabel}
|
||||
requests := adminMetrics.requests.MustCurryWith(labels)
|
||||
errors := adminMetrics.errors.MustCurryWith(labels)
|
||||
|
||||
func newDelegator(w http.ResponseWriter) *delegator {
|
||||
return &delegator{
|
||||
ResponseWriter: w,
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
d := delegator{ResponseWriter: w}
|
||||
labels := prometheus.Labels{
|
||||
"method": internal.SanitizeMethod(r.Method),
|
||||
}
|
||||
|
||||
if err := h.ServeHTTP(w, r); err != nil {
|
||||
errors.With(labels).Inc()
|
||||
errorHandler(w, r, err)
|
||||
}
|
||||
|
||||
labels["code"] = internal.SanitizeCode(d.status)
|
||||
requests.With(labels).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -357,12 +357,10 @@ func (app *App) Start() error {
|
||||
return fmt.Errorf("getting HTTP/3 QUIC listener: %v", err)
|
||||
}
|
||||
h3srv := &http3.Server{
|
||||
Server: &http.Server{
|
||||
Addr: hostport,
|
||||
Handler: srv,
|
||||
TLSConfig: tlsCfg,
|
||||
ErrorLog: serverLogger,
|
||||
},
|
||||
Addr: hostport,
|
||||
Handler: srv,
|
||||
TLSConfig: tlsCfg,
|
||||
MaxHeaderBytes: srv.MaxHeaderBytes,
|
||||
}
|
||||
//nolint:errcheck
|
||||
go h3srv.ServeListener(h3ln)
|
||||
|
||||
@@ -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,
|
||||
@@ -222,11 +225,15 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
app.logger.Info("enabling automatic HTTP->HTTPS redirects", zap.String("server_name", srvName))
|
||||
|
||||
// create HTTP->HTTPS redirects
|
||||
for _, addr := range srv.Listen {
|
||||
for _, listenAddr := range srv.Listen {
|
||||
// figure out the address we will redirect to...
|
||||
addr, err := caddy.ParseNetworkAddress(addr)
|
||||
addr, err := caddy.ParseNetworkAddress(listenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: invalid listener address: %v", srvName, addr)
|
||||
msg := "%s: invalid listener address: %v"
|
||||
if strings.Count(listenAddr, ":") > 1 {
|
||||
msg = msg + ", there are too many colons, so the port is ambiguous. Did you mean to wrap the IPv6 address with [] brackets?"
|
||||
}
|
||||
return fmt.Errorf(msg, srvName, listenAddr)
|
||||
}
|
||||
|
||||
// this address might not have a hostname, i.e. might be a
|
||||
@@ -432,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
|
||||
@@ -481,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?
|
||||
@@ -491,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
|
||||
|
||||
+480
-12
@@ -17,6 +17,7 @@ package caddyhttp
|
||||
import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -28,11 +29,16 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common"
|
||||
"github.com/google/cel-go/common/operators"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"github.com/google/cel-go/ext"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
"github.com/google/cel-go/parser"
|
||||
"go.uber.org/zap"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -62,6 +68,8 @@ type MatchExpression struct {
|
||||
expandedExpr string
|
||||
prg cel.Program
|
||||
ta ref.TypeAdapter
|
||||
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -83,7 +91,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)
|
||||
@@ -91,6 +101,29 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
|
||||
// our type adapter expands CEL's standard type support
|
||||
m.ta = celTypeAdapter{}
|
||||
|
||||
// initialize the CEL libraries from the Matcher implementations which
|
||||
// have been configured to support CEL.
|
||||
matcherLibProducers := []CELLibraryProducer{}
|
||||
for _, info := range caddy.GetModules("http.matchers") {
|
||||
p, ok := info.New().(CELLibraryProducer)
|
||||
if ok {
|
||||
matcherLibProducers = append(matcherLibProducers, p)
|
||||
}
|
||||
}
|
||||
// Assemble the compilation and program options from the different library
|
||||
// producers into a single cel.Library implementation.
|
||||
matcherEnvOpts := []cel.EnvOption{}
|
||||
matcherProgramOpts := []cel.ProgramOption{}
|
||||
for _, producer := range matcherLibProducers {
|
||||
l, err := producer.CELLibrary(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing CEL library for %T: %v", producer, err)
|
||||
}
|
||||
matcherEnvOpts = append(matcherEnvOpts, l.CompileOptions()...)
|
||||
matcherProgramOpts = append(matcherProgramOpts, l.ProgramOptions()...)
|
||||
}
|
||||
matcherLib := cel.Lib(NewMatcherCELLibrary(matcherEnvOpts, matcherProgramOpts))
|
||||
|
||||
// create the CEL environment
|
||||
env, err := cel.NewEnv(
|
||||
cel.Declarations(
|
||||
@@ -102,6 +135,7 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
|
||||
),
|
||||
cel.CustomTypeAdapter(m.ta),
|
||||
ext.Strings(),
|
||||
matcherLib,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up CEL environment: %v", err)
|
||||
@@ -109,7 +143,7 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
|
||||
|
||||
// parse and type-check the expression
|
||||
checked, issues := env.Compile(m.expandedExpr)
|
||||
if issues != nil && issues.Err() != nil {
|
||||
if issues.Err() != nil {
|
||||
return fmt.Errorf("compiling CEL program: %s", issues.Err())
|
||||
}
|
||||
|
||||
@@ -121,6 +155,7 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
|
||||
|
||||
// compile the "program"
|
||||
m.prg, err = env.Program(checked,
|
||||
cel.EvalOptions(cel.OptOptimize),
|
||||
cel.Functions(
|
||||
&functions.Overload{
|
||||
Operator: placeholderFuncName,
|
||||
@@ -128,7 +163,6 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("compiling CEL program: %s", err)
|
||||
}
|
||||
@@ -137,14 +171,17 @@ 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{}{
|
||||
"request": celHTTPRequest{r},
|
||||
})
|
||||
celReq := celHTTPRequest{r}
|
||||
out, _, err := m.prg.Eval(celReq)
|
||||
if err != nil {
|
||||
m.log.Error("evaluating expression", zap.Error(err))
|
||||
SetVar(r.Context(), MatcherErrorVarKey, err)
|
||||
return false
|
||||
}
|
||||
if outBool, ok := out.Value().(bool); ok {
|
||||
return outBool
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
@@ -166,13 +203,15 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
|
||||
if !ok {
|
||||
return types.NewErr(
|
||||
"invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
|
||||
lhs.Type())
|
||||
lhs.Type(),
|
||||
)
|
||||
}
|
||||
phStr, ok := rhs.(types.String)
|
||||
if !ok {
|
||||
return types.NewErr(
|
||||
"invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
|
||||
rhs.Type())
|
||||
rhs.Type(),
|
||||
)
|
||||
}
|
||||
|
||||
repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
@@ -184,10 +223,23 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
|
||||
// httpRequestCELType is the type representation of a native HTTP request.
|
||||
var httpRequestCELType = types.NewTypeValue("http.Request", traits.ReceiverType)
|
||||
|
||||
// cellHTTPRequest wraps an http.Request with
|
||||
// methods to satisfy the ref.Val interface.
|
||||
// celHTTPRequest wraps an http.Request with ref.Val interface methods.
|
||||
//
|
||||
// This type also implements the interpreter.Activation interface which
|
||||
// drops allocation costs for CEL expression evaluations by roughly half.
|
||||
type celHTTPRequest struct{ *http.Request }
|
||||
|
||||
func (cr celHTTPRequest) ResolveName(name string) (interface{}, bool) {
|
||||
if name == "request" {
|
||||
return cr, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (cr celHTTPRequest) Parent() interpreter.Activation {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
return cr.Request, nil
|
||||
}
|
||||
@@ -241,12 +293,428 @@ func (celTypeAdapter) NativeToValue(value interface{}) ref.Val {
|
||||
return types.DefaultTypeAdapter.NativeToValue(value)
|
||||
}
|
||||
|
||||
// CELLibraryProducer provide CEL libraries that expose a Matcher
|
||||
// implementation as a first class function within the CEL expression
|
||||
// matcher.
|
||||
type CELLibraryProducer interface {
|
||||
// CELLibrary creates a cel.Library which makes it possible to use the
|
||||
// target object within CEL expression matchers.
|
||||
CELLibrary(caddy.Context) (cel.Library, error)
|
||||
}
|
||||
|
||||
// CELMatcherImpl creates a new cel.Library based on the following pieces of
|
||||
// data:
|
||||
//
|
||||
// - macroName: the function name to be used within CEL. This will be a macro
|
||||
// and not a function proper.
|
||||
// - funcName: the function overload name generated by the CEL macro used to
|
||||
// represent the matcher.
|
||||
// - matcherDataTypes: the argument types to the macro.
|
||||
// - fac: a matcherFactory implementation which converts from CEL constant
|
||||
// values to a Matcher instance.
|
||||
//
|
||||
// Note, macro names and function names must not collide with other macros or
|
||||
// functions exposed within CEL expressions, or an error will be produced
|
||||
// during the expression matcher plan time.
|
||||
//
|
||||
// The existing CELMatcherImpl support methods are configured to support a
|
||||
// limited set of function signatures. For strong type validation you may need
|
||||
// to provide a custom macro which does a more detailed analysis of the CEL
|
||||
// literal provided to the macro as an argument.
|
||||
func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*exprpb.Type, fac CELMatcherFactory) (cel.Library, error) {
|
||||
requestType := decls.NewObjectType("http.Request")
|
||||
var macro parser.Macro
|
||||
switch len(matcherDataTypes) {
|
||||
case 1:
|
||||
matcherDataType := matcherDataTypes[0]
|
||||
if isCELStringListType(matcherDataType) {
|
||||
macro = parser.NewGlobalVarArgMacro(macroName, celMatcherStringListMacroExpander(funcName))
|
||||
} else if isCELStringType(matcherDataType) {
|
||||
macro = parser.NewGlobalMacro(macroName, 1, celMatcherStringMacroExpander(funcName))
|
||||
} else if isCELJSONType(matcherDataType) {
|
||||
macro = parser.NewGlobalMacro(macroName, 1, celMatcherJSONMacroExpander(funcName))
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported matcher data type: %s", cel.FormatType(matcherDataType))
|
||||
}
|
||||
case 2:
|
||||
if isCELStringType(matcherDataTypes[0]) && isCELStringType(matcherDataTypes[1]) {
|
||||
macro = parser.NewGlobalMacro(macroName, 2, celMatcherStringListMacroExpander(funcName))
|
||||
matcherDataTypes = []*exprpb.Type{CelTypeListString}
|
||||
} else {
|
||||
return nil, fmt.Errorf(
|
||||
"unsupported matcher data type: %s, %s",
|
||||
cel.FormatType(matcherDataTypes[0]), cel.FormatType(matcherDataTypes[1]),
|
||||
)
|
||||
}
|
||||
case 3:
|
||||
if isCELStringType(matcherDataTypes[0]) && isCELStringType(matcherDataTypes[1]) && isCELStringType(matcherDataTypes[2]) {
|
||||
macro = parser.NewGlobalMacro(macroName, 3, celMatcherStringListMacroExpander(funcName))
|
||||
matcherDataTypes = []*exprpb.Type{CelTypeListString}
|
||||
} else {
|
||||
return nil, fmt.Errorf(
|
||||
"unsupported matcher data type: %s, %s, %s",
|
||||
cel.FormatType(matcherDataTypes[0]), cel.FormatType(matcherDataTypes[1]), cel.FormatType(matcherDataTypes[2]),
|
||||
)
|
||||
}
|
||||
}
|
||||
envOptions := []cel.EnvOption{
|
||||
cel.Macros(macro),
|
||||
cel.Declarations(
|
||||
decls.NewFunction(funcName,
|
||||
decls.NewOverload(
|
||||
funcName,
|
||||
append([]*exprpb.Type{requestType}, matcherDataTypes...),
|
||||
decls.Bool,
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
programOptions := []cel.ProgramOption{
|
||||
cel.CustomDecorator(CELMatcherDecorator(funcName, fac)),
|
||||
cel.Functions(
|
||||
&functions.Overload{
|
||||
Operator: funcName,
|
||||
Binary: CELMatcherRuntimeFunction(funcName, fac),
|
||||
},
|
||||
),
|
||||
}
|
||||
return NewMatcherCELLibrary(envOptions, programOptions), nil
|
||||
}
|
||||
|
||||
// CELMatcherFactory converts a constant CEL value into a RequestMatcher.
|
||||
type CELMatcherFactory func(data ref.Val) (RequestMatcher, error)
|
||||
|
||||
// matcherCELLibrary is a simplistic configurable cel.Library implementation.
|
||||
type matcherCELLibary struct {
|
||||
envOptions []cel.EnvOption
|
||||
programOptions []cel.ProgramOption
|
||||
}
|
||||
|
||||
// NewMatcherCELLibrary creates a matcherLibrary from option setes.
|
||||
func NewMatcherCELLibrary(envOptions []cel.EnvOption, programOptions []cel.ProgramOption) cel.Library {
|
||||
return &matcherCELLibary{
|
||||
envOptions: envOptions,
|
||||
programOptions: programOptions,
|
||||
}
|
||||
}
|
||||
|
||||
func (lib *matcherCELLibary) CompileOptions() []cel.EnvOption {
|
||||
return lib.envOptions
|
||||
}
|
||||
|
||||
func (lib *matcherCELLibary) ProgramOptions() []cel.ProgramOption {
|
||||
return lib.programOptions
|
||||
}
|
||||
|
||||
// CELMatcherDecorator matches a call overload generated by a CEL macro
|
||||
// that takes a single argument, and optimizes the implementation to precompile
|
||||
// the matcher and return a function that references the precompiled and
|
||||
// provisioned matcher.
|
||||
func CELMatcherDecorator(funcName string, fac CELMatcherFactory) interpreter.InterpretableDecorator {
|
||||
return func(i interpreter.Interpretable) (interpreter.Interpretable, error) {
|
||||
call, ok := i.(interpreter.InterpretableCall)
|
||||
if !ok {
|
||||
return i, nil
|
||||
}
|
||||
if call.OverloadID() != funcName {
|
||||
return i, nil
|
||||
}
|
||||
callArgs := call.Args()
|
||||
reqAttr, ok := callArgs[0].(interpreter.InterpretableAttribute)
|
||||
if !ok {
|
||||
return nil, errors.New("missing 'request' argument")
|
||||
}
|
||||
nsAttr, ok := reqAttr.Attr().(interpreter.NamespacedAttribute)
|
||||
if !ok {
|
||||
return nil, errors.New("missing 'request' argument")
|
||||
}
|
||||
varNames := nsAttr.CandidateVariableNames()
|
||||
if len(varNames) != 1 || len(varNames) == 1 && varNames[0] != "request" {
|
||||
return nil, errors.New("missing 'request' argument")
|
||||
}
|
||||
matcherData, ok := callArgs[1].(interpreter.InterpretableConst)
|
||||
if !ok {
|
||||
// If the matcher arguments are not constant, then this means
|
||||
// they contain a Caddy placeholder reference and the evaluation
|
||||
// and matcher provisioning should be handled at dynamically.
|
||||
return i, nil
|
||||
}
|
||||
matcher, err := fac(matcherData.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return interpreter.NewCall(
|
||||
i.ID(), funcName, funcName+"_opt",
|
||||
[]interpreter.Interpretable{reqAttr},
|
||||
func(args ...ref.Val) ref.Val {
|
||||
// The request value, guaranteed to be of type celHTTPRequest
|
||||
celReq := args[0]
|
||||
// If needed this call could be changed to convert the value
|
||||
// to a *http.Request using CEL's ConvertToNative method.
|
||||
httpReq := celReq.Value().(celHTTPRequest)
|
||||
return types.Bool(matcher.Match(httpReq.Request))
|
||||
},
|
||||
), nil
|
||||
}
|
||||
}
|
||||
|
||||
// CELMatcherRuntimeFunction creates a function binding for when the input to the matcher
|
||||
// is dynamically resolved rather than a set of static constant values.
|
||||
func CELMatcherRuntimeFunction(funcName string, fac CELMatcherFactory) functions.BinaryOp {
|
||||
return func(celReq, matcherData ref.Val) ref.Val {
|
||||
matcher, err := fac(matcherData)
|
||||
if err != nil {
|
||||
return types.NewErr(err.Error())
|
||||
}
|
||||
httpReq := celReq.Value().(celHTTPRequest)
|
||||
return types.Bool(matcher.Match(httpReq.Request))
|
||||
}
|
||||
}
|
||||
|
||||
// celMatcherStringListMacroExpander validates that the macro is called
|
||||
// with a variable number of string arguments (at least one).
|
||||
//
|
||||
// The arguments are collected into a single list argument the following
|
||||
// function call returned: <funcName>(request, [args])
|
||||
func celMatcherStringListMacroExpander(funcName string) parser.MacroExpander {
|
||||
return func(eh parser.ExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
matchArgs := []*exprpb.Expr{}
|
||||
if len(args) == 0 {
|
||||
return nil, &common.Error{
|
||||
Message: "matcher requires at least one argument",
|
||||
}
|
||||
}
|
||||
for _, arg := range args {
|
||||
if isCELStringExpr(arg) {
|
||||
matchArgs = append(matchArgs, arg)
|
||||
} else {
|
||||
return nil, &common.Error{
|
||||
Location: eh.OffsetLocation(arg.GetId()),
|
||||
Message: "matcher arguments must be string constants",
|
||||
}
|
||||
}
|
||||
}
|
||||
return eh.GlobalCall(funcName, eh.Ident("request"), eh.NewList(matchArgs...)), nil
|
||||
}
|
||||
}
|
||||
|
||||
// celMatcherStringMacroExpander validates that the macro is called a single
|
||||
// string argument.
|
||||
//
|
||||
// The following function call is returned: <funcName>(request, arg)
|
||||
func celMatcherStringMacroExpander(funcName string) parser.MacroExpander {
|
||||
return func(eh parser.ExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
if len(args) != 1 {
|
||||
return nil, &common.Error{
|
||||
Message: "matcher requires one argument",
|
||||
}
|
||||
}
|
||||
if isCELStringExpr(args[0]) {
|
||||
return eh.GlobalCall(funcName, eh.Ident("request"), args[0]), nil
|
||||
}
|
||||
return nil, &common.Error{
|
||||
Location: eh.OffsetLocation(args[0].GetId()),
|
||||
Message: "matcher argument must be a string literal",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// celMatcherStringMacroExpander validates that the macro is called a single
|
||||
// map literal argument.
|
||||
//
|
||||
// The following function call is returned: <funcName>(request, arg)
|
||||
func celMatcherJSONMacroExpander(funcName string) parser.MacroExpander {
|
||||
return func(eh parser.ExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
if len(args) != 1 {
|
||||
return nil, &common.Error{
|
||||
Message: "matcher requires a map literal argument",
|
||||
}
|
||||
}
|
||||
arg := args[0]
|
||||
switch arg.GetExprKind().(type) {
|
||||
case *exprpb.Expr_StructExpr:
|
||||
structExpr := arg.GetStructExpr()
|
||||
if structExpr.GetMessageName() != "" {
|
||||
return nil, &common.Error{
|
||||
Location: eh.OffsetLocation(arg.GetId()),
|
||||
Message: fmt.Sprintf(
|
||||
"matcher input must be a map literal, not a %s",
|
||||
structExpr.GetMessageName(),
|
||||
),
|
||||
}
|
||||
}
|
||||
for _, entry := range structExpr.GetEntries() {
|
||||
isStringPlaceholder := isCELStringExpr(entry.GetMapKey())
|
||||
if !isStringPlaceholder {
|
||||
return nil, &common.Error{
|
||||
Location: eh.OffsetLocation(entry.GetId()),
|
||||
Message: "matcher map keys must be string literals",
|
||||
}
|
||||
}
|
||||
isStringListPlaceholder := isCELStringExpr(entry.GetValue()) ||
|
||||
isCELStringListLiteral(entry.GetValue())
|
||||
if !isStringListPlaceholder {
|
||||
return nil, &common.Error{
|
||||
Location: eh.OffsetLocation(entry.GetValue().GetId()),
|
||||
Message: "matcher map values must be string or list literals",
|
||||
}
|
||||
}
|
||||
}
|
||||
return eh.GlobalCall(funcName, eh.Ident("request"), arg), nil
|
||||
}
|
||||
|
||||
return nil, &common.Error{
|
||||
Location: eh.OffsetLocation(arg.GetId()),
|
||||
Message: "matcher requires a map literal argument",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CELValueToMapStrList converts a CEL value to a map[string][]string
|
||||
//
|
||||
// Earlier validation stages should guarantee that the value has this type
|
||||
// at compile time, and that the runtime value type is map[string]interface{}.
|
||||
// The reason for the slight difference in value type is that CEL allows for
|
||||
// map literals containing heterogeneous values, in this case string and list
|
||||
// of string.
|
||||
func CELValueToMapStrList(data ref.Val) (map[string][]string, error) {
|
||||
mapStrType := reflect.TypeOf(map[string]interface{}{})
|
||||
mapStrRaw, err := data.ConvertToNative(mapStrType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapStrIface := mapStrRaw.(map[string]interface{})
|
||||
mapStrListStr := make(map[string][]string, len(mapStrIface))
|
||||
for k, v := range mapStrIface {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
mapStrListStr[k] = []string{val}
|
||||
case types.String:
|
||||
mapStrListStr[k] = []string{string(val)}
|
||||
case []string:
|
||||
mapStrListStr[k] = val
|
||||
case []ref.Val:
|
||||
convVals := make([]string, len(val))
|
||||
for i, elem := range val {
|
||||
strVal, ok := elem.(types.String)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported value type in header match: %T", val)
|
||||
}
|
||||
convVals[i] = string(strVal)
|
||||
}
|
||||
mapStrListStr[k] = convVals
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported value type in header match: %T", val)
|
||||
}
|
||||
}
|
||||
return mapStrListStr, nil
|
||||
}
|
||||
|
||||
// isCELJSONType returns whether the type corresponds to JSON input.
|
||||
func isCELJSONType(t *exprpb.Type) bool {
|
||||
switch t.GetTypeKind().(type) {
|
||||
case *exprpb.Type_MapType_:
|
||||
mapType := t.GetMapType()
|
||||
return isCELStringType(mapType.GetKeyType()) && mapType.GetValueType().GetDyn() != nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELStringType returns whether the type corresponds to a string.
|
||||
func isCELStringType(t *exprpb.Type) bool {
|
||||
switch t.GetTypeKind().(type) {
|
||||
case *exprpb.Type_Primitive:
|
||||
return t.GetPrimitive() == exprpb.Type_STRING
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELStringExpr indicates whether the expression is a supported string expression
|
||||
func isCELStringExpr(e *exprpb.Expr) bool {
|
||||
return isCELStringLiteral(e) || isCELCaddyPlaceholderCall(e) || isCELConcatCall(e)
|
||||
}
|
||||
|
||||
// isCELStringLiteral returns whether the expression is a CEL string literal.
|
||||
func isCELStringLiteral(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_ConstExpr:
|
||||
constant := e.GetConstExpr()
|
||||
switch constant.GetConstantKind().(type) {
|
||||
case *exprpb.Constant_StringValue:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELCaddyPlaceholderCall returns whether the expression is a caddy placeholder call.
|
||||
func isCELCaddyPlaceholderCall(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_CallExpr:
|
||||
call := e.GetCallExpr()
|
||||
if call.GetFunction() == "caddyPlaceholder" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELConcatCall tests whether the expression is a concat function (+) with string, placeholder, or
|
||||
// other concat call arguments.
|
||||
func isCELConcatCall(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_CallExpr:
|
||||
call := e.GetCallExpr()
|
||||
if call.GetTarget() != nil {
|
||||
return false
|
||||
}
|
||||
if call.GetFunction() != operators.Add {
|
||||
return false
|
||||
}
|
||||
for _, arg := range call.GetArgs() {
|
||||
if !isCELStringExpr(arg) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELStringListType returns whether the type corresponds to a list of strings.
|
||||
func isCELStringListType(t *exprpb.Type) bool {
|
||||
switch t.GetTypeKind().(type) {
|
||||
case *exprpb.Type_ListType_:
|
||||
return isCELStringType(t.GetListType().GetElemType())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELStringListLiteral returns whether the expression resolves to a list literal
|
||||
// containing only string constants or a placeholder call.
|
||||
func isCELStringListLiteral(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_ListExpr:
|
||||
list := e.GetListExpr()
|
||||
for _, elem := range list.GetElements() {
|
||||
if !isCELStringExpr(elem) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Variables used for replacing Caddy placeholders in CEL
|
||||
// expressions with a proper CEL function call; this is
|
||||
// just for syntactic sugar.
|
||||
var (
|
||||
placeholderRegexp = regexp.MustCompile(`{([\w.-]+)}`)
|
||||
placeholderRegexp = regexp.MustCompile(`{([a-zA-Z][\w.-]+)}`)
|
||||
placeholderExpansion = `caddyPlaceholder(request, "${1}")`
|
||||
|
||||
CelTypeListString = decls.NewListType(decls.String)
|
||||
CelTypeJson = decls.NewMapType(decls.String, decls.Dyn)
|
||||
)
|
||||
|
||||
var httpRequestObjectType = decls.NewObjectType("http.Request")
|
||||
|
||||
@@ -19,12 +19,462 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk
|
||||
eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG
|
||||
A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
||||
iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF
|
||||
z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+
|
||||
fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ
|
||||
BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A
|
||||
AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+
|
||||
eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
||||
3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH
|
||||
9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g=
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
matcherTests = []struct {
|
||||
name string
|
||||
expression *MatchExpression
|
||||
urlTarget string
|
||||
httpMethod string
|
||||
httpHeader *http.Header
|
||||
wantErr bool
|
||||
wantResult bool
|
||||
clientCertificate []byte
|
||||
}{
|
||||
{
|
||||
name: "boolean matches succeed for placeholder http.request.tls.client.subject",
|
||||
expression: &MatchExpression{
|
||||
Expr: "{http.request.tls.client.subject} == 'CN=client.localdomain'",
|
||||
},
|
||||
clientCertificate: clientCert,
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "header matches (MatchHeader)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `header({'Field': 'foo'})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "header error (MatchHeader)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `header('foo')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "header_regexp matches (MatchHeaderRE)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `header_regexp('Field', 'fo{2}')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "header_regexp matches with name (MatchHeaderRE)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `header_regexp('foo', 'Field', 'fo{2}')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "header_regexp does not match (MatchHeaderRE)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `header_regexp('foo', 'Nope', 'fo{2}')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
||||
wantResult: false,
|
||||
},
|
||||
{
|
||||
name: "header_regexp error (MatchHeaderRE)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `header_regexp('foo')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
httpHeader: &http.Header{"Field": []string{"foo", "bar"}},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "host matches localhost (MatchHost)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `host('localhost')`,
|
||||
},
|
||||
urlTarget: "http://localhost",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "host matches (MatchHost)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `host('*.example.com')`,
|
||||
},
|
||||
urlTarget: "https://foo.example.com",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "host does not match (MatchHost)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `host('example.net', '*.example.com')`,
|
||||
},
|
||||
urlTarget: "https://foo.example.org",
|
||||
wantResult: false,
|
||||
},
|
||||
{
|
||||
name: "host error (MatchHost)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `host(80)`,
|
||||
},
|
||||
urlTarget: "http://localhost:80",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "method does not match (MatchMethod)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `method('PUT')`,
|
||||
},
|
||||
urlTarget: "https://foo.example.com",
|
||||
httpMethod: "GET",
|
||||
wantResult: false,
|
||||
},
|
||||
{
|
||||
name: "method matches (MatchMethod)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `method('DELETE', 'PUT', 'POST')`,
|
||||
},
|
||||
urlTarget: "https://foo.example.com",
|
||||
httpMethod: "PUT",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "method error not enough arguments (MatchMethod)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `method()`,
|
||||
},
|
||||
urlTarget: "https://foo.example.com",
|
||||
httpMethod: "PUT",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "path matches substring (MatchPath)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path('*substring*')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/substring/bar.txt",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "path does not match (MatchPath)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path('/foo')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/bar",
|
||||
wantResult: false,
|
||||
},
|
||||
{
|
||||
name: "path matches end url fragment (MatchPath)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path('/foo')`,
|
||||
},
|
||||
urlTarget: "https://example.com/FOO",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "path matches end fragment with substring prefix (MatchPath)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path('/foo*')`,
|
||||
},
|
||||
urlTarget: "https://example.com/FOOOOO",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "path matches one of multiple (MatchPath)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path('/foo', '/foo/*', '/bar', '/bar/*', '/baz', '/baz*')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "path_regexp with empty regex matches empty path (MatchPathRE)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path_regexp('')`,
|
||||
},
|
||||
urlTarget: "https://example.com/",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "path_regexp with slash regex matches empty path (MatchPathRE)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path_regexp('/')`,
|
||||
},
|
||||
urlTarget: "https://example.com/",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "path_regexp matches end url fragment (MatchPathRE)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path_regexp('^/foo')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "path_regexp does not match fragment at end (MatchPathRE)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `path_regexp('bar_at_start', '^/bar')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/bar",
|
||||
wantResult: false,
|
||||
},
|
||||
{
|
||||
name: "protocol matches (MatchProtocol)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `protocol('HTTPs')`,
|
||||
},
|
||||
urlTarget: "https://example.com",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "protocol does not match (MatchProtocol)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `protocol('grpc')`,
|
||||
},
|
||||
urlTarget: "https://example.com",
|
||||
wantResult: false,
|
||||
},
|
||||
{
|
||||
name: "protocol invocation error no args (MatchProtocol)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `protocol()`,
|
||||
},
|
||||
urlTarget: "https://example.com",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "protocol invocation error too many args (MatchProtocol)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `protocol('grpc', 'https')`,
|
||||
},
|
||||
urlTarget: "https://example.com",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "protocol invocation error wrong arg type (MatchProtocol)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `protocol(true)`,
|
||||
},
|
||||
urlTarget: "https://example.com",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "query does not match against a specific value (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query({"debug": "1"})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantResult: false,
|
||||
},
|
||||
{
|
||||
name: "query matches against a specific value (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query({"debug": "1"})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/?debug=1",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "query matches against multiple values (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query({"debug": ["0", "1", {http.request.uri.query.debug}+"1"]})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/?debug=1",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "query matches against a wildcard (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query({"debug": ["*"]})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/?debug=something",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "query matches against a placeholder value (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query({"debug": {http.request.uri.query.debug}})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/?debug=1",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "query error bad map key type (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query({1: "1"})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "query error typed struct instead of map (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query(Message{field: "1"})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "query error bad map value type (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query({"debug": 1})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/?debug=1",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "query error no args (MatchQuery)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `query()`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo/?debug=1",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remote_ip error no args (MatchRemoteIP)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `remote_ip()`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remote_ip single IP match (MatchRemoteIP)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `remote_ip('192.0.2.1')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "remote_ip forwarded (MatchRemoteIP)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `remote_ip('forwarded', '192.0.2.1')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "remote_ip forwarded not first (MatchRemoteIP)",
|
||||
expression: &MatchExpression{
|
||||
Expr: `remote_ip('192.0.2.1', 'forwarded')`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestMatchExpressionMatch(t *testing.T) {
|
||||
for _, tst := range matcherTests {
|
||||
tc := tst
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.expression.Provision(caddy.Context{})
|
||||
if err != nil {
|
||||
if !tc.wantErr {
|
||||
t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(tc.httpMethod, tc.urlTarget, nil)
|
||||
if tc.httpHeader != nil {
|
||||
req.Header = *tc.httpHeader
|
||||
}
|
||||
repl := caddy.NewReplacer()
|
||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||
req = req.WithContext(ctx)
|
||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||
|
||||
if tc.clientCertificate != nil {
|
||||
block, _ := pem.Decode(clientCert)
|
||||
if block == nil {
|
||||
t.Fatalf("failed to decode PEM certificate")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode PEM certificate: %v", err)
|
||||
}
|
||||
|
||||
req.TLS = &tls.ConnectionState{
|
||||
PeerCertificates: []*x509.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
if tc.expression.Match(req) != tc.wantResult {
|
||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMatchExpressionMatch(b *testing.B) {
|
||||
for _, tst := range matcherTests {
|
||||
tc := tst
|
||||
if tc.wantErr {
|
||||
continue
|
||||
}
|
||||
b.Run(tst.name, func(b *testing.B) {
|
||||
tc.expression.Provision(caddy.Context{})
|
||||
req := httptest.NewRequest(tc.httpMethod, tc.urlTarget, nil)
|
||||
if tc.httpHeader != nil {
|
||||
req.Header = *tc.httpHeader
|
||||
}
|
||||
repl := caddy.NewReplacer()
|
||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||
req = req.WithContext(ctx)
|
||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||
if tc.clientCertificate != nil {
|
||||
block, _ := pem.Decode(clientCert)
|
||||
if block == nil {
|
||||
b.Fatalf("failed to decode PEM certificate")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
b.Fatalf("failed to decode PEM certificate: %v", err)
|
||||
}
|
||||
|
||||
req.TLS = &tls.ConnectionState{
|
||||
PeerCertificates: []*x509.Certificate{cert},
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tc.expression.Match(req)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchExpressionProvision(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -54,71 +504,3 @@ func TestMatchExpressionProvision(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchExpressionMatch(t *testing.T) {
|
||||
|
||||
clientCert := []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk
|
||||
eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG
|
||||
A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
||||
iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF
|
||||
z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+
|
||||
fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ
|
||||
BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A
|
||||
AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+
|
||||
eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
||||
3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH
|
||||
9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g=
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expression *MatchExpression
|
||||
wantErr bool
|
||||
wantResult bool
|
||||
clientCertificate []byte
|
||||
}{
|
||||
{
|
||||
name: "boolean matches succeed for placeholder http.request.tls.client.subject",
|
||||
expression: &MatchExpression{
|
||||
Expr: "{http.request.tls.client.subject} == 'CN=client.localdomain'",
|
||||
},
|
||||
clientCertificate: clientCert,
|
||||
wantResult: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.expression.Provision(caddy.Context{}); (err != nil) != tt.wantErr {
|
||||
t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "https://example.com/foo", nil)
|
||||
repl := caddy.NewReplacer()
|
||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||
req = req.WithContext(ctx)
|
||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||
|
||||
if tt.clientCertificate != nil {
|
||||
block, _ := pem.Decode(clientCert)
|
||||
if block == nil {
|
||||
t.Fatalf("failed to decode PEM certificate")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode PEM certificate: %v", err)
|
||||
}
|
||||
|
||||
req.TLS = &tls.ConnectionState{
|
||||
PeerCertificates: []*x509.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
if tt.expression.Match(req) != tt.wantResult {
|
||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tt.wantResult, tt.expression)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
log.Printf("Caddy 2 serving static files on %s", listen)
|
||||
log.Printf("Caddy serving static files on %s", listen)
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,14 @@ import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common"
|
||||
"github.com/google/cel-go/common/operators"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
"github.com/google/cel-go/parser"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -139,6 +147,110 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression file({'root': '/srv', 'try_files': [{http.request.uri.path}, '/index.php'], 'try_policy': 'first_exist', 'split_path': ['.php']})
|
||||
func (MatchFile) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
requestType := decls.NewObjectType("http.Request")
|
||||
envOptions := []cel.EnvOption{
|
||||
cel.Macros(parser.NewGlobalVarArgMacro("file", celFileMatcherMacroExpander())),
|
||||
cel.Declarations(
|
||||
decls.NewFunction("file",
|
||||
decls.NewOverload("file_request_map",
|
||||
[]*exprpb.Type{requestType, caddyhttp.CelTypeJson},
|
||||
decls.Bool,
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
matcherFactory := func(data ref.Val) (caddyhttp.RequestMatcher, error) {
|
||||
values, err := caddyhttp.CELValueToMapStrList(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var root string
|
||||
if len(values["root"]) > 0 {
|
||||
root = values["root"][0]
|
||||
}
|
||||
|
||||
var try_policy string
|
||||
if len(values["try_policy"]) > 0 {
|
||||
root = values["try_policy"][0]
|
||||
}
|
||||
|
||||
m := MatchFile{
|
||||
Root: root,
|
||||
TryFiles: values["try_files"],
|
||||
TryPolicy: try_policy,
|
||||
SplitPath: values["split_path"],
|
||||
}
|
||||
|
||||
err = m.Provision(ctx)
|
||||
return m, err
|
||||
}
|
||||
|
||||
programOptions := []cel.ProgramOption{
|
||||
cel.CustomDecorator(caddyhttp.CELMatcherDecorator("file_request_map", matcherFactory)),
|
||||
cel.Functions(
|
||||
&functions.Overload{
|
||||
Operator: "file_request_map",
|
||||
Binary: caddyhttp.CELMatcherRuntimeFunction("file_request_map", matcherFactory),
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
return caddyhttp.NewMatcherCELLibrary(envOptions, programOptions), nil
|
||||
}
|
||||
|
||||
func celFileMatcherMacroExpander() parser.MacroExpander {
|
||||
return func(eh parser.ExprHelper, target *exprpb.Expr, args []*exprpb.Expr) (*exprpb.Expr, *common.Error) {
|
||||
if len(args) == 0 {
|
||||
return nil, &common.Error{
|
||||
Message: "matcher requires at least one argument",
|
||||
}
|
||||
}
|
||||
if len(args) == 1 {
|
||||
arg := args[0]
|
||||
if isCELStringLiteral(arg) || isCELCaddyPlaceholderCall(arg) {
|
||||
return eh.GlobalCall("file",
|
||||
eh.Ident("request"),
|
||||
eh.NewMap(
|
||||
eh.NewMapEntry(eh.LiteralString("try_files"), eh.NewList(arg)),
|
||||
),
|
||||
), nil
|
||||
}
|
||||
if isCELTryFilesLiteral(arg) {
|
||||
return eh.GlobalCall("file", eh.Ident("request"), arg), nil
|
||||
}
|
||||
return nil, &common.Error{
|
||||
Location: eh.OffsetLocation(arg.GetId()),
|
||||
Message: "matcher requires either a map or string literal argument",
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if !(isCELStringLiteral(arg) || isCELCaddyPlaceholderCall(arg)) {
|
||||
return nil, &common.Error{
|
||||
Location: eh.OffsetLocation(arg.GetId()),
|
||||
Message: "matcher only supports repeated string literal arguments",
|
||||
}
|
||||
}
|
||||
}
|
||||
return eh.GlobalCall("file",
|
||||
eh.Ident("request"),
|
||||
eh.NewMap(
|
||||
eh.NewMapEntry(
|
||||
eh.LiteralString("try_files"), eh.NewList(args...),
|
||||
),
|
||||
),
|
||||
), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up m's defaults.
|
||||
func (m *MatchFile) Provision(_ caddy.Context) error {
|
||||
if m.Root == "" {
|
||||
@@ -359,6 +471,107 @@ func indexFold(haystack, needle string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// isCELMapLiteral returns whether the expression resolves to a map literal containing
|
||||
// only string keys with or a placeholder call.
|
||||
func isCELTryFilesLiteral(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_StructExpr:
|
||||
structExpr := e.GetStructExpr()
|
||||
if structExpr.GetMessageName() != "" {
|
||||
return false
|
||||
}
|
||||
for _, entry := range structExpr.GetEntries() {
|
||||
mapKey := entry.GetMapKey()
|
||||
mapVal := entry.GetValue()
|
||||
if !isCELStringLiteral(mapKey) {
|
||||
return false
|
||||
}
|
||||
mapKeyStr := mapKey.GetConstExpr().GetStringValue()
|
||||
if mapKeyStr == "try_files" || mapKeyStr == "split_path" {
|
||||
if !isCELStringListLiteral(mapVal) {
|
||||
return false
|
||||
}
|
||||
} else if mapKeyStr == "try_policy" || mapKeyStr == "root" {
|
||||
if !(isCELStringExpr(mapVal)) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELStringExpr indicates whether the expression is a supported string expression
|
||||
func isCELStringExpr(e *exprpb.Expr) bool {
|
||||
return isCELStringLiteral(e) || isCELCaddyPlaceholderCall(e) || isCELConcatCall(e)
|
||||
}
|
||||
|
||||
// isCELStringLiteral returns whether the expression is a CEL string literal.
|
||||
func isCELStringLiteral(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_ConstExpr:
|
||||
constant := e.GetConstExpr()
|
||||
switch constant.GetConstantKind().(type) {
|
||||
case *exprpb.Constant_StringValue:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELCaddyPlaceholderCall returns whether the expression is a caddy placeholder call.
|
||||
func isCELCaddyPlaceholderCall(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_CallExpr:
|
||||
call := e.GetCallExpr()
|
||||
if call.GetFunction() == "caddyPlaceholder" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELConcatCall tests whether the expression is a concat function (+) with string, placeholder, or
|
||||
// other concat call arguments.
|
||||
func isCELConcatCall(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_CallExpr:
|
||||
call := e.GetCallExpr()
|
||||
if call.GetTarget() != nil {
|
||||
return false
|
||||
}
|
||||
if call.GetFunction() != operators.Add {
|
||||
return false
|
||||
}
|
||||
for _, arg := range call.GetArgs() {
|
||||
if !isCELStringExpr(arg) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isCELStringListLiteral returns whether the expression resolves to a list literal
|
||||
// containing only string constants or a placeholder call.
|
||||
func isCELStringListLiteral(e *exprpb.Expr) bool {
|
||||
switch e.GetExprKind().(type) {
|
||||
case *exprpb.Expr_ListExpr:
|
||||
list := e.GetListExpr()
|
||||
for _, elem := range list.GetElements() {
|
||||
if !isCELStringExpr(elem) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
tryPolicyFirstExist = "first_exist"
|
||||
tryPolicyLargestSize = "largest_size"
|
||||
@@ -368,6 +581,7 @@ const (
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Validator = (*MatchFile)(nil)
|
||||
_ caddyhttp.RequestMatcher = (*MatchFile)(nil)
|
||||
_ caddy.Validator = (*MatchFile)(nil)
|
||||
_ caddyhttp.RequestMatcher = (*MatchFile)(nil)
|
||||
_ caddyhttp.CELLibraryProducer = (*MatchFile)(nil)
|
||||
)
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
package fileserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
@@ -259,3 +262,109 @@ func TestFirstSplit(t *testing.T) {
|
||||
t.Errorf("Expected remainder %s but got %s", expectedRemainder, remainder)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
expressionTests = []struct {
|
||||
name string
|
||||
expression *caddyhttp.MatchExpression
|
||||
urlTarget string
|
||||
httpMethod string
|
||||
httpHeader *http.Header
|
||||
wantErr bool
|
||||
wantResult bool
|
||||
clientCertificate []byte
|
||||
}{
|
||||
{
|
||||
name: "file error no args (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file()`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "file error bad try files (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file({"try_file": ["bad_arg"]})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "file match short pattern index.php (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file("index.php")`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "file match short pattern foo.txt (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file({http.request.uri.path})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo.txt",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "file match index.php (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file({"root": "./testdata", "try_files": [{http.request.uri.path}, "/index.php"]})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "file match long pattern foo.txt (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file({"root": "./testdata", "try_files": [{http.request.uri.path}]})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo.txt",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "file match long pattern foo.txt with concatenation (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file({"root": ".", "try_files": ["./testdata" + {http.request.uri.path}]})`,
|
||||
},
|
||||
urlTarget: "https://example.com/foo.txt",
|
||||
wantResult: true,
|
||||
},
|
||||
{
|
||||
name: "file not match long pattern (MatchFile)",
|
||||
expression: &caddyhttp.MatchExpression{
|
||||
Expr: `file({"root": "./testdata", "try_files": [{http.request.uri.path}]})`,
|
||||
},
|
||||
urlTarget: "https://example.com/nopenope.txt",
|
||||
wantResult: false,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestMatchExpressionMatch(t *testing.T) {
|
||||
for _, tst := range expressionTests {
|
||||
tc := tst
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.expression.Provision(caddy.Context{})
|
||||
if err != nil {
|
||||
if !tc.wantErr {
|
||||
t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tc.wantErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(tc.httpMethod, tc.urlTarget, nil)
|
||||
if tc.httpHeader != nil {
|
||||
req.Header = *tc.httpHeader
|
||||
}
|
||||
repl := caddyhttp.NewTestReplacer(req)
|
||||
repl.Set("http.vars.root", "./testdata")
|
||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
if tc.expression.Match(req) != tc.wantResult {
|
||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tc.wantResult, tc.expression.Expr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,10 +118,16 @@ type HeaderOps struct {
|
||||
// Sets HTTP headers; replaces existing header fields.
|
||||
Set http.Header `json:"set,omitempty"`
|
||||
|
||||
// Names of HTTP header fields to delete.
|
||||
// Names of HTTP header fields to delete. Basic wildcards are supported:
|
||||
//
|
||||
// - Start with `*` for all field names with the given suffix;
|
||||
// - End with `*` for all field names with the given prefix;
|
||||
// - Start and end with `*` for all field names containing a substring.
|
||||
Delete []string `json:"delete,omitempty"`
|
||||
|
||||
// Performs substring replacements of HTTP headers in-situ.
|
||||
// Performs in-situ substring replacements of HTTP headers.
|
||||
// Keys are the field names on which to perform the associated replacements.
|
||||
// If the field name is `*`, the replacements are performed on all header fields.
|
||||
Replace map[string][]Replacement `json:"replace,omitempty"`
|
||||
}
|
||||
|
||||
@@ -208,7 +214,29 @@ func (ops HeaderOps) ApplyTo(hdr http.Header, repl *caddy.Replacer) {
|
||||
|
||||
// delete
|
||||
for _, fieldName := range ops.Delete {
|
||||
hdr.Del(repl.ReplaceAll(fieldName, ""))
|
||||
fieldName = strings.ToLower(repl.ReplaceAll(fieldName, ""))
|
||||
switch {
|
||||
case strings.HasPrefix(fieldName, "*") && strings.HasSuffix(fieldName, "*"):
|
||||
for existingField := range hdr {
|
||||
if strings.Contains(strings.ToLower(existingField), fieldName[1:len(fieldName)-1]) {
|
||||
delete(hdr, existingField)
|
||||
}
|
||||
}
|
||||
case strings.HasPrefix(fieldName, "*"):
|
||||
for existingField := range hdr {
|
||||
if strings.HasSuffix(strings.ToLower(existingField), fieldName[1:]) {
|
||||
delete(hdr, existingField)
|
||||
}
|
||||
}
|
||||
case strings.HasSuffix(fieldName, "*"):
|
||||
for existingField := range hdr {
|
||||
if strings.HasPrefix(strings.ToLower(existingField), fieldName[:len(fieldName)-1]) {
|
||||
delete(hdr, existingField)
|
||||
}
|
||||
}
|
||||
default:
|
||||
hdr.Del(fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
// replace
|
||||
|
||||
@@ -79,6 +79,26 @@ func TestHandler(t *testing.T) {
|
||||
"Keep-Me": []string{"i swear i'm innocent"},
|
||||
},
|
||||
},
|
||||
{
|
||||
handler: Handler{
|
||||
Request: &HeaderOps{
|
||||
Delete: []string{
|
||||
"*-suffix",
|
||||
"prefix-*",
|
||||
"*_*",
|
||||
},
|
||||
},
|
||||
},
|
||||
reqHeader: http.Header{
|
||||
"Header-Suffix": []string{"lalala"},
|
||||
"Prefix-Test": []string{"asdf"},
|
||||
"Host_Header": []string{"silly django... sigh"}, // see issue #4830
|
||||
"Keep-Me": []string{"foofoofoo"},
|
||||
},
|
||||
expectedReqHeader: http.Header{
|
||||
"Keep-Me": []string{"foofoofoo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
handler: Handler{
|
||||
Request: &HeaderOps{
|
||||
|
||||
@@ -56,6 +56,11 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
if len(handler.Destinations) == 0 {
|
||||
return nil, h.Err("missing destination argument(s)")
|
||||
}
|
||||
for _, dest := range handler.Destinations {
|
||||
if shorthand := httpcaddyfile.WasReplacedPlaceholderShorthand(dest); shorthand != "" {
|
||||
return nil, h.Errf("destination %s conflicts with a Caddyfile placeholder shorthand", shorthand)
|
||||
}
|
||||
}
|
||||
|
||||
// mappings
|
||||
for h.NextBlock(0) {
|
||||
|
||||
@@ -16,6 +16,7 @@ package caddyhttp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -30,7 +32,12 @@ import (
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"go.uber.org/zap"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -291,6 +298,29 @@ outer:
|
||||
return false
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression host('localhost')
|
||||
func (MatchHost) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
return CELMatcherImpl(
|
||||
"host",
|
||||
"host_match_request_list",
|
||||
[]*exprpb.Type{CelTypeListString},
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
refStringList := reflect.TypeOf([]string{})
|
||||
strList, err := data.ConvertToNative(refStringList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matcher := MatchHost(strList.([]string))
|
||||
err = matcher.Provision(ctx)
|
||||
return matcher, err
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// fuzzy returns true if the given hostname h is not a specific
|
||||
// hostname, e.g. has placeholders or wildcards.
|
||||
func (MatchHost) fuzzy(h string) bool { return strings.ContainsAny(h, "{*") }
|
||||
@@ -396,6 +426,33 @@ func (m MatchPath) Match(r *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression path('*substring*', '*suffix')
|
||||
func (MatchPath) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
return CELMatcherImpl(
|
||||
// name of the macro, this is the function name that users see when writing expressions.
|
||||
"path",
|
||||
// name of the function that the macro will be rewritten to call.
|
||||
"path_match_request_list",
|
||||
// internal data type of the MatchPath value.
|
||||
[]*exprpb.Type{CelTypeListString},
|
||||
// function to convert a constant list of strings to a MatchPath instance.
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
refStringList := reflect.TypeOf([]string{})
|
||||
strList, err := data.ConvertToNative(refStringList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matcher := MatchPath(strList.([]string))
|
||||
err = matcher.Provision(ctx)
|
||||
return matcher, err
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (m *MatchPath) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
@@ -440,6 +497,50 @@ func (m MatchPathRE) Match(r *http.Request) bool {
|
||||
return m.MatchRegexp.Match(cleanedPath, repl)
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression path_regexp('^/bar')
|
||||
func (MatchPathRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
unnamedPattern, err := CELMatcherImpl(
|
||||
"path_regexp",
|
||||
"path_regexp_request_string",
|
||||
[]*exprpb.Type{decls.String},
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
pattern := data.(types.String)
|
||||
matcher := MatchPathRE{MatchRegexp{Pattern: string(pattern)}}
|
||||
err := matcher.Provision(ctx)
|
||||
return matcher, err
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namedPattern, err := CELMatcherImpl(
|
||||
"path_regexp",
|
||||
"path_regexp_request_string_string",
|
||||
[]*exprpb.Type{decls.String, decls.String},
|
||||
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 := MatchPathRE{MatchRegexp{Name: strParams[0], Pattern: strParams[1]}}
|
||||
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
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (MatchMethod) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
@@ -469,6 +570,27 @@ func (m MatchMethod) Match(r *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression method('PUT', 'POST')
|
||||
func (MatchMethod) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||
return CELMatcherImpl(
|
||||
"method",
|
||||
"method_request_list",
|
||||
[]*exprpb.Type{CelTypeListString},
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
refStringList := reflect.TypeOf([]string{})
|
||||
strList, err := data.ConvertToNative(refStringList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return MatchMethod(strList.([]string)), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (MatchQuery) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
@@ -518,6 +640,26 @@ func (m MatchQuery) Match(r *http.Request) bool {
|
||||
return len(m) == 0 && len(r.URL.Query()) == 0
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression query({'sort': 'asc'}) || query({'foo': ['*bar*', 'baz']})
|
||||
func (MatchQuery) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||
return CELMatcherImpl(
|
||||
"query",
|
||||
"query_matcher_request_map",
|
||||
[]*exprpb.Type{CelTypeJson},
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
mapStrListStr, err := CELValueToMapStrList(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return MatchQuery(url.Values(mapStrListStr)), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (MatchHeader) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
@@ -573,6 +715,27 @@ func (m MatchHeader) Match(r *http.Request) bool {
|
||||
return matchHeaders(r.Header, http.Header(m), r.Host, repl)
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression header({'content-type': 'image/png'})
|
||||
// expression header({'foo': ['bar', 'baz']}) // match bar or baz
|
||||
func (MatchHeader) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||
return CELMatcherImpl(
|
||||
"header",
|
||||
"header_matcher_request_map",
|
||||
[]*exprpb.Type{CelTypeJson},
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
mapStrListStr, err := CELValueToMapStrList(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return MatchHeader(http.Header(mapStrListStr)), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// getHeaderFieldVals returns the field values for the given fieldName from input.
|
||||
// The host parameter should be obtained from the http.Request.Host field since
|
||||
// net/http removes it from the header map.
|
||||
@@ -710,6 +873,57 @@ func (m MatchHeaderRE) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression header_regexp('foo', 'Field', 'fo+')
|
||||
func (MatchHeaderRE) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
unnamedPattern, err := CELMatcherImpl(
|
||||
"header_regexp",
|
||||
"header_regexp_request_string_string",
|
||||
[]*exprpb.Type{decls.String, decls.String},
|
||||
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 := MatchHeaderRE{}
|
||||
matcher[strParams[0]] = &MatchRegexp{Pattern: strParams[1], Name: ""}
|
||||
err = matcher.Provision(ctx)
|
||||
return matcher, err
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namedPattern, err := CELMatcherImpl(
|
||||
"header_regexp",
|
||||
"header_regexp_request_string_string_string",
|
||||
[]*exprpb.Type{decls.String, decls.String, decls.String},
|
||||
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 := MatchHeaderRE{}
|
||||
matcher[strParams[1]] = &MatchRegexp{Pattern: strParams[2], Name: strParams[0]}
|
||||
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
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (MatchProtocol) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
@@ -743,6 +957,26 @@ func (m *MatchProtocol) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression protocol('https')
|
||||
func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
|
||||
return CELMatcherImpl(
|
||||
"protocol",
|
||||
"protocol_request_string",
|
||||
[]*exprpb.Type{decls.String},
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
protocolStr, ok := data.(types.String)
|
||||
if !ok {
|
||||
return nil, errors.New("protocol argument was not a string")
|
||||
}
|
||||
return MatchProtocol(strings.ToLower(string(protocolStr))), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (MatchNot) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
@@ -867,6 +1101,17 @@ func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
m.Forwarded = true
|
||||
continue
|
||||
}
|
||||
if d.Val() == "private_ranges" {
|
||||
m.Ranges = append(m.Ranges, []string{
|
||||
"192.168.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.1/8",
|
||||
"fd00::/8",
|
||||
"::1",
|
||||
}...)
|
||||
continue
|
||||
}
|
||||
m.Ranges = append(m.Ranges, d.Val())
|
||||
}
|
||||
if d.NextBlock(0) {
|
||||
@@ -876,6 +1121,46 @@ func (m *MatchRemoteIP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CELLibrary produces options that expose this matcher for use in CEL
|
||||
// expression matchers.
|
||||
//
|
||||
// Example:
|
||||
// expression remote_ip('forwarded', '192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8')
|
||||
func (MatchRemoteIP) CELLibrary(ctx caddy.Context) (cel.Library, error) {
|
||||
return CELMatcherImpl(
|
||||
// name of the macro, this is the function name that users see when writing expressions.
|
||||
"remote_ip",
|
||||
// name of the function that the macro will be rewritten to call.
|
||||
"remote_ip_match_request_list",
|
||||
// internal data type of the MatchPath value.
|
||||
[]*exprpb.Type{CelTypeListString},
|
||||
// function to convert a constant list of strings to a MatchPath instance.
|
||||
func(data ref.Val) (RequestMatcher, error) {
|
||||
refStringList := reflect.TypeOf([]string{})
|
||||
strList, err := data.ConvertToNative(refStringList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := MatchRemoteIP{}
|
||||
|
||||
for _, input := range strList.([]string) {
|
||||
if input == "forwarded" {
|
||||
if len(m.Ranges) > 0 {
|
||||
return nil, errors.New("if used, 'forwarded' must be first argument")
|
||||
}
|
||||
m.Forwarded = true
|
||||
continue
|
||||
}
|
||||
m.Ranges = append(m.Ranges, input)
|
||||
}
|
||||
|
||||
err = m.Provision(ctx)
|
||||
return m, err
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Provision parses m's IP ranges, either from IP or CIDR expressions.
|
||||
func (m *MatchRemoteIP) Provision(ctx caddy.Context) error {
|
||||
m.logger = ctx.Logger(m)
|
||||
@@ -1051,7 +1336,9 @@ func (mre *MatchRegexp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var wordRE = regexp.MustCompile(`\w+`)
|
||||
var (
|
||||
wordRE = regexp.MustCompile(`\w+`)
|
||||
)
|
||||
|
||||
const regexpPlaceholderPrefix = "http.regexp"
|
||||
|
||||
@@ -1092,6 +1379,18 @@ var (
|
||||
_ caddyfile.Unmarshaler = (*VarsMatcher)(nil)
|
||||
_ caddyfile.Unmarshaler = (*MatchVarsRE)(nil)
|
||||
|
||||
_ CELLibraryProducer = (*MatchHost)(nil)
|
||||
_ CELLibraryProducer = (*MatchPath)(nil)
|
||||
_ CELLibraryProducer = (*MatchPathRE)(nil)
|
||||
_ CELLibraryProducer = (*MatchMethod)(nil)
|
||||
_ CELLibraryProducer = (*MatchQuery)(nil)
|
||||
_ CELLibraryProducer = (*MatchHeader)(nil)
|
||||
_ CELLibraryProducer = (*MatchHeaderRE)(nil)
|
||||
_ CELLibraryProducer = (*MatchProtocol)(nil)
|
||||
_ CELLibraryProducer = (*MatchRemoteIP)(nil)
|
||||
// _ CELLibraryProducer = (*VarsMatcher)(nil)
|
||||
// _ CELLibraryProducer = (*MatchVarsRE)(nil)
|
||||
|
||||
_ json.Marshaler = (*MatchNot)(nil)
|
||||
_ json.Unmarshaler = (*MatchNot)(nil)
|
||||
)
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package reverseproxy
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -28,6 +27,7 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
@@ -86,10 +86,12 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
// buffer_responses
|
||||
// max_buffer_size <size>
|
||||
//
|
||||
// # header manipulation
|
||||
// # request manipulation
|
||||
// trusted_proxies [private_ranges] <ranges...>
|
||||
// header_up [+|-]<field> [<value|regexp> [<replacement>]]
|
||||
// header_down [+|-]<field> [<value|regexp> [<replacement>]]
|
||||
// method <method>
|
||||
// rewrite <to>
|
||||
//
|
||||
// # round trip
|
||||
// transport <name> {
|
||||
@@ -552,16 +554,16 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
case 2:
|
||||
// some lint checks, I guess
|
||||
if strings.EqualFold(args[0], "host") && (args[1] == "{hostport}" || args[1] == "{http.request.hostport}") {
|
||||
log.Printf("[WARNING] Unnecessary header_up ('Host' field): the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
caddy.Log().Named("caddyfile").Warn("Unnecessary header_up Host: the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
}
|
||||
if strings.EqualFold(args[0], "x-forwarded-for") && (args[1] == "{remote}" || args[1] == "{http.request.remote}" || args[1] == "{remote_host}" || args[1] == "{http.request.remote.host}") {
|
||||
log.Printf("[WARNING] Unnecessary header_up ('X-Forwarded-For' field): the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
}
|
||||
if strings.EqualFold(args[0], "x-forwarded-proto") && (args[1] == "{scheme}" || args[1] == "{http.request.scheme}") {
|
||||
log.Printf("[WARNING] Unnecessary header_up ('X-Forwarded-Proto' field): the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
}
|
||||
if strings.EqualFold(args[0], "x-forwarded-host") && (args[1] == "{host}" || args[1] == "{http.request.host}" || args[1] == "{hostport}" || args[1] == "{http.request.hostport}") {
|
||||
log.Printf("[WARNING] Unnecessary header_up ('X-Forwarded-Host' field): the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
caddy.Log().Named("caddyfile").Warn("Unnecessary header_up X-Forwarded-Host: the reverse proxy's default behavior is to pass headers to the upstream")
|
||||
}
|
||||
err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], "")
|
||||
case 3:
|
||||
@@ -601,6 +603,30 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return d.Err(err.Error())
|
||||
}
|
||||
|
||||
case "method":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if h.Rewrite == nil {
|
||||
h.Rewrite = &rewrite.Rewrite{}
|
||||
}
|
||||
h.Rewrite.Method = d.Val()
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "rewrite":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if h.Rewrite == nil {
|
||||
h.Rewrite = &rewrite.Rewrite{}
|
||||
}
|
||||
h.Rewrite.URI = d.Val()
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "transport":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
@@ -628,22 +654,24 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
|
||||
case "replace_status":
|
||||
args := d.RemainingArgs()
|
||||
if len(args) != 2 {
|
||||
return d.Errf("must have two arguments: a response matcher and a status code")
|
||||
if len(args) != 1 && len(args) != 2 {
|
||||
return d.Errf("must have one or two arguments: an optional response matcher, and a status code")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(args[0], matcherPrefix) {
|
||||
return d.Errf("must use a named response matcher, starting with '@'")
|
||||
}
|
||||
responseHandler := caddyhttp.ResponseHandler{}
|
||||
|
||||
foundMatcher, ok := h.responseMatchers[args[0]]
|
||||
if !ok {
|
||||
return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
|
||||
}
|
||||
|
||||
_, err := strconv.Atoi(args[1])
|
||||
if err != nil {
|
||||
return d.Errf("bad integer value '%s': %v", args[1], err)
|
||||
if len(args) == 2 {
|
||||
if !strings.HasPrefix(args[0], matcherPrefix) {
|
||||
return d.Errf("must use a named response matcher, starting with '@'")
|
||||
}
|
||||
foundMatcher, ok := h.responseMatchers[args[0]]
|
||||
if !ok {
|
||||
return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
|
||||
}
|
||||
responseHandler.Match = &foundMatcher
|
||||
responseHandler.StatusCode = caddyhttp.WeakString(args[1])
|
||||
} else if len(args) == 1 {
|
||||
responseHandler.StatusCode = caddyhttp.WeakString(args[0])
|
||||
}
|
||||
|
||||
// make sure there's no block, cause it doesn't make sense
|
||||
@@ -653,10 +681,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
|
||||
h.HandleResponse = append(
|
||||
h.HandleResponse,
|
||||
caddyhttp.ResponseHandler{
|
||||
Match: &foundMatcher,
|
||||
StatusCode: caddyhttp.WeakString(args[1]),
|
||||
},
|
||||
responseHandler,
|
||||
)
|
||||
|
||||
default:
|
||||
@@ -897,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)
|
||||
@@ -1024,6 +1063,15 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
}
|
||||
h.MaxConnsPerHost = num
|
||||
|
||||
case "except_ports":
|
||||
if h.TLS == nil {
|
||||
h.TLS = new(TLSConfig)
|
||||
}
|
||||
h.TLS.ExceptPorts = d.RemainingArgs()
|
||||
if len(h.TLS.ExceptPorts) == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
default:
|
||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -80,7 +80,7 @@ func (h CopyResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request
|
||||
hrc.isFinalized = true
|
||||
|
||||
// write the response
|
||||
return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger, false)
|
||||
return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger)
|
||||
}
|
||||
|
||||
// CopyResponseHeadersHandler is a special HTTP handler which may
|
||||
|
||||
@@ -196,7 +196,15 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
||||
// NOTE: we delete the tokens as we go so that the reverse_proxy
|
||||
// unmarshal doesn't see these subdirectives which it cannot handle
|
||||
for dispenser.Next() {
|
||||
for dispenser.NextBlock(0) && dispenser.Nesting() == 1 {
|
||||
for dispenser.NextBlock(0) {
|
||||
// ignore any sub-subdirectives that might
|
||||
// have the same name somewhere within
|
||||
// the reverse_proxy passthrough tokens
|
||||
if dispenser.Nesting() != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// parse the php_fastcgi subdirectives
|
||||
switch dispenser.Val() {
|
||||
case "root":
|
||||
if !dispenser.NextArg() {
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package forwardauth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
httpcaddyfile.RegisterDirective("forward_auth", parseCaddyfile)
|
||||
}
|
||||
|
||||
// parseCaddyfile parses the forward_auth directive, which has the same syntax
|
||||
// as the reverse_proxy directive (in fact, the reverse_proxy's directive
|
||||
// Unmarshaler is invoked by this function) but the resulting proxy is specially
|
||||
// configured for most™️ auth gateways that support forward auth. The typical
|
||||
// config which looks something like this:
|
||||
//
|
||||
// forward_auth auth-gateway:9091 {
|
||||
// uri /authenticate?redirect=https://auth.example.com
|
||||
// copy_headers Remote-User Remote-Email
|
||||
// }
|
||||
//
|
||||
// is equivalent to a reverse_proxy directive like this:
|
||||
//
|
||||
// reverse_proxy auth-gateway:9091 {
|
||||
// method GET
|
||||
// rewrite /authenticate?redirect=https://auth.example.com
|
||||
//
|
||||
// header_up X-Forwarded-Method {method}
|
||||
// header_up X-Forwarded-Uri {uri}
|
||||
//
|
||||
// @good status 2xx
|
||||
// handle_response @good {
|
||||
// request_header {
|
||||
// Remote-User {http.reverse_proxy.header.Remote-User}
|
||||
// Remote-Email {http.reverse_proxy.header.Remote-Email}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
||||
if !h.Next() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
// if the user specified a matcher token, use that
|
||||
// matcher in a route that wraps both of our routes;
|
||||
// either way, strip the matcher token and pass
|
||||
// the remaining tokens to the unmarshaler so that
|
||||
// we can gain the rest of the reverse_proxy syntax
|
||||
userMatcherSet, err := h.ExtractMatcherSet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make a new dispenser from the remaining tokens so that we
|
||||
// can reset the dispenser back to this point for the
|
||||
// reverse_proxy unmarshaler to read from it as well
|
||||
dispenser := h.NewFromNextSegment()
|
||||
|
||||
// create the reverse proxy handler
|
||||
rpHandler := &reverseproxy.Handler{
|
||||
// set up defaults for header_up; reverse_proxy already deals with
|
||||
// adding the other three X-Forwarded-* headers, but for this flow,
|
||||
// we want to also send along the incoming method and URI since this
|
||||
// request will have a rewritten URI and method.
|
||||
Headers: &headers.Handler{
|
||||
Request: &headers.HeaderOps{
|
||||
Set: http.Header{
|
||||
"X-Forwarded-Method": []string{"{http.request.method}"},
|
||||
"X-Forwarded-Uri": []string{"{http.request.uri}"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// we always rewrite the method to GET, which implicitly
|
||||
// turns off sending the incoming request's body, which
|
||||
// allows later middleware handlers to consume it
|
||||
Rewrite: &rewrite.Rewrite{
|
||||
Method: "GET",
|
||||
},
|
||||
|
||||
HandleResponse: []caddyhttp.ResponseHandler{},
|
||||
}
|
||||
|
||||
// collect the headers to copy from the auth response
|
||||
// onto the original request, so they can get passed
|
||||
// through to a backend app
|
||||
headersToCopy := make(map[string]string)
|
||||
|
||||
// read the subdirectives for configuring the forward_auth shortcut
|
||||
// NOTE: we delete the tokens as we go so that the reverse_proxy
|
||||
// unmarshal doesn't see these subdirectives which it cannot handle
|
||||
for dispenser.Next() {
|
||||
for dispenser.NextBlock(0) {
|
||||
// ignore any sub-subdirectives that might
|
||||
// have the same name somewhere within
|
||||
// the reverse_proxy passthrough tokens
|
||||
if dispenser.Nesting() != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// parse the forward_auth subdirectives
|
||||
switch dispenser.Val() {
|
||||
case "uri":
|
||||
if !dispenser.NextArg() {
|
||||
return nil, dispenser.ArgErr()
|
||||
}
|
||||
rpHandler.Rewrite.URI = dispenser.Val()
|
||||
dispenser.Delete()
|
||||
dispenser.Delete()
|
||||
|
||||
case "copy_headers":
|
||||
args := dispenser.RemainingArgs()
|
||||
hadBlock := false
|
||||
for nesting := dispenser.Nesting(); dispenser.NextBlock(nesting); {
|
||||
hadBlock = true
|
||||
args = append(args, dispenser.Val())
|
||||
}
|
||||
|
||||
dispenser.Delete() // directive name
|
||||
if hadBlock {
|
||||
dispenser.Delete() // opening brace
|
||||
dispenser.Delete() // closing brace
|
||||
}
|
||||
for range args {
|
||||
dispenser.Delete()
|
||||
}
|
||||
|
||||
for _, headerField := range args {
|
||||
if strings.Contains(headerField, ">") {
|
||||
parts := strings.Split(headerField, ">")
|
||||
headersToCopy[parts[0]] = parts[1]
|
||||
} else {
|
||||
headersToCopy[headerField] = headerField
|
||||
}
|
||||
}
|
||||
if len(headersToCopy) == 0 {
|
||||
return nil, dispenser.ArgErr()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset the dispenser after we're done so that the reverse_proxy
|
||||
// unmarshaler can read it from the start
|
||||
dispenser.Reset()
|
||||
|
||||
// the auth target URI must not be empty
|
||||
if rpHandler.Rewrite.URI == "" {
|
||||
return nil, dispenser.Errf("the 'uri' subdirective is required")
|
||||
}
|
||||
|
||||
// set up handler for good responses; when a response
|
||||
// has 2xx status, then we will copy some headers from
|
||||
// the response onto the original request, and allow
|
||||
// handling to continue down the middleware chain,
|
||||
// by _not_ executing a terminal handler.
|
||||
goodResponseHandler := caddyhttp.ResponseHandler{
|
||||
Match: &caddyhttp.ResponseMatcher{
|
||||
StatusCode: []int{2},
|
||||
},
|
||||
Routes: []caddyhttp.Route{},
|
||||
}
|
||||
|
||||
handler := &headers.Handler{
|
||||
Request: &headers.HeaderOps{
|
||||
Set: http.Header{},
|
||||
},
|
||||
}
|
||||
|
||||
// the list of headers to copy may be empty, but that's okay; we
|
||||
// need at least one handler in the routes for the response handling
|
||||
// logic in reverse_proxy to not skip this entry as empty.
|
||||
for from, to := range headersToCopy {
|
||||
handler.Request.Set[to] = []string{
|
||||
"{http.reverse_proxy.header." + from + "}",
|
||||
}
|
||||
}
|
||||
|
||||
goodResponseHandler.Routes = append(
|
||||
goodResponseHandler.Routes,
|
||||
caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
|
||||
handler,
|
||||
"handler",
|
||||
"headers",
|
||||
nil,
|
||||
)},
|
||||
},
|
||||
)
|
||||
|
||||
// note that when a response has any other status than 2xx, then we
|
||||
// use the reverse proxy's default behaviour of copying the response
|
||||
// back to the client, so we don't need to explicitly add a response
|
||||
// handler specifically for that behaviour; we do need the 2xx handler
|
||||
// though, to make handling fall through to handlers deeper in the chain.
|
||||
rpHandler.HandleResponse = append(rpHandler.HandleResponse, goodResponseHandler)
|
||||
|
||||
// the rest of the config is specified by the user
|
||||
// using the reverse_proxy directive syntax
|
||||
err = rpHandler.UnmarshalCaddyfile(dispenser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = rpHandler.FinalizeUnmarshalCaddyfile(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create the final reverse proxy route
|
||||
rpRoute := caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
|
||||
rpHandler,
|
||||
"handler",
|
||||
"reverse_proxy",
|
||||
nil,
|
||||
)},
|
||||
}
|
||||
|
||||
// apply the user's matcher if any
|
||||
if userMatcherSet != nil {
|
||||
rpRoute.MatcherSetsRaw = []caddy.ModuleMap{userMatcherSet}
|
||||
}
|
||||
|
||||
return []httpcaddyfile.ConfigValue{
|
||||
{
|
||||
Class: "route",
|
||||
Value: rpRoute,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
@@ -242,29 +243,83 @@ func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error)
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
// replaceTLSServername checks TLS servername to see if it needs replacing
|
||||
// if it does need replacing, it creates a new cloned HTTPTransport object to avoid any races
|
||||
// and does the replacing of the TLS servername on that and returns the new object
|
||||
// if no replacement is necessary it returns the original
|
||||
func (h *HTTPTransport) replaceTLSServername(repl *caddy.Replacer) *HTTPTransport {
|
||||
// check whether we have TLS and need to replace the servername in the TLSClientConfig
|
||||
if h.TLSEnabled() && strings.Contains(h.TLS.ServerName, "{") {
|
||||
// make a new h, "copy" the parts we don't need to touch, add a new *tls.Config and replace servername
|
||||
newtransport := &HTTPTransport{
|
||||
Resolver: h.Resolver,
|
||||
TLS: h.TLS,
|
||||
KeepAlive: h.KeepAlive,
|
||||
Compression: h.Compression,
|
||||
MaxConnsPerHost: h.MaxConnsPerHost,
|
||||
DialTimeout: h.DialTimeout,
|
||||
FallbackDelay: h.FallbackDelay,
|
||||
ResponseHeaderTimeout: h.ResponseHeaderTimeout,
|
||||
ExpectContinueTimeout: h.ExpectContinueTimeout,
|
||||
MaxResponseHeaderSize: h.MaxResponseHeaderSize,
|
||||
WriteBufferSize: h.WriteBufferSize,
|
||||
ReadBufferSize: h.ReadBufferSize,
|
||||
Versions: h.Versions,
|
||||
Transport: h.Transport.Clone(),
|
||||
h2cTransport: h.h2cTransport,
|
||||
}
|
||||
newtransport.Transport.TLSClientConfig.ServerName = repl.ReplaceAll(newtransport.Transport.TLSClientConfig.ServerName, "")
|
||||
return newtransport
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper.
|
||||
func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
h.SetScheme(req)
|
||||
// Try to replace TLS servername if needed
|
||||
repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
transport := h.replaceTLSServername(repl)
|
||||
|
||||
transport.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)
|
||||
}
|
||||
|
||||
return h.Transport.RoundTrip(req)
|
||||
return transport.Transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
// SetScheme ensures that the outbound request req
|
||||
// setScheme ensures that the outbound request req
|
||||
// has the scheme set in its URL; the underlying
|
||||
// http.Transport requires a scheme to be set.
|
||||
func (h *HTTPTransport) SetScheme(req *http.Request) {
|
||||
if req.URL.Scheme == "" {
|
||||
func (h *HTTPTransport) setScheme(req *http.Request) {
|
||||
if req.URL.Scheme != "" {
|
||||
return
|
||||
}
|
||||
if h.shouldUseTLS(req) {
|
||||
req.URL.Scheme = "https"
|
||||
} else {
|
||||
req.URL.Scheme = "http"
|
||||
if h.TLS != nil {
|
||||
req.URL.Scheme = "https"
|
||||
}
|
||||
}
|
||||
|
||||
// shouldUseTLS returns true if TLS should be used for req.
|
||||
func (h *HTTPTransport) shouldUseTLS(req *http.Request) bool {
|
||||
if h.TLS == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
port := req.URL.Port()
|
||||
for i := range h.TLS.ExceptPorts {
|
||||
if h.TLS.ExceptPorts[i] == port {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// TLSEnabled returns true if TLS is enabled.
|
||||
@@ -315,8 +370,30 @@ 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"`
|
||||
|
||||
// Skip TLS ports specifies a list of upstream ports on which TLS should not be
|
||||
// attempted even if it is configured. Handy when using dynamic upstreams that
|
||||
// return HTTP and HTTPS endpoints too.
|
||||
// When specified, TLS will automatically be configured on the transport.
|
||||
// The value can be a list of any valid tcp port numbers, default empty.
|
||||
ExceptPorts []string `json:"except_ports,omitempty"`
|
||||
}
|
||||
|
||||
// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
|
||||
@@ -386,7 +463,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 +491,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`.
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
@@ -136,6 +137,18 @@ type Handler struct {
|
||||
// used for the requests and responses (in bytes).
|
||||
MaxBufferSize int64 `json:"max_buffer_size,omitempty"`
|
||||
|
||||
// If configured, rewrites the copy of the upstream request.
|
||||
// Allows changing the request method and URI (path and query).
|
||||
// Since the rewrite is applied to the copy, it does not persist
|
||||
// past the reverse proxy handler.
|
||||
// If the method is changed to `GET` or `HEAD`, the request body
|
||||
// will not be copied to the backend. This allows a later request
|
||||
// handler -- either in a `handle_response` route, or after -- to
|
||||
// read the body.
|
||||
// By default, no rewrite is performed, and the method and URI
|
||||
// from the incoming request is used as-is for proxying.
|
||||
Rewrite *rewrite.Rewrite `json:"rewrite,omitempty"`
|
||||
|
||||
// List of handlers and their associated matchers to evaluate
|
||||
// after successful roundtrips. The first handler that matches
|
||||
// the response from a backend will be invoked. The response
|
||||
@@ -258,6 +271,13 @@ func (h *Handler) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if h.Rewrite != nil {
|
||||
err := h.Rewrite.Provision(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("provisioning rewrite: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// set up transport
|
||||
if h.Transport == nil {
|
||||
t := &HTTPTransport{}
|
||||
@@ -385,7 +405,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
// prepare the request for proxying; this is needed only once
|
||||
clonedReq, err := h.prepareRequest(r)
|
||||
clonedReq, err := h.prepareRequest(r, repl)
|
||||
if err != nil {
|
||||
return caddyhttp.Error(http.StatusInternalServerError,
|
||||
fmt.Errorf("preparing request for upstream round-trip: %v", err))
|
||||
@@ -412,7 +432,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
||||
var proxyErr error
|
||||
for {
|
||||
var done bool
|
||||
done, proxyErr = h.proxyLoopIteration(clonedReq, w, proxyErr, start, repl, reqHeader, reqHost, next)
|
||||
done, proxyErr = h.proxyLoopIteration(clonedReq, r, w, proxyErr, start, repl, reqHeader, reqHost, next)
|
||||
if done {
|
||||
break
|
||||
}
|
||||
@@ -429,7 +449,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
||||
// that has to be passed in, we brought this into its own method so that we could run defer more easily.
|
||||
// It returns true when the loop is done and should break; false otherwise. The error value returned should
|
||||
// be assigned to the proxyErr value for the next iteration of the loop (or the error handled after break).
|
||||
func (h *Handler) proxyLoopIteration(r *http.Request, w http.ResponseWriter, proxyErr error, start time.Time,
|
||||
func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w http.ResponseWriter, proxyErr error, start time.Time,
|
||||
repl *caddy.Replacer, reqHeader http.Header, reqHost string, next caddyhttp.Handler) (bool, error) {
|
||||
// get the updated list of upstreams
|
||||
upstreams := h.Upstreams
|
||||
@@ -503,8 +523,8 @@ func (h *Handler) proxyLoopIteration(r *http.Request, w http.ResponseWriter, pro
|
||||
}
|
||||
|
||||
// proxy the request to that upstream
|
||||
proxyErr = h.reverseProxy(w, r, repl, dialInfo, next)
|
||||
if proxyErr == nil || proxyErr == context.Canceled {
|
||||
proxyErr = h.reverseProxy(w, r, origReq, repl, dialInfo, next)
|
||||
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
|
||||
@@ -536,9 +556,20 @@ func (h *Handler) proxyLoopIteration(r *http.Request, w http.ResponseWriter, pro
|
||||
// properties of the cloned request and should be done just once (before
|
||||
// proxying) regardless of proxy retries. This assumes that no mutations
|
||||
// of the cloned request are performed by h during or after proxying.
|
||||
func (h Handler) prepareRequest(req *http.Request) (*http.Request, error) {
|
||||
func (h Handler) prepareRequest(req *http.Request, repl *caddy.Replacer) (*http.Request, error) {
|
||||
req = cloneRequest(req)
|
||||
|
||||
// if enabled, perform rewrites on the cloned request; if
|
||||
// the method is GET or HEAD, prevent the request body
|
||||
// from being copied to the upstream
|
||||
if h.Rewrite != nil {
|
||||
changed := h.Rewrite.Rewrite(req, repl)
|
||||
if changed && (h.Rewrite.Method == "GET" || h.Rewrite.Method == "HEAD") {
|
||||
req.ContentLength = 0
|
||||
req.Body = nil
|
||||
}
|
||||
}
|
||||
|
||||
// if enabled, buffer client request; this should only be
|
||||
// enabled if the upstream requires it and does not work
|
||||
// with "slow clients" (gunicorn, etc.) - this obviously
|
||||
@@ -547,7 +578,7 @@ func (h Handler) prepareRequest(req *http.Request) (*http.Request, error) {
|
||||
// attacks, so it is strongly recommended to only use this
|
||||
// feature if absolutely required, if read timeouts are
|
||||
// set, and if body size is limited
|
||||
if h.BufferRequests {
|
||||
if h.BufferRequests && req.Body != nil {
|
||||
req.Body = h.bufferedBody(req.Body)
|
||||
}
|
||||
|
||||
@@ -673,10 +704,7 @@ func (h Handler) addForwardedHeaders(req *http.Request) error {
|
||||
// we pass through the request Host as-is, but in situations
|
||||
// where we proxy over HTTPS, the user may need to override
|
||||
// Host themselves, so it's helpful to send the original too.
|
||||
host, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
host = req.Host // OK; there probably was no port
|
||||
}
|
||||
host := req.Host
|
||||
prior, ok, omit = lastHeaderValue(req.Header, "X-Forwarded-Host")
|
||||
if trusted && ok && prior != "" {
|
||||
host = prior
|
||||
@@ -691,7 +719,7 @@ func (h Handler) addForwardedHeaders(req *http.Request) error {
|
||||
// reverseProxy performs a round-trip to the given backend and processes the response with the client.
|
||||
// (This method is mostly the beginning of what was borrowed from the net/http/httputil package in the
|
||||
// Go standard library which was used as the foundation.)
|
||||
func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl *caddy.Replacer, di DialInfo, next caddyhttp.Handler) error {
|
||||
func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origReq *http.Request, repl *caddy.Replacer, di DialInfo, next caddyhttp.Handler) error {
|
||||
_ = di.Upstream.Host.countRequest(1)
|
||||
//nolint:errcheck
|
||||
defer di.Upstream.Host.countRequest(-1)
|
||||
@@ -756,18 +784,14 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl *
|
||||
res.Body = h.bufferedBody(res.Body)
|
||||
}
|
||||
|
||||
// the response body may get closed by a response handler,
|
||||
// and we need to keep track to make sure we don't try to copy
|
||||
// the response if it was already closed
|
||||
bodyClosed := false
|
||||
|
||||
// see if any response handler is configured for this response from the backend
|
||||
for i, rh := range h.HandleResponse {
|
||||
if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) {
|
||||
continue
|
||||
}
|
||||
|
||||
// if configured to only change the status code, do that then continue regular proxy response
|
||||
// if configured to only change the status code,
|
||||
// do that then continue regular proxy response
|
||||
if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" {
|
||||
statusCode, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, ""))
|
||||
if err != nil {
|
||||
@@ -798,45 +822,43 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, repl *
|
||||
// we make some data available via request context to child routes
|
||||
// so that they may inherit some options and functions from the
|
||||
// handler, and be able to copy the response.
|
||||
// we use the original request here, so that any routes from 'next'
|
||||
// see the original request rather than the proxy cloned request.
|
||||
hrc := &handleResponseContext{
|
||||
handler: h,
|
||||
response: res,
|
||||
start: start,
|
||||
logger: logger,
|
||||
}
|
||||
ctx := req.Context()
|
||||
ctx := origReq.Context()
|
||||
ctx = context.WithValue(ctx, proxyHandleResponseContextCtxKey, hrc)
|
||||
|
||||
// pass the request through the response handler routes
|
||||
routeErr := rh.Routes.Compile(next).ServeHTTP(rw, req.WithContext(ctx))
|
||||
routeErr := rh.Routes.Compile(next).ServeHTTP(rw, origReq.WithContext(ctx))
|
||||
|
||||
// if the response handler routes already finalized the response,
|
||||
// we can return early. It should be finalized if the routes executed
|
||||
// included a copy_response handler. If a fresh response was written
|
||||
// by the routes instead, then we still need to finalize the response
|
||||
// without copying the body.
|
||||
if routeErr == nil && hrc.isFinalized {
|
||||
return nil
|
||||
// close the response body afterwards, since we don't need it anymore;
|
||||
// either a route had 'copy_response' which already consumed the body,
|
||||
// or some other terminal handler ran which doesn't need the response
|
||||
// body after that point (e.g. 'file_server' for X-Accel-Redirect flow),
|
||||
// or we fell through to subsequent handlers past this proxy
|
||||
// (e.g. forward auth's 2xx response flow).
|
||||
if !hrc.isFinalized {
|
||||
res.Body.Close()
|
||||
}
|
||||
|
||||
// always close the response body afterwards, since it's expected
|
||||
// that the response handler routes will have written to the
|
||||
// response writer with a new body, if it wasn't already finalized.
|
||||
res.Body.Close()
|
||||
bodyClosed = true
|
||||
|
||||
// wrap any route error in roundtripSucceeded so caller knows that
|
||||
// the roundtrip was successful and to not retry
|
||||
if routeErr != nil {
|
||||
// wrap error in roundtripSucceeded so caller knows that
|
||||
// the roundtrip was successful and to not retry
|
||||
return roundtripSucceeded{routeErr}
|
||||
}
|
||||
|
||||
// we've already closed the body, so there's no use allowing
|
||||
// another response handler to run as well
|
||||
break
|
||||
// we're done handling the response, and we don't want to
|
||||
// fall through to the default finalize/copy behaviour
|
||||
return nil
|
||||
}
|
||||
|
||||
return h.finalizeResponse(rw, req, res, repl, start, logger, bodyClosed)
|
||||
// copy the response body and headers back to the upstream client
|
||||
return h.finalizeResponse(rw, req, res, repl, start, logger)
|
||||
}
|
||||
|
||||
// finalizeResponse prepares and copies the response.
|
||||
@@ -847,7 +869,6 @@ func (h Handler) finalizeResponse(
|
||||
repl *caddy.Replacer,
|
||||
start time.Time,
|
||||
logger *zap.Logger,
|
||||
bodyClosed bool,
|
||||
) error {
|
||||
// deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
|
||||
if res.StatusCode == http.StatusSwitchingProtocols {
|
||||
@@ -861,13 +882,6 @@ func (h Handler) finalizeResponse(
|
||||
res.Header.Del(h)
|
||||
}
|
||||
|
||||
// remove the content length if we're not going to be copying
|
||||
// from the response, because otherwise there'll be a mismatch
|
||||
// between bytes written and the advertised length
|
||||
if bodyClosed {
|
||||
res.Header.Del("Content-Length")
|
||||
}
|
||||
|
||||
// apply any response header operations
|
||||
if h.Headers != nil && h.Headers.Response != nil {
|
||||
if h.Headers.Response.Require == nil ||
|
||||
@@ -890,17 +904,16 @@ func (h Handler) finalizeResponse(
|
||||
}
|
||||
|
||||
rw.WriteHeader(res.StatusCode)
|
||||
if !bodyClosed {
|
||||
err := h.copyResponse(rw, res.Body, h.flushInterval(req, res))
|
||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||
if err != nil {
|
||||
// we're streaming the response and we've already written headers, so
|
||||
// there's nothing an error handler can do to recover at this point;
|
||||
// the standard lib's proxy panics at this point, but we'll just log
|
||||
// the error and abort the stream here
|
||||
h.logger.Error("aborting with incomplete response", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
err := h.copyResponse(rw, res.Body, h.flushInterval(req, res))
|
||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||
if err != nil {
|
||||
// we're streaming the response and we've already written headers, so
|
||||
// there's nothing an error handler can do to recover at this point;
|
||||
// the standard lib's proxy panics at this point, but we'll just log
|
||||
// the error and abort the stream here
|
||||
h.logger.Error("aborting with incomplete response", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(res.Trailer) > 0 {
|
||||
@@ -1150,6 +1163,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
|
||||
|
||||
@@ -514,21 +514,26 @@ func leastRequests(upstreams []*Upstream) *Upstream {
|
||||
return best[weakrand.Intn(len(best))]
|
||||
}
|
||||
|
||||
// hostByHashing returns an available host
|
||||
// from pool based on a hashable string s.
|
||||
// hostByHashing returns an available host from pool based on a hashable string s.
|
||||
func hostByHashing(pool []*Upstream, s string) *Upstream {
|
||||
poolLen := uint32(len(pool))
|
||||
if poolLen == 0 {
|
||||
return nil
|
||||
}
|
||||
index := hash(s) % poolLen
|
||||
for i := uint32(0); i < poolLen; i++ {
|
||||
upstream := pool[(index+i)%poolLen]
|
||||
if upstream.Available() {
|
||||
return upstream
|
||||
// Highest Random Weight (HRW, or "Rendezvous") hashing,
|
||||
// guarantees stability when the list of upstreams changes;
|
||||
// see https://medium.com/i0exception/rendezvous-hashing-8c00e2fb58b0,
|
||||
// https://randorithms.com/2020/12/26/rendezvous-hashing.html,
|
||||
// and https://en.wikipedia.org/wiki/Rendezvous_hashing.
|
||||
var highestHash uint32
|
||||
var upstream *Upstream
|
||||
for _, up := range pool {
|
||||
if !up.Available() {
|
||||
continue
|
||||
}
|
||||
h := hash(s + up.String()) // important to hash key and server together
|
||||
if h > highestHash {
|
||||
highestHash = h
|
||||
upstream = up
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return upstream
|
||||
}
|
||||
|
||||
// hash calculates a fast hash based on s.
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
|
||||
func testPool() UpstreamPool {
|
||||
return UpstreamPool{
|
||||
{Host: new(Host)},
|
||||
{Host: new(Host)},
|
||||
{Host: new(Host)},
|
||||
{Host: new(Host), Dial: "0.0.0.1"},
|
||||
{Host: new(Host), Dial: "0.0.0.2"},
|
||||
{Host: new(Host), Dial: "0.0.0.3"},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,13 +95,13 @@ func TestIPHashPolicy(t *testing.T) {
|
||||
// We should be able to predict where every request is routed.
|
||||
req.RemoteAddr = "172.0.0.1:80"
|
||||
h := ipHash.Select(pool, req, nil)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected ip hash policy host to be the second host.")
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.2:80"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected ip hash policy host to be the second host.")
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.3:80"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
@@ -117,13 +117,13 @@ func TestIPHashPolicy(t *testing.T) {
|
||||
// we should get the same results without a port
|
||||
req.RemoteAddr = "172.0.0.1"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected ip hash policy host to be the second host.")
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.2"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected ip hash policy host to be the second host.")
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.3"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
@@ -138,7 +138,7 @@ func TestIPHashPolicy(t *testing.T) {
|
||||
|
||||
// we should get a healthy host if the original host is unhealthy and a
|
||||
// healthy host is available
|
||||
req.RemoteAddr = "172.0.0.1"
|
||||
req.RemoteAddr = "172.0.0.4"
|
||||
pool[1].setHealthy(false)
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[2] {
|
||||
@@ -147,16 +147,16 @@ func TestIPHashPolicy(t *testing.T) {
|
||||
|
||||
req.RemoteAddr = "172.0.0.2"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[2] {
|
||||
t.Error("Expected ip hash policy host to be the third host.")
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
}
|
||||
pool[1].setHealthy(true)
|
||||
|
||||
req.RemoteAddr = "172.0.0.3"
|
||||
pool[2].setHealthy(false)
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
if h != pool[1] {
|
||||
t.Error("Expected ip hash policy host to be the second host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.4"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
@@ -167,29 +167,29 @@ func TestIPHashPolicy(t *testing.T) {
|
||||
// We should be able to resize the host pool and still be able to predict
|
||||
// where a req will be routed with the same IP's used above
|
||||
pool = UpstreamPool{
|
||||
{Host: new(Host)},
|
||||
{Host: new(Host)},
|
||||
{Host: new(Host), Dial: "0.0.0.2"},
|
||||
{Host: new(Host), Dial: "0.0.0.3"},
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.1:80"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
if h != pool[1] {
|
||||
t.Error("Expected ip hash policy host to be the second host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.2:80"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected ip hash policy host to be the second host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.3:80"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.4:80"
|
||||
req.RemoteAddr = "172.0.0.3:80"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected ip hash policy host to be the second host.")
|
||||
}
|
||||
req.RemoteAddr = "172.0.0.4:80"
|
||||
h = ipHash.Select(pool, req, nil)
|
||||
if h != pool[0] {
|
||||
t.Error("Expected ip hash policy host to be the first host.")
|
||||
}
|
||||
|
||||
// We should get nil when there are no healthy hosts
|
||||
pool[0].setHealthy(false)
|
||||
@@ -252,14 +252,14 @@ func TestURIHashPolicy(t *testing.T) {
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
h := uriPolicy.Select(pool, request, nil)
|
||||
if h != pool[0] {
|
||||
t.Error("Expected uri policy host to be the first host.")
|
||||
if h != pool[2] {
|
||||
t.Error("Expected uri policy host to be the third host.")
|
||||
}
|
||||
|
||||
pool[0].setHealthy(false)
|
||||
pool[2].setHealthy(false)
|
||||
h = uriPolicy.Select(pool, request, nil)
|
||||
if h != pool[1] {
|
||||
t.Error("Expected uri policy host to be the first host.")
|
||||
t.Error("Expected uri policy host to be the second host.")
|
||||
}
|
||||
|
||||
request = httptest.NewRequest(http.MethodGet, "/test_2", nil)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -372,7 +373,14 @@ func (u *UpstreamResolver) ParseAddresses() error {
|
||||
for _, v := range u.Addresses {
|
||||
addr, err := caddy.ParseNetworkAddress(v)
|
||||
if err != nil {
|
||||
return err
|
||||
// If a port wasn't specified for the resolver,
|
||||
// try defaulting to 53 and parse again
|
||||
if strings.Contains(err.Error(), "missing port in address") {
|
||||
addr, err = caddy.ParseNetworkAddress(v + ":53")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if addr.PortRangeSize() != 1 {
|
||||
return fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr)
|
||||
|
||||
@@ -106,7 +106,7 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
|
||||
zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: r}),
|
||||
)
|
||||
|
||||
changed := rewr.rewrite(r, repl, logger)
|
||||
changed := rewr.Rewrite(r, repl)
|
||||
|
||||
if changed {
|
||||
logger.Debug("rewrote request",
|
||||
@@ -121,7 +121,7 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
|
||||
// rewrite performs the rewrites on r using repl, which should
|
||||
// have been obtained from r, but is passed in for efficiency.
|
||||
// It returns true if any changes were made to r.
|
||||
func (rewr Rewrite) rewrite(r *http.Request, repl *caddy.Replacer, logger *zap.Logger) bool {
|
||||
func (rewr Rewrite) Rewrite(r *http.Request, repl *caddy.Replacer) bool {
|
||||
oldMethod := r.Method
|
||||
oldURI := r.RequestURI
|
||||
|
||||
@@ -135,13 +135,20 @@ func (rewr Rewrite) rewrite(r *http.Request, repl *caddy.Replacer, logger *zap.L
|
||||
// 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
|
||||
@@ -292,7 +303,7 @@ func TestRewrite(t *testing.T) {
|
||||
rep.re = re
|
||||
}
|
||||
|
||||
changed := tc.rule.rewrite(tc.input, repl, nil)
|
||||
changed := tc.rule.Rewrite(tc.input, repl)
|
||||
|
||||
if expected, actual := !reqEqual(originalInput, tc.input), changed; expected != actual {
|
||||
t.Errorf("Test %d: Expected changed=%t but was %t", i, expected, actual)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/requestbody"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/fastcgi"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy/forwardauth"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/tracing"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
@@ -36,6 +37,8 @@ func init() {
|
||||
//
|
||||
// ⚠️ Template functions/actions are still experimental, so they are subject to change.
|
||||
//
|
||||
// Custom template functions can be registered by creating a plugin module under the `http.handlers.templates.functions.*` namespace that implements the `CustomFunctions` interface.
|
||||
//
|
||||
// [All Sprig functions](https://masterminds.github.io/sprig/) are supported.
|
||||
//
|
||||
// In addition to the standard functions and the Sprig library, Caddy adds
|
||||
@@ -100,10 +103,11 @@ func init() {
|
||||
// {{ define "main" }}
|
||||
// content
|
||||
// {{ end }}
|
||||
// ```
|
||||
//
|
||||
// **index.html**
|
||||
// ```
|
||||
// {{ import "/path/to/file.html" }}
|
||||
// {{ import "/path/to/filename.html" }}
|
||||
// {{ template "main" }}
|
||||
// ```
|
||||
//
|
||||
@@ -234,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
|
||||
@@ -248,6 +275,14 @@ type Templates struct {
|
||||
// The template action delimiters. If set, must be precisely two elements:
|
||||
// the opening and closing delimiters. Default: `["{{", "}}"]`
|
||||
Delimiters []string `json:"delimiters,omitempty"`
|
||||
|
||||
customFuncs []template.FuncMap
|
||||
}
|
||||
|
||||
// Customfunctions is the interface for registering custom template functions.
|
||||
type CustomFunctions interface {
|
||||
// CustomTemplateFunctions should return the mapping from custom function names to implementations.
|
||||
CustomTemplateFunctions() template.FuncMap
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -260,6 +295,18 @@ func (Templates) CaddyModule() caddy.ModuleInfo {
|
||||
|
||||
// Provision provisions t.
|
||||
func (t *Templates) Provision(ctx caddy.Context) error {
|
||||
fnModInfos := caddy.GetModules("http.handlers.templates.functions")
|
||||
customFuncs := make([]template.FuncMap, len(fnModInfos), 0)
|
||||
for _, modInfo := range fnModInfos {
|
||||
mod := modInfo.New()
|
||||
fnMod, ok := mod.(CustomFunctions)
|
||||
if !ok {
|
||||
return fmt.Errorf("module %q does not satisfy the CustomFunctions interface", modInfo.ID)
|
||||
}
|
||||
customFuncs = append(customFuncs, fnMod.CustomTemplateFunctions())
|
||||
}
|
||||
t.customFuncs = customFuncs
|
||||
|
||||
if t.MIMETypes == nil {
|
||||
t.MIMETypes = defaultMIMETypes
|
||||
}
|
||||
@@ -330,10 +377,11 @@ func (t *Templates) executeTemplate(rr caddyhttp.ResponseRecorder, r *http.Reque
|
||||
}
|
||||
|
||||
ctx := &TemplateContext{
|
||||
Root: fs,
|
||||
Req: r,
|
||||
RespHeader: WrappedHeader{rr.Header()},
|
||||
config: t,
|
||||
Root: fs,
|
||||
Req: r,
|
||||
RespHeader: WrappedHeader{rr.Header()},
|
||||
config: t,
|
||||
CustomFuncs: t.customFuncs,
|
||||
}
|
||||
|
||||
err := ctx.executeTemplateInBuffer(r.URL.Path, rr.Buffer())
|
||||
|
||||
@@ -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"
|
||||
@@ -40,10 +42,11 @@ import (
|
||||
|
||||
// TemplateContext is the TemplateContext with which HTTP templates are executed.
|
||||
type TemplateContext struct {
|
||||
Root http.FileSystem
|
||||
Req *http.Request
|
||||
Args []interface{} // defined by arguments to funcInclude
|
||||
RespHeader WrappedHeader
|
||||
Root http.FileSystem
|
||||
Req *http.Request
|
||||
Args []interface{} // defined by arguments to funcInclude
|
||||
RespHeader WrappedHeader
|
||||
CustomFuncs []template.FuncMap // functions added by plugins
|
||||
|
||||
config *Templates
|
||||
tpl *template.Template
|
||||
@@ -62,6 +65,11 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
|
||||
// add sprig library
|
||||
c.tpl.Funcs(sprigFuncMap)
|
||||
|
||||
// add all custom functions
|
||||
for _, funcMap := range c.CustomFuncs {
|
||||
c.tpl.Funcs(funcMap)
|
||||
}
|
||||
|
||||
// add our own library
|
||||
c.tpl.Funcs(template.FuncMap{
|
||||
"include": c.funcInclude,
|
||||
@@ -75,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
|
||||
}
|
||||
@@ -392,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() {
|
||||
|
||||
@@ -133,7 +133,7 @@ func cmdTrust(fl caddycmd.Flags) (int, error) {
|
||||
ca := CA{
|
||||
log: caddy.Log(),
|
||||
root: rootCert,
|
||||
rootCertPath: adminAddr + path.Join(adminPKIEndpointBase, caID, "certificates"),
|
||||
rootCertPath: adminAddr + path.Join(adminPKIEndpointBase, "ca", caID),
|
||||
}
|
||||
|
||||
// Install the cert!
|
||||
@@ -207,7 +207,7 @@ func cmdUntrust(fl caddycmd.Flags) (int, error) {
|
||||
|
||||
// rootCertFromAdmin makes the API request to fetch the root certificate for the named CA via admin API.
|
||||
func rootCertFromAdmin(adminAddr string, caID string) (*x509.Certificate, error) {
|
||||
uri := path.Join(adminPKIEndpointBase, caID, "certificates")
|
||||
uri := path.Join(adminPKIEndpointBase, "ca", caID)
|
||||
|
||||
// Make the request to fetch the CA info
|
||||
resp, err := caddycmd.AdminAPIRequest(adminAddr, http.MethodGet, uri, make(http.Header), nil)
|
||||
|
||||
@@ -85,9 +85,11 @@ type ACMEIssuer struct {
|
||||
PreferredChains *ChainPreference `json:"preferred_chains,omitempty"`
|
||||
|
||||
rootPool *x509.CertPool
|
||||
template certmagic.ACMEIssuer
|
||||
magic *certmagic.Config
|
||||
logger *zap.Logger
|
||||
|
||||
template certmagic.ACMEIssuer // set at Provision
|
||||
magic *certmagic.Config // set at PreCheck
|
||||
issuer *certmagic.ACMEIssuer // set at PreCheck; result of template + magic
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -142,6 +144,7 @@ func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
|
||||
iss.Challenges.DNS.solver = &certmagic.DNS01Solver{
|
||||
DNSProvider: val.(certmagic.ACMEDNSProvider),
|
||||
TTL: time.Duration(iss.Challenges.DNS.TTL),
|
||||
PropagationDelay: time.Duration(iss.Challenges.DNS.PropagationDelay),
|
||||
PropagationTimeout: time.Duration(iss.Challenges.DNS.PropagationTimeout),
|
||||
Resolvers: iss.Challenges.DNS.Resolvers,
|
||||
OverrideDomain: iss.Challenges.DNS.OverrideDomain,
|
||||
@@ -216,30 +219,27 @@ func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) {
|
||||
// the ConfigSetter interface.
|
||||
func (iss *ACMEIssuer) SetConfig(cfg *certmagic.Config) {
|
||||
iss.magic = cfg
|
||||
iss.issuer = certmagic.NewACMEIssuer(cfg, iss.template)
|
||||
}
|
||||
|
||||
// TODO: I kind of hate how each call to these methods needs to
|
||||
// make a new ACME manager to fill in defaults before using; can
|
||||
// we find the right place to do that just once and then re-use?
|
||||
|
||||
// PreCheck implements the certmagic.PreChecker interface.
|
||||
func (iss *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error {
|
||||
return certmagic.NewACMEIssuer(iss.magic, iss.template).PreCheck(ctx, names, interactive)
|
||||
return iss.issuer.PreCheck(ctx, names, interactive)
|
||||
}
|
||||
|
||||
// Issue obtains a certificate for the given csr.
|
||||
func (iss *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||
return certmagic.NewACMEIssuer(iss.magic, iss.template).Issue(ctx, csr)
|
||||
return iss.issuer.Issue(ctx, csr)
|
||||
}
|
||||
|
||||
// IssuerKey returns the unique issuer key for the configured CA endpoint.
|
||||
func (iss *ACMEIssuer) IssuerKey() string {
|
||||
return certmagic.NewACMEIssuer(iss.magic, iss.template).IssuerKey()
|
||||
return iss.issuer.IssuerKey()
|
||||
}
|
||||
|
||||
// Revoke revokes the given certificate.
|
||||
func (iss *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource, reason int) error {
|
||||
return certmagic.NewACMEIssuer(iss.magic, iss.template).Revoke(ctx, cert, reason)
|
||||
return iss.issuer.Revoke(ctx, cert, reason)
|
||||
}
|
||||
|
||||
// GetACMEIssuer returns iss. This is useful when other types embed ACMEIssuer, because
|
||||
@@ -262,10 +262,13 @@ func (iss *ACMEIssuer) GetACMEIssuer() *ACMEIssuer { return iss }
|
||||
// eab <key_id> <mac_key>
|
||||
// trusted_roots <pem_files...>
|
||||
// dns <provider_name> [<options>]
|
||||
// propagation_delay <duration>
|
||||
// propagation_timeout <duration>
|
||||
// resolvers <dns_servers...>
|
||||
// dns_challenge_override_domain <domain>
|
||||
// preferred_chains [smallest] {
|
||||
// root_common_name <common_names...>
|
||||
// any_common_name <common_names...>
|
||||
// root_common_name <common_names...>
|
||||
// any_common_name <common_names...>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
@@ -389,14 +392,38 @@ func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return err
|
||||
}
|
||||
iss.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, nil)
|
||||
|
||||
case "propagation_delay":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
delayStr := d.Val()
|
||||
delay, err := caddy.ParseDuration(delayStr)
|
||||
if err != nil {
|
||||
return d.Errf("invalid propagation_delay duration %s: %v", delayStr, err)
|
||||
}
|
||||
if iss.Challenges == nil {
|
||||
iss.Challenges = new(ChallengesConfig)
|
||||
}
|
||||
if iss.Challenges.DNS == nil {
|
||||
iss.Challenges.DNS = new(DNSChallengeConfig)
|
||||
}
|
||||
iss.Challenges.DNS.PropagationDelay = caddy.Duration(delay)
|
||||
|
||||
case "propagation_timeout":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
timeoutStr := d.Val()
|
||||
timeout, err := caddy.ParseDuration(timeoutStr)
|
||||
if err != nil {
|
||||
return d.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err)
|
||||
var timeout time.Duration
|
||||
if timeoutStr == "-1" {
|
||||
timeout = time.Duration(-1)
|
||||
} else {
|
||||
var err error
|
||||
timeout, err = caddy.ParseDuration(timeoutStr)
|
||||
if err != nil {
|
||||
return d.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err)
|
||||
}
|
||||
}
|
||||
if iss.Challenges == nil {
|
||||
iss.Challenges = new(ChallengesConfig)
|
||||
|
||||
@@ -363,7 +363,13 @@ type DNSChallengeConfig struct {
|
||||
// The TTL of the TXT record used for the DNS challenge.
|
||||
TTL caddy.Duration `json:"ttl,omitempty"`
|
||||
|
||||
// How long to wait for DNS record to propagate.
|
||||
// How long to wait before starting propagation checks.
|
||||
// Default: 0 (no wait).
|
||||
PropagationDelay caddy.Duration `json:"propagation_delay,omitempty"`
|
||||
|
||||
// Maximum time to wait for temporary DNS record to appear.
|
||||
// Set to -1 to disable propagation checks.
|
||||
// Default: 2 minutes.
|
||||
PropagationTimeout caddy.Duration `json:"propagation_timeout,omitempty"`
|
||||
|
||||
// Custom DNS resolvers to prefer over system/built-in defaults.
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -336,7 +336,7 @@ func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
||||
for _, iss := range ap.magic.Issuers {
|
||||
if am, ok := iss.(acmeCapable); ok {
|
||||
iss := am.GetACMEIssuer()
|
||||
if certmagic.NewACMEIssuer(iss.magic, iss.template).HandleHTTPChallenge(w, r) {
|
||||
if iss.issuer.HandleHTTPChallenge(w, r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,8 +162,8 @@ func (iss *ZeroSSLIssuer) generateEABCredentials(ctx context.Context, acct acme.
|
||||
func (iss *ZeroSSLIssuer) initialize() {
|
||||
iss.mu.Lock()
|
||||
defer iss.mu.Unlock()
|
||||
if iss.template.NewAccountFunc == nil {
|
||||
iss.template.NewAccountFunc = iss.newAccountCallback
|
||||
if iss.ACMEIssuer.issuer.NewAccountFunc == nil {
|
||||
iss.ACMEIssuer.issuer.NewAccountFunc = iss.newAccountCallback
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,13 @@ import (
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(DeleteFilter{})
|
||||
caddy.RegisterModule(HashFilter{})
|
||||
caddy.RegisterModule(ReplaceFilter{})
|
||||
caddy.RegisterModule(IPMaskFilter{})
|
||||
caddy.RegisterModule(QueryFilter{})
|
||||
caddy.RegisterModule(CookieFilter{})
|
||||
caddy.RegisterModule(RegexpFilter{})
|
||||
caddy.RegisterModule(HashFilter{})
|
||||
caddy.RegisterModule(RenameFilter{})
|
||||
}
|
||||
|
||||
// LogFieldFilter can filter (or manipulate)
|
||||
@@ -542,21 +543,56 @@ func (f *RegexpFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||
return in
|
||||
}
|
||||
|
||||
// RenameFilter is a Caddy log field filter that
|
||||
// renames the field's key with the indicated name.
|
||||
type RenameFilter struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (RenameFilter) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "caddy.logging.encoders.filter.rename",
|
||||
New: func() caddy.Module { return new(RenameFilter) },
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the module from Caddyfile tokens.
|
||||
func (f *RenameFilter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
if d.NextArg() {
|
||||
f.Name = d.Val()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter renames the input field with the replacement name.
|
||||
func (f *RenameFilter) Filter(in zapcore.Field) zapcore.Field {
|
||||
in.Type = zapcore.StringType
|
||||
in.Key = f.Name
|
||||
return in
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ LogFieldFilter = (*DeleteFilter)(nil)
|
||||
_ LogFieldFilter = (*HashFilter)(nil)
|
||||
_ LogFieldFilter = (*ReplaceFilter)(nil)
|
||||
_ LogFieldFilter = (*IPMaskFilter)(nil)
|
||||
_ LogFieldFilter = (*QueryFilter)(nil)
|
||||
_ LogFieldFilter = (*CookieFilter)(nil)
|
||||
_ LogFieldFilter = (*RegexpFilter)(nil)
|
||||
_ LogFieldFilter = (*RenameFilter)(nil)
|
||||
|
||||
_ caddyfile.Unmarshaler = (*DeleteFilter)(nil)
|
||||
_ caddyfile.Unmarshaler = (*HashFilter)(nil)
|
||||
_ caddyfile.Unmarshaler = (*ReplaceFilter)(nil)
|
||||
_ caddyfile.Unmarshaler = (*IPMaskFilter)(nil)
|
||||
_ caddyfile.Unmarshaler = (*QueryFilter)(nil)
|
||||
_ caddyfile.Unmarshaler = (*CookieFilter)(nil)
|
||||
_ caddyfile.Unmarshaler = (*RegexpFilter)(nil)
|
||||
_ caddyfile.Unmarshaler = (*RenameFilter)(nil)
|
||||
|
||||
_ caddy.Provisioner = (*IPMaskFilter)(nil)
|
||||
_ caddy.Provisioner = (*RegexpFilter)(nil)
|
||||
|
||||
Reference in New Issue
Block a user