mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-25 16:22:36 -04:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 385adf5d87 | |||
| c7efb0307d | |||
| e34d9f1244 | |||
| ef8a372a1c | |||
| 0fc47e8357 | |||
| 25d2b4bf29 | |||
| 023d702f30 | |||
| 6722426f1a | |||
| 3b9eae70c9 | |||
| aa9c3eb732 | |||
| fdfdc03339 | |||
| dadfe1933b | |||
| 85152679ce | |||
| a33e4b5426 | |||
| f197cec7f3 | |||
| be6daa5fd4 | |||
| fe27f9cf0c | |||
| b1d456d8ab | |||
| d16ede358a | |||
| c82c231ba7 | |||
| 3ee663dee1 | |||
| 8ec51bbede | |||
| bc453fa6ae | |||
| e3324aa6de | |||
| d55d50b3b3 | |||
| b95b87381a | |||
| b01bb275b3 | |||
| 309c1fec62 | |||
| b88e2b6a49 | |||
| 4217217bad | |||
| 1c5969b576 | |||
| 0ee4378227 | |||
| 9859ab8148 | |||
| 00e6b77fe4 | |||
| d4f249741e | |||
| 04f50a9759 | |||
| 4cd7ae35b3 | |||
| 24f34780b6 | |||
| 724b74d981 | |||
| 4940325844 | |||
| 744d04c258 | |||
| ecbc1f85c5 | |||
| 997ef522bc | |||
| 0279a57ac4 | |||
| c94f5bb7dd | |||
| 0afbab8667 | |||
| fc65320e9c | |||
| e385be9225 | |||
| 66863aad3b | |||
| c42bfaf31e | |||
| e2f913bb7f | |||
| 65a09524c3 | |||
| c6d6a775a1 | |||
| 4accf737a6 | |||
| ff19bddac5 | |||
| 584eba94a4 | |||
| 904f149e5b | |||
| 8b80a3201f | |||
| 68529e2f9e | |||
| 399eff415c | |||
| c054a818a1 | |||
| af5c148ed1 | |||
| 514eef33fe | |||
| 3860b235d0 | |||
| 6f73a358f4 | |||
| 6a14e2c2a8 | |||
| 2bc30bb780 | |||
| 28d870c193 | |||
| fb9d874fa9 | |||
| 6cea1f239d | |||
| 2ae8c11927 | |||
| e9b1d7dcb4 | |||
| bd9d796e6e | |||
| 246a31aacd | |||
| 0665a86eb7 | |||
| 3fdaf50785 | |||
| 19cc2bd3c3 | |||
| 705de11bef | |||
| 8a0fff58aa | |||
| 6f0f159ba5 | |||
| 6eafd4e82f | |||
| eda54c22a6 | |||
| 2c71fb116b | |||
| 724613a1be | |||
| 735c86658d | |||
| a2dae1d43f | |||
| efc0cc5e85 | |||
| 0bf2565c37 | |||
| 7bfe5b6c95 | |||
| 2a5599e2ad | |||
| c35820012b | |||
| 2d0f8831f8 |
+2
-2
@@ -11,12 +11,12 @@ Please note that we consider publicly-registered domain names to be public infor
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | :white_check_mark: |
|
||||
| 1.x | :white_check_mark: (deprecating soon) |
|
||||
| 1.x | :x: |
|
||||
| < 1.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please email Matt Holt (the author) directly: matt [at] lightcodelabs [dot com].
|
||||
Please email Matt Holt (the author) directly: matt [at] ardanlabs [dot com].
|
||||
|
||||
We'll need enough information to verify the bug and make a patch. It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, resources permitting. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. Thank you for understanding.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Used as inspiration: https://github.com/mvdan/github-actions-golang
|
||||
|
||||
name: Cross-Platform
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
go-version: [ 1.14.x ]
|
||||
go: [ '1.14', '1.15' ]
|
||||
|
||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
||||
@@ -39,9 +39,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@@ -69,12 +69,12 @@ jobs:
|
||||
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.vars.outputs.go_cache }}
|
||||
key: ${{ runner.os }}-go-ci-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-${{ matrix.go }}-go-ci-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-ci
|
||||
${{ runner.os }}-${{ matrix.go }}-go-ci
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
- name: Publish Build Artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: caddy_v2_${{ runner.os }}_${{ steps.vars.outputs.short_sha }}
|
||||
name: caddy_${{ runner.os }}_go${{ matrix.go }}_${{ steps.vars.outputs.short_sha }}
|
||||
path: ${{ matrix.CADDY_BIN_PATH }}
|
||||
|
||||
# Commented bits below were useful to allow the job to continue
|
||||
@@ -124,6 +124,7 @@ jobs:
|
||||
name: test (s390x on IBM Z)
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
@@ -166,7 +167,7 @@ jobs:
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: goreleaser/goreleaser-action@v1
|
||||
- uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: check
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Cross-Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
cross-build-test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
|
||||
go: [ '1.14', '1.15' ]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Print Go version and environment
|
||||
id: vars
|
||||
run: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
printf "\n\nGo environment:\n\n"
|
||||
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 }}
|
||||
key: cross-build-go${{ matrix.go }}-${{ matrix.goos }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
cross-build-go${{ matrix.go }}-${{ matrix.goos }}
|
||||
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Run Build
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
GOOS: ${{ matrix.goos }}
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
working-directory: ./cmd/caddy
|
||||
run: |
|
||||
GOOS=$GOOS go build -trimpath -o caddy-"$GOOS"-amd64 2> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "::warning ::$GOOS Build Failed"
|
||||
exit 0
|
||||
fi
|
||||
@@ -12,14 +12,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
go-version: [ 1.14.x ]
|
||||
go: [ '1.14' ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
@@ -11,14 +11,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
go-version: [ 1.14.x ]
|
||||
go: [ '1.15' ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@@ -54,16 +54,16 @@ jobs:
|
||||
echo "::set-output name=tag_special::${TAG_SPECIAL}"
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.vars.outputs.go_cache }}
|
||||
key: ${{ runner.os }}-go-release-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go${{ matrix.go }}-release-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-release
|
||||
${{ runner.os }}-go${{ matrix.go }}-release
|
||||
|
||||
# GoReleaser will take care of publishing those artifacts into the release
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
|
||||
@@ -30,5 +30,5 @@ jobs:
|
||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||
repository: caddyserver/caddy-docker
|
||||
event-type: release-tagged
|
||||
client-payload: '{"tag": "${{ github.release.tag_name }}"}'
|
||||
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
|
||||
|
||||
|
||||
@@ -112,5 +112,6 @@ changelog:
|
||||
- '^chore:'
|
||||
- '^ci:'
|
||||
- '^docs?:'
|
||||
- '^readme:'
|
||||
- '^tests?:'
|
||||
- '^\w+\s+' # a hack to remove commit messages without colons thus don't correspond to a package
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
<p align="center">Caddy is an extensible server platform that uses TLS by default.</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/caddyserver/caddy/actions?query=workflow%3ACross-Platform"><img src="https://github.com/caddyserver/caddy/workflows/Cross-Platform/badge.svg"></a>
|
||||
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
|
||||
<a href="https://app.fuzzit.dev/orgs/caddyserver-gh/dashboard"><img src="https://app.fuzzit.dev/badge?org_id=caddyserver-gh"></a>
|
||||
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
|
||||
<br>
|
||||
<a href="https://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" alt="@caddyserver on Twitter"></a>
|
||||
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
|
||||
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/caddyserver/caddy/releases">Download</a> ·
|
||||
<a href="https://github.com/caddyserver/caddy/releases">Releases</a> ·
|
||||
<a href="https://caddyserver.com/docs/">Documentation</a> ·
|
||||
<a href="https://caddy.community">Community</a>
|
||||
<a href="https://caddy.community">Get Help</a>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -23,6 +22,7 @@
|
||||
### Menu
|
||||
|
||||
- [Features](#features)
|
||||
- [Install](#install)
|
||||
- [Build from source](#build-from-source)
|
||||
- [For development](#for-development)
|
||||
- [With version information and/or plugins](#with-version-information-andor-plugins)
|
||||
@@ -57,7 +57,11 @@
|
||||
- Actually **fun to use**
|
||||
- So, so much more to discover
|
||||
|
||||
## Install
|
||||
|
||||
You can install Caddy by downloading from the Github Releases and placing it in your PATH.
|
||||
|
||||
For other install options, see https://caddyserver.com/docs/download.
|
||||
|
||||
## Build from source
|
||||
|
||||
@@ -77,7 +81,7 @@ _**Note:** These steps [will not embed proper version information](https://githu
|
||||
|
||||
### With version information and/or plugins
|
||||
|
||||
Using [our builder tool](https://github.com/caddyserver/xcaddy)...
|
||||
Using [our builder tool, `xcaddy`](https://github.com/caddyserver/xcaddy)...
|
||||
|
||||
```
|
||||
$ xcaddy build
|
||||
@@ -89,8 +93,8 @@ $ xcaddy build
|
||||
2. Change into it: `cd caddy`
|
||||
3. Copy [Caddy's main.go](https://github.com/caddyserver/caddy/blob/master/cmd/caddy/main.go) into the empty folder. Add imports for any custom plugins you want to add.
|
||||
4. Initialize a Go module: `go mod init caddy`
|
||||
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@TAG` replacing `TAG` with a git tag or commit.
|
||||
6. (Optional) Add plugins by adding their import: `_ "IMPORT_PATH"`
|
||||
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag or commit.
|
||||
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
|
||||
7. Compile: `go build`
|
||||
|
||||
|
||||
@@ -119,7 +123,7 @@ The primary way to configure Caddy is through [its API](https://caddyserver.com/
|
||||
|
||||
Caddy exposes an unprecedented level of control compared to any web server in existence. In Caddy, you are usually setting the actual values of the initialized types in memory that power everything from your HTTP handlers and TLS handshakes to your storage medium. Caddy is also ridiculously extensible, with a powerful plugin system that makes vast improvements over other web servers.
|
||||
|
||||
To wield the power of this design, you need to know how the config document is structured. Please see the [our documentation site](https://caddyserver.com/docs/) for details about [Caddy's config structure](https://caddyserver.com/docs/json/).
|
||||
To wield the power of this design, you need to know how the config document is structured. Please see [our documentation site](https://caddyserver.com/docs/) for details about [Caddy's config structure](https://caddyserver.com/docs/json/).
|
||||
|
||||
Nearly all of Caddy's configuration is contained in a single config document, rather than being scattered across CLI flags and env variables and a configuration file as with other web servers. This makes managing your server config more straightforward and reduces hidden variables/factors.
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -34,6 +35,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -106,34 +108,53 @@ func (admin AdminConfig) newAdminHandler(addr NetworkAddress) adminHandler {
|
||||
mux: http.NewServeMux(),
|
||||
}
|
||||
|
||||
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, h AdminHandler) {
|
||||
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)
|
||||
})
|
||||
muxWrap.mux.Handle(pattern, wrapper)
|
||||
addRouteWithMetrics(pattern, handlerLabel, wrapper)
|
||||
}
|
||||
|
||||
const handlerLabel = "admin"
|
||||
|
||||
// register standard config control endpoints
|
||||
addRoute("/"+rawConfigKey+"/", AdminHandlerFunc(handleConfig))
|
||||
addRoute("/id/", AdminHandlerFunc(handleConfigID))
|
||||
addRoute("/stop", AdminHandlerFunc(handleStop))
|
||||
addRoute("/"+rawConfigKey+"/", handlerLabel, AdminHandlerFunc(handleConfig))
|
||||
addRoute("/id/", handlerLabel, AdminHandlerFunc(handleConfigID))
|
||||
addRoute("/stop", handlerLabel, AdminHandlerFunc(handleStop))
|
||||
|
||||
// register debugging endpoints
|
||||
muxWrap.mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
muxWrap.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
muxWrap.mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
muxWrap.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
muxWrap.mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
muxWrap.mux.Handle("/debug/vars", expvar.Handler())
|
||||
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())
|
||||
|
||||
// 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, route.Handler)
|
||||
addRoute(route.Pattern, handlerLabel, route.Handler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,15 +256,20 @@ func replaceAdmin(cfg *Config) error {
|
||||
MaxHeaderBytes: 1024 * 64,
|
||||
}
|
||||
|
||||
go adminServer.Serve(ln)
|
||||
adminLogger := Log().Named("admin")
|
||||
go func() {
|
||||
if err := adminServer.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
|
||||
adminLogger.Error("admin server shutdown for unknown reason", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
Log().Named("admin").Info("admin endpoint started",
|
||||
adminLogger.Info("admin endpoint started",
|
||||
zap.String("address", addr.String()),
|
||||
zap.Bool("enforce_origin", adminConfig.EnforceOrigin),
|
||||
zap.Strings("origins", handler.allowedOrigins))
|
||||
|
||||
if !handler.enforceHost {
|
||||
Log().Named("admin").Warn("admin endpoint on open interface; host checking disabled",
|
||||
adminLogger.Warn("admin endpoint on open interface; host checking disabled",
|
||||
zap.String("address", addr.String()))
|
||||
}
|
||||
|
||||
@@ -285,13 +311,18 @@ type adminHandler struct {
|
||||
// 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) {
|
||||
Log().Named("admin.api").Info("received request",
|
||||
log := Log().Named("admin.api").With(
|
||||
zap.String("method", r.Method),
|
||||
zap.String("host", r.Host),
|
||||
zap.String("uri", r.RequestURI),
|
||||
zap.String("remote_addr", r.RemoteAddr),
|
||||
zap.Reflect("headers", r.Header),
|
||||
)
|
||||
if r.RequestURI == "/metrics" {
|
||||
log.Debug("received request")
|
||||
} else {
|
||||
log.Info("received request")
|
||||
}
|
||||
h.serveHTTP(w, r)
|
||||
}
|
||||
|
||||
|
||||
@@ -471,7 +471,7 @@ func stopAndCleanup() error {
|
||||
}
|
||||
certmagic.CleanUpOwnLocks()
|
||||
if pidfile != "" {
|
||||
os.Remove(pidfile)
|
||||
return os.Remove(pidfile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package caddyfile
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"unicode"
|
||||
)
|
||||
@@ -168,3 +169,21 @@ func (l *lexer) next() bool {
|
||||
val = append(val, ch)
|
||||
}
|
||||
}
|
||||
|
||||
// Tokenize takes bytes as input and lexes it into
|
||||
// a list of tokens that can be parsed as a Caddyfile.
|
||||
// Also takes a filename to fill the token's File as
|
||||
// the source of the tokens, which is important to
|
||||
// determine relative paths for `import` directives.
|
||||
func Tokenize(input []byte, filename string) ([]Token, error) {
|
||||
l := lexer{}
|
||||
if err := l.load(bytes.NewReader(input)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokens []Token
|
||||
for l.next() {
|
||||
l.token.File = filename
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
@@ -15,37 +15,35 @@
|
||||
package caddyfile
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type lexerTestCase struct {
|
||||
input string
|
||||
input []byte
|
||||
expected []Token
|
||||
}
|
||||
|
||||
func TestLexer(t *testing.T) {
|
||||
testCases := []lexerTestCase{
|
||||
{
|
||||
input: `host:123`,
|
||||
input: []byte(`host:123`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123
|
||||
input: []byte(`host:123
|
||||
|
||||
directive`,
|
||||
directive`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 3, Text: "directive"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 {
|
||||
input: []byte(`host:123 {
|
||||
directive
|
||||
}`,
|
||||
}`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 1, Text: "{"},
|
||||
@@ -54,7 +52,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 { directive }`,
|
||||
input: []byte(`host:123 { directive }`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 1, Text: "{"},
|
||||
@@ -63,12 +61,12 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 {
|
||||
input: []byte(`host:123 {
|
||||
#comment
|
||||
directive
|
||||
# comment
|
||||
foobar # another comment
|
||||
}`,
|
||||
}`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 1, Text: "{"},
|
||||
@@ -78,10 +76,10 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 {
|
||||
input: []byte(`host:123 {
|
||||
# hash inside string is not a comment
|
||||
redir / /some/#/path
|
||||
}`,
|
||||
}`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 1, Text: "{"},
|
||||
@@ -92,14 +90,14 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "# comment at beginning of file\n# comment at beginning of line\nhost:123",
|
||||
input: []byte("# comment at beginning of file\n# comment at beginning of line\nhost:123"),
|
||||
expected: []Token{
|
||||
{Line: 3, Text: "host:123"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `a "quoted value" b
|
||||
foobar`,
|
||||
input: []byte(`a "quoted value" b
|
||||
foobar`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "a"},
|
||||
{Line: 1, Text: "quoted value"},
|
||||
@@ -108,7 +106,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `A "quoted \"value\" inside" B`,
|
||||
input: []byte(`A "quoted \"value\" inside" B`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "A"},
|
||||
{Line: 1, Text: `quoted "value" inside`},
|
||||
@@ -116,7 +114,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "An escaped \"newline\\\ninside\" quotes",
|
||||
input: []byte("An escaped \"newline\\\ninside\" quotes"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "An"},
|
||||
{Line: 1, Text: "escaped"},
|
||||
@@ -125,7 +123,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "An escaped newline\\\noutside quotes",
|
||||
input: []byte("An escaped newline\\\noutside quotes"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "An"},
|
||||
{Line: 1, Text: "escaped"},
|
||||
@@ -135,7 +133,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "line1\\\nescaped\nline2\nline3",
|
||||
input: []byte("line1\\\nescaped\nline2\nline3"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "line1"},
|
||||
{Line: 1, Text: "escaped"},
|
||||
@@ -144,7 +142,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "line1\\\nescaped1\\\nescaped2\nline4\nline5",
|
||||
input: []byte("line1\\\nescaped1\\\nescaped2\nline4\nline5"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "line1"},
|
||||
{Line: 1, Text: "escaped1"},
|
||||
@@ -154,34 +152,34 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"unescapable\ in quotes"`,
|
||||
input: []byte(`"unescapable\ in quotes"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `unescapable\ in quotes`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"don't\escape"`,
|
||||
input: []byte(`"don't\escape"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `don't\escape`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"don't\\escape"`,
|
||||
input: []byte(`"don't\\escape"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `don't\\escape`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `un\escapable`,
|
||||
input: []byte(`un\escapable`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `un\escapable`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `A "quoted value with line
|
||||
input: []byte(`A "quoted value with line
|
||||
break inside" {
|
||||
foobar
|
||||
}`,
|
||||
}`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "A"},
|
||||
{Line: 1, Text: "quoted value with line\n\t\t\t\t\tbreak inside"},
|
||||
@@ -191,13 +189,13 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"C:\php\php-cgi.exe"`,
|
||||
input: []byte(`"C:\php\php-cgi.exe"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `C:\php\php-cgi.exe`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `empty "" string`,
|
||||
input: []byte(`empty "" string`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `empty`},
|
||||
{Line: 1, Text: ``},
|
||||
@@ -205,7 +203,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "skip those\r\nCR characters",
|
||||
input: []byte("skip those\r\nCR characters"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "skip"},
|
||||
{Line: 1, Text: "those"},
|
||||
@@ -214,13 +212,13 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "\xEF\xBB\xBF:8080", // test with leading byte order mark
|
||||
input: []byte("\xEF\xBB\xBF:8080"), // test with leading byte order mark
|
||||
expected: []Token{
|
||||
{Line: 1, Text: ":8080"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "simple `backtick quoted` string",
|
||||
input: []byte("simple `backtick quoted` string"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `simple`},
|
||||
{Line: 1, Text: `backtick quoted`},
|
||||
@@ -228,7 +226,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "multiline `backtick\nquoted\n` string",
|
||||
input: []byte("multiline `backtick\nquoted\n` string"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `multiline`},
|
||||
{Line: 1, Text: "backtick\nquoted\n"},
|
||||
@@ -236,7 +234,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "nested `\"quotes inside\" backticks` string",
|
||||
input: []byte("nested `\"quotes inside\" backticks` string"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `nested`},
|
||||
{Line: 1, Text: `"quotes inside" backticks`},
|
||||
@@ -244,7 +242,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "reverse-nested \"`backticks` inside\" quotes",
|
||||
input: []byte("reverse-nested \"`backticks` inside\" quotes"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `reverse-nested`},
|
||||
{Line: 1, Text: "`backticks` inside"},
|
||||
@@ -254,22 +252,14 @@ func TestLexer(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
actual := tokenize(testCase.input)
|
||||
actual, err := Tokenize(testCase.input, "")
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
lexerCompare(t, i, testCase.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func tokenize(input string) (tokens []Token) {
|
||||
l := lexer{}
|
||||
if err := l.load(strings.NewReader(input)); err != nil {
|
||||
log.Printf("[ERROR] load failed: %v", err)
|
||||
}
|
||||
for l.next() {
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lexerCompare(t *testing.T, n int, expected, actual []Token) {
|
||||
if len(expected) != len(actual) {
|
||||
t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
|
||||
|
||||
@@ -87,16 +87,10 @@ func allTokens(filename string, input []byte) ([]Token, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := new(lexer)
|
||||
err = l.load(bytes.NewReader(input))
|
||||
tokens, err := Tokenize(input, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokens []Token
|
||||
for l.next() {
|
||||
l.token.File = filename
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
@@ -73,8 +75,10 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
||||
// load <paths...>
|
||||
// ca <acme_ca_endpoint>
|
||||
// ca_root <pem_file>
|
||||
// dns <provider_name>
|
||||
// dns <provider_name> [...]
|
||||
// on_demand
|
||||
// eab <key_id> <mac_key>
|
||||
// issuer <module_name> [...]
|
||||
// }
|
||||
//
|
||||
func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
@@ -84,6 +88,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
var certSelector caddytls.CustomCertSelectionPolicy
|
||||
var acmeIssuer *caddytls.ACMEIssuer
|
||||
var internalIssuer *caddytls.InternalIssuer
|
||||
var issuer certmagic.Issuer
|
||||
var onDemand bool
|
||||
|
||||
for h.Next() {
|
||||
@@ -262,6 +267,41 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
}
|
||||
acmeIssuer.CA = arg[0]
|
||||
|
||||
case "eab":
|
||||
arg := h.RemainingArgs()
|
||||
if len(arg) != 2 {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
acmeIssuer.ExternalAccount = &acme.EAB{
|
||||
KeyID: arg[0],
|
||||
MACKey: arg[1],
|
||||
}
|
||||
|
||||
case "issuer":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
modName := h.Val()
|
||||
mod, err := caddy.GetModule("tls.issuance." + modName)
|
||||
if err != nil {
|
||||
return nil, h.Errf("getting issuer module '%s': %v", modName, err)
|
||||
}
|
||||
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||
if !ok {
|
||||
return nil, h.Errf("issuer module '%s' is not a Caddyfile unmarshaler", mod.ID)
|
||||
}
|
||||
err = unm.UnmarshalCaddyfile(h.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
issuer, ok = unm.(certmagic.Issuer)
|
||||
if !ok {
|
||||
return nil, h.Errf("module %s is not a certmagic.Issuer", mod.ID)
|
||||
}
|
||||
|
||||
case "dns":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
@@ -336,7 +376,24 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
// the logic to support this would be complex
|
||||
return nil, h.Err("cannot use both ACME and internal issuers in same server block")
|
||||
}
|
||||
if acmeIssuer != nil {
|
||||
if issuer != nil && (acmeIssuer != nil || internalIssuer != nil) {
|
||||
// similarly, the logic to support this would be complex
|
||||
return nil, h.Err("when defining an issuer, all its config must be in its block, rather than from separate tls subdirectives")
|
||||
}
|
||||
switch {
|
||||
case issuer != nil:
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
Value: issuer,
|
||||
})
|
||||
|
||||
case internalIssuer != nil:
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
Value: internalIssuer,
|
||||
})
|
||||
|
||||
case acmeIssuer != nil:
|
||||
// fill in global defaults, if configured
|
||||
if email := h.Option("email"); email != nil && acmeIssuer.Email == "" {
|
||||
acmeIssuer.Email = email.(string)
|
||||
@@ -347,15 +404,9 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
if caPemFile := h.Option("acme_ca_root"); caPemFile != nil {
|
||||
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, caPemFile.(string))
|
||||
}
|
||||
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
Value: acmeIssuer,
|
||||
})
|
||||
} else if internalIssuer != nil {
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
Value: internalIssuer,
|
||||
Value: disambiguateACMEIssuer(acmeIssuer),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -466,36 +517,23 @@ func parseRespond(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
sr := new(caddyhttp.Subroute)
|
||||
|
||||
for h.Next() {
|
||||
for nesting := h.Nesting(); h.NextBlock(nesting); {
|
||||
dir := h.Val()
|
||||
allResults, err := parseSegmentAsConfig(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dirFunc, ok := registeredDirectives[dir]
|
||||
if !ok {
|
||||
return nil, h.Errf("unrecognized directive: %s", dir)
|
||||
}
|
||||
|
||||
subHelper := h
|
||||
subHelper.Dispenser = h.NewFromNextSegment()
|
||||
|
||||
results, err := dirFunc(subHelper)
|
||||
if err != nil {
|
||||
return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
|
||||
}
|
||||
for _, result := range results {
|
||||
switch handler := result.Value.(type) {
|
||||
case caddyhttp.Route:
|
||||
sr.Routes = append(sr.Routes, handler)
|
||||
case caddyhttp.Subroute:
|
||||
// directives which return a literal subroute instead of a route
|
||||
// means they intend to keep those handlers together without
|
||||
// them being reordered; we're doing that anyway since we're in
|
||||
// the route directive, so just append its handlers
|
||||
sr.Routes = append(sr.Routes, handler.Routes...)
|
||||
default:
|
||||
return nil, h.Errf("%s directive returned something other than an HTTP route or subroute: %#v (only handler directives can be used in routes)", dir, result.Value)
|
||||
}
|
||||
}
|
||||
for _, result := range allResults {
|
||||
switch handler := result.Value.(type) {
|
||||
case caddyhttp.Route:
|
||||
sr.Routes = append(sr.Routes, handler)
|
||||
case caddyhttp.Subroute:
|
||||
// directives which return a literal subroute instead of a route
|
||||
// means they intend to keep those handlers together without
|
||||
// them being reordered; we're doing that anyway since we're in
|
||||
// the route directive, so just append its handlers
|
||||
sr.Routes = append(sr.Routes, handler.Routes...)
|
||||
default:
|
||||
return nil, h.Errf("%s directive returned something other than an HTTP route or subroute: %#v (only handler directives can be used in routes)", result.directive, result.Value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,13 +55,15 @@ var directiveOrder = []string{
|
||||
"encode",
|
||||
"templates",
|
||||
|
||||
// special routing directives
|
||||
// special routing & dispatching directives
|
||||
"handle",
|
||||
"handle_path",
|
||||
"route",
|
||||
"push",
|
||||
|
||||
// handlers that typically respond to requests
|
||||
"respond",
|
||||
"metrics",
|
||||
"reverse_proxy",
|
||||
"php_fastcgi",
|
||||
"file_server",
|
||||
@@ -268,9 +270,26 @@ func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
|
||||
// are themselves treated as directives, from which a subroute is built
|
||||
// and returned.
|
||||
func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
allResults, err := parseSegmentAsConfig(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildSubroute(allResults, h.groupCounter)
|
||||
}
|
||||
|
||||
// parseSegmentAsConfig parses the segment such that its subdirectives
|
||||
// are themselves treated as directives, including named matcher definitions,
|
||||
// and the raw Config structs are returned.
|
||||
func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
|
||||
var allResults []ConfigValue
|
||||
|
||||
for h.Next() {
|
||||
// don't allow non-matcher args on the first line
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
// slice the linear list of tokens into top-level segments
|
||||
var segments []caddyfile.Segment
|
||||
for nesting := h.Nesting(); h.NextBlock(nesting); {
|
||||
@@ -285,13 +304,17 @@ func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
}
|
||||
|
||||
// find and extract any embedded matcher definitions in this scope
|
||||
for i, seg := range segments {
|
||||
for i := 0; i < len(segments); i++ {
|
||||
seg := segments[i]
|
||||
if strings.HasPrefix(seg.Directive(), matcherPrefix) {
|
||||
// parse, then add the matcher to matcherDefs
|
||||
err := parseMatcherDefinitions(caddyfile.NewDispenser(seg), matcherDefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// remove the matcher segment (consumed), then step back the loop
|
||||
segments = append(segments[:i], segments[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +341,7 @@ func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return buildSubroute(allResults, h.groupCounter)
|
||||
return allResults, nil
|
||||
}
|
||||
|
||||
// ConfigValue represents a value to be added to the final
|
||||
@@ -385,6 +408,14 @@ func sortRoutes(routes []ConfigValue) {
|
||||
if len(jPM) > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// sort with the most-specific (longest) path first
|
||||
return iPathLen > jPathLen
|
||||
})
|
||||
}
|
||||
|
||||
@@ -172,6 +172,15 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||
if err != nil {
|
||||
return nil, warnings, fmt.Errorf("parsing caddyfile tokens for '%s': %v", dir, err)
|
||||
}
|
||||
|
||||
// As a special case, we want "handle_path" to be sorted
|
||||
// at the same level as "handle", so we force them to use
|
||||
// the same directive name after their parsing is complete.
|
||||
// See https://github.com/caddyserver/caddy/issues/3675#issuecomment-678042377
|
||||
if dir == "handle_path" {
|
||||
dir = "handle"
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
result.directive = dir
|
||||
sb.pile[result.Class] = append(sb.pile[result.Class], result)
|
||||
@@ -261,12 +270,8 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
|
||||
storageCvtr.(caddy.Module).CaddyModule().ID.Name(),
|
||||
&warnings)
|
||||
}
|
||||
if adminConfig, ok := options["admin"].(string); ok && adminConfig != "" {
|
||||
if adminConfig == "off" {
|
||||
cfg.Admin = &caddy.AdminConfig{Disabled: true}
|
||||
} else {
|
||||
cfg.Admin = &caddy.AdminConfig{Listen: adminConfig}
|
||||
}
|
||||
if adminConfig, ok := options["admin"].(*caddy.AdminConfig); ok && adminConfig != nil {
|
||||
cfg.Admin = adminConfig
|
||||
}
|
||||
if len(customLogs) > 0 {
|
||||
if cfg.Logging == nil {
|
||||
@@ -450,12 +455,12 @@ func (st *ServerType) serversFromPairings(
|
||||
}
|
||||
} else {
|
||||
cp.DefaultSNI = defaultSNI
|
||||
hasCatchAllTLSConnPolicy = true
|
||||
}
|
||||
|
||||
// only append this policy if it actually changes something
|
||||
if !cp.SettingsEmpty() {
|
||||
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
|
||||
hasCatchAllTLSConnPolicy = len(hosts) == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -657,9 +662,15 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock,
|
||||
return nil
|
||||
}
|
||||
|
||||
// consolidateConnPolicies removes empty TLS connection policies and combines
|
||||
// equivalent ones for a cleaner overall output.
|
||||
// consolidateConnPolicies sorts any catch-all policy to the end, removes empty TLS connection
|
||||
// policies, and combines equivalent ones for a cleaner overall output.
|
||||
func consolidateConnPolicies(cps caddytls.ConnectionPolicies) (caddytls.ConnectionPolicies, error) {
|
||||
// catch-all policies (those without any matcher) should be at the
|
||||
// end, otherwise it nullifies any more specific policies
|
||||
sort.SliceStable(cps, func(i, j int) bool {
|
||||
return cps[j].MatchersRaw == nil && cps[i].MatchersRaw != nil
|
||||
})
|
||||
|
||||
for i := 0; i < len(cps); i++ {
|
||||
// compare it to the others
|
||||
for j := 0; j < len(cps); j++ {
|
||||
@@ -852,7 +863,18 @@ func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subro
|
||||
// root directives would overwrite previously-matched ones; they should not cascade
|
||||
"root": {},
|
||||
}
|
||||
for meDir, info := range mutuallyExclusiveDirs {
|
||||
|
||||
// we need to deterministically loop over each of these directives
|
||||
// in order to keep the group numbers consistent
|
||||
keys := make([]string, 0, len(mutuallyExclusiveDirs))
|
||||
for k := range mutuallyExclusiveDirs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, meDir := range keys {
|
||||
info := mutuallyExclusiveDirs[meDir]
|
||||
|
||||
// see how many instances of the directive there are
|
||||
for _, r := range routes {
|
||||
if r.directive == meDir {
|
||||
|
||||
@@ -164,6 +164,58 @@ func TestGlobalOptions(t *testing.T) {
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
input: `
|
||||
{
|
||||
admin {
|
||||
enforce_origin
|
||||
origins 192.168.1.1:2020 127.0.0.1:2020
|
||||
}
|
||||
}
|
||||
:80
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `
|
||||
{
|
||||
admin 127.0.0.1:2020 {
|
||||
enforce_origin
|
||||
origins 192.168.1.1:2020 127.0.0.1:2020
|
||||
}
|
||||
}
|
||||
:80
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `
|
||||
{
|
||||
admin 192.168.1.1:2020 127.0.0.1:2020 {
|
||||
enforce_origin
|
||||
origins 192.168.1.1:2020 127.0.0.1:2020
|
||||
}
|
||||
}
|
||||
:80
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
input: `
|
||||
{
|
||||
admin off {
|
||||
enforce_origin
|
||||
origins 192.168.1.1:2020 127.0.0.1:2020
|
||||
}
|
||||
}
|
||||
:80
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
|
||||
adapter := caddyfile.Adapter{
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -34,6 +36,7 @@ func init() {
|
||||
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
|
||||
RegisterGlobalOption("acme_dns", parseOptSingleString)
|
||||
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
|
||||
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
|
||||
RegisterGlobalOption("email", parseOptSingleString)
|
||||
RegisterGlobalOption("admin", parseOptAdmin)
|
||||
RegisterGlobalOption("on_demand_tls", parseOptOnDemand)
|
||||
@@ -182,7 +185,7 @@ func parseOptStorage(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
}
|
||||
|
||||
func parseOptACMEEAB(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
eab := new(caddytls.ExternalAccountBinding)
|
||||
eab := new(acme.EAB)
|
||||
for d.Next() {
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
@@ -195,11 +198,11 @@ func parseOptACMEEAB(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
}
|
||||
eab.KeyID = d.Val()
|
||||
|
||||
case "hmac":
|
||||
case "mac_key":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
eab.HMAC = d.Val()
|
||||
eab.MACKey = d.Val()
|
||||
|
||||
default:
|
||||
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
||||
@@ -209,6 +212,33 @@ func parseOptACMEEAB(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
return eab, nil
|
||||
}
|
||||
|
||||
func parseOptCertIssuer(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
if !d.Next() { // consume option name
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
if !d.Next() { // get issuer module name
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
modName := d.Val()
|
||||
mod, err := caddy.GetModule("tls.issuance." + modName)
|
||||
if err != nil {
|
||||
return nil, d.Errf("getting issuer module '%s': %v", modName, err)
|
||||
}
|
||||
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||
if !ok {
|
||||
return nil, d.Errf("issuer module '%s' is not a Caddyfile unmarshaler", mod.ID)
|
||||
}
|
||||
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iss, ok := unm.(certmagic.Issuer)
|
||||
if !ok {
|
||||
return nil, d.Errf("module %s is not a certmagic.Issuer", mod.ID)
|
||||
}
|
||||
return iss, nil
|
||||
}
|
||||
|
||||
func parseOptSingleString(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
d.Next() // consume parameter name
|
||||
if !d.Next() {
|
||||
@@ -222,17 +252,39 @@ func parseOptSingleString(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
}
|
||||
|
||||
func parseOptAdmin(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
if d.Next() {
|
||||
var listenAddress string
|
||||
if !d.AllArgs(&listenAddress) {
|
||||
return "", d.ArgErr()
|
||||
adminCfg := new(caddy.AdminConfig)
|
||||
for d.Next() {
|
||||
if d.NextArg() {
|
||||
listenAddress := d.Val()
|
||||
if listenAddress == "off" {
|
||||
adminCfg.Disabled = true
|
||||
if d.Next() { // Do not accept any remaining options including block
|
||||
return nil, d.Err("No more option is allowed after turning off admin config")
|
||||
}
|
||||
} else {
|
||||
adminCfg.Listen = listenAddress
|
||||
if d.NextArg() { // At most 1 arg is allowed
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
}
|
||||
}
|
||||
if listenAddress == "" {
|
||||
listenAddress = caddy.DefaultAdminListen
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "enforce_origin":
|
||||
adminCfg.EnforceOrigin = true
|
||||
|
||||
case "origins":
|
||||
adminCfg.Origins = d.RemainingArgs()
|
||||
|
||||
default:
|
||||
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
||||
}
|
||||
}
|
||||
return listenAddress, nil
|
||||
}
|
||||
return "", nil
|
||||
if adminCfg.Listen == "" && !adminCfg.Disabled {
|
||||
adminCfg.Listen = caddy.DefaultAdminListen
|
||||
}
|
||||
return adminCfg, nil
|
||||
}
|
||||
|
||||
func parseOptOnDemand(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
|
||||
@@ -21,12 +21,14 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
func (st ServerType) buildTLSApp(
|
||||
@@ -134,8 +136,11 @@ func (st ServerType) buildTLSApp(
|
||||
// issuer, skip, since we intend to adjust only ACME issuers
|
||||
var acmeIssuer *caddytls.ACMEIssuer
|
||||
if ap.Issuer != nil {
|
||||
var ok bool
|
||||
if acmeIssuer, ok = ap.Issuer.(*caddytls.ACMEIssuer); !ok {
|
||||
// ensure we include any issuer that embeds/wraps an underlying ACME issuer
|
||||
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
|
||||
if acmeWrapper, ok := ap.Issuer.(acmeCapable); ok {
|
||||
acmeIssuer = acmeWrapper.GetACMEIssuer()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -321,6 +326,9 @@ func (st ServerType) buildTLSApp(
|
||||
|
||||
// do a little verification & cleanup
|
||||
if tlsApp.Automation != nil {
|
||||
// consolidate automation policies that are the exact same
|
||||
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
|
||||
|
||||
// ensure automation policies don't overlap subjects (this should be
|
||||
// an error at provision-time as well, but catch it in the adapt phase
|
||||
// for convenience)
|
||||
@@ -333,9 +341,6 @@ func (st ServerType) buildTLSApp(
|
||||
automationHostSet[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// consolidate automation policies that are the exact same
|
||||
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
|
||||
}
|
||||
|
||||
return tlsApp, warnings, nil
|
||||
@@ -347,6 +352,8 @@ func (st ServerType) buildTLSApp(
|
||||
// returned if there are no default/global options. However, if always is
|
||||
// true, a non-nil value will always be returned (unless there is an error).
|
||||
func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddyconfig.Warning, always bool) (*caddytls.AutomationPolicy, error) {
|
||||
issuer, hasIssuer := options["cert_issuer"]
|
||||
|
||||
acmeCA, hasACMECA := options["acme_ca"]
|
||||
acmeCARoot, hasACMECARoot := options["acme_ca_root"]
|
||||
acmeDNS, hasACMEDNS := options["acme_dns"]
|
||||
@@ -356,7 +363,7 @@ func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddycon
|
||||
localCerts, hasLocalCerts := options["local_certs"]
|
||||
keyType, hasKeyType := options["key_type"]
|
||||
|
||||
hasGlobalAutomationOpts := hasACMECA || hasACMECARoot || hasACMEDNS || hasACMEEAB || hasEmail || hasLocalCerts || hasKeyType
|
||||
hasGlobalAutomationOpts := hasIssuer || hasACMECA || hasACMECARoot || hasACMEDNS || hasACMEEAB || hasEmail || hasLocalCerts || hasKeyType
|
||||
|
||||
// if there are no global options related to automation policies
|
||||
// set, then we can just return right away
|
||||
@@ -368,8 +375,16 @@ func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddycon
|
||||
}
|
||||
|
||||
ap := new(caddytls.AutomationPolicy)
|
||||
if keyType != nil {
|
||||
ap.KeyType = keyType.(string)
|
||||
}
|
||||
|
||||
if localCerts != nil {
|
||||
if hasIssuer {
|
||||
if hasACMECA || hasACMEDNS || hasACMEEAB || hasEmail || hasLocalCerts {
|
||||
return nil, fmt.Errorf("global options are ambiguous: cert_issuer is confusing when combined with acme_*, email, or local_certs options")
|
||||
}
|
||||
ap.Issuer = issuer.(certmagic.Issuer)
|
||||
} else if localCerts != nil {
|
||||
// internal issuer enabled trumps any ACME configurations; useful in testing
|
||||
ap.Issuer = new(caddytls.InternalIssuer) // we'll encode it later
|
||||
} else {
|
||||
@@ -399,17 +414,27 @@ func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddycon
|
||||
mgr.TrustedRootsPEMFiles = []string{acmeCARoot.(string)}
|
||||
}
|
||||
if acmeEAB != nil {
|
||||
mgr.ExternalAccount = acmeEAB.(*caddytls.ExternalAccountBinding)
|
||||
mgr.ExternalAccount = acmeEAB.(*acme.EAB)
|
||||
}
|
||||
if keyType != nil {
|
||||
ap.KeyType = keyType.(string)
|
||||
}
|
||||
ap.Issuer = mgr // we'll encode it later
|
||||
ap.Issuer = disambiguateACMEIssuer(mgr) // we'll encode it later
|
||||
}
|
||||
|
||||
return ap, nil
|
||||
}
|
||||
|
||||
// disambiguateACMEIssuer returns an issuer based on the properties of acmeIssuer.
|
||||
// If acmeIssuer implicitly configures a certain kind of ACMEIssuer (for example,
|
||||
// ZeroSSL), the proper wrapper over acmeIssuer will be returned instead.
|
||||
func disambiguateACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) certmagic.Issuer {
|
||||
// as a special case, we integrate with ZeroSSL's ACME endpoint if it looks like an
|
||||
// implicit ZeroSSL configuration (this requires a wrapper type over ACMEIssuer
|
||||
// because of the EAB generation; if EAB is provided, we can use plain ACMEIssuer)
|
||||
if strings.Contains(acmeIssuer.CA, "acme.zerossl.com") && acmeIssuer.ExternalAccount == nil {
|
||||
return &caddytls.ZeroSSLIssuer{ACMEIssuer: acmeIssuer}
|
||||
}
|
||||
return acmeIssuer
|
||||
}
|
||||
|
||||
// consolidateAutomationPolicies combines automation policies that are the same,
|
||||
// for a cleaner overall output.
|
||||
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
|
||||
@@ -443,7 +468,12 @@ func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls
|
||||
} else if len(aps[i].Subjects) > 0 && len(aps[j].Subjects) == 0 {
|
||||
aps = append(aps[:i], aps[i+1:]...)
|
||||
} else {
|
||||
aps[i].Subjects = append(aps[i].Subjects, aps[j].Subjects...)
|
||||
// avoid repeated subjects
|
||||
for _, subj := range aps[j].Subjects {
|
||||
if !sliceContains(aps[i].Subjects, subj) {
|
||||
aps[i].Subjects = append(aps[i].Subjects, subj)
|
||||
}
|
||||
}
|
||||
aps = append(aps[:j], aps[j+1:]...)
|
||||
}
|
||||
i--
|
||||
|
||||
@@ -430,7 +430,7 @@ func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expe
|
||||
|
||||
body := string(bytes)
|
||||
|
||||
if !strings.Contains(body, expectedBody) {
|
||||
if body != expectedBody {
|
||||
tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestAutoHTTPtoHTTPSRedirects(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
localhost:9443
|
||||
respond "Yahaha! You found me!"
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertRedirect("http://localhost:9080/", "https://localhost/", http.StatusPermanentRedirect)
|
||||
}
|
||||
@@ -56,7 +56,8 @@
|
||||
{
|
||||
"issuer": {
|
||||
"module": "internal"
|
||||
}
|
||||
},
|
||||
"key_type": "ed25519"
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
}
|
||||
acme_ca https://example.com
|
||||
acme_eab {
|
||||
key_id 4K2scIVbBpNd-78scadB2g
|
||||
hmac abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh
|
||||
key_id 4K2scIVbBpNd-78scadB2g
|
||||
mac_key abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh
|
||||
}
|
||||
acme_ca_root /path/to/ca.crt
|
||||
email test@example.com
|
||||
@@ -61,8 +61,8 @@
|
||||
"ca": "https://example.com",
|
||||
"email": "test@example.com",
|
||||
"external_account": {
|
||||
"hmac": "abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh",
|
||||
"key_id": "4K2scIVbBpNd-78scadB2g"
|
||||
"key_id": "4K2scIVbBpNd-78scadB2g",
|
||||
"mac_key": "abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
|
||||
},
|
||||
"module": "acme",
|
||||
"trusted_roots_pem_files": [
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
debug
|
||||
http_port 8080
|
||||
https_port 8443
|
||||
default_sni localhost
|
||||
order root first
|
||||
storage file_system {
|
||||
root /data
|
||||
}
|
||||
acme_ca https://example.com
|
||||
acme_ca_root /path/to/ca.crt
|
||||
|
||||
email test@example.com
|
||||
admin {
|
||||
origins localhost:2019 [::1]:2019 127.0.0.1:2019 192.168.10.128
|
||||
}
|
||||
on_demand_tls {
|
||||
ask https://example.com
|
||||
interval 30s
|
||||
burst 20
|
||||
}
|
||||
local_certs
|
||||
key_type ed25519
|
||||
}
|
||||
|
||||
:80
|
||||
----------
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2019",
|
||||
"origins": [
|
||||
"localhost:2019",
|
||||
"[::1]:2019",
|
||||
"127.0.0.1:2019",
|
||||
"192.168.10.128"
|
||||
]
|
||||
},
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "DEBUG"
|
||||
}
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"module": "file_system",
|
||||
"root": "/data"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 8080,
|
||||
"https_port": 8443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"issuer": {
|
||||
"module": "internal"
|
||||
},
|
||||
"key_type": "ed25519"
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
:80 {
|
||||
handle /api/* {
|
||||
respond "api"
|
||||
}
|
||||
|
||||
handle_path /static/* {
|
||||
respond "static"
|
||||
}
|
||||
|
||||
handle {
|
||||
respond "handle"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"group": "group3",
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/static/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"strip_path_prefix": "/static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "static",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group3",
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/api/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "api",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "group3",
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "handle",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
:80
|
||||
|
||||
log {
|
||||
output stdout
|
||||
format filter {
|
||||
wrap console
|
||||
fields {
|
||||
request>headers>Authorization delete
|
||||
request>headers>Server delete
|
||||
request>remote_addr ip_mask {
|
||||
ipv4 24
|
||||
ipv6 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
},
|
||||
"log0": {
|
||||
"writer": {
|
||||
"output": "stdout"
|
||||
},
|
||||
"encoder": {
|
||||
"fields": {
|
||||
"request\u003eheaders\u003eAuthorization": {
|
||||
"filter": "delete"
|
||||
},
|
||||
"request\u003eheaders\u003eServer": {
|
||||
"filter": "delete"
|
||||
},
|
||||
"request\u003eremote_addr": {
|
||||
"filter": "ip_mask",
|
||||
"ipv4_cidr": 24,
|
||||
"ipv6_cidr": 32
|
||||
}
|
||||
},
|
||||
"format": "filter",
|
||||
"wrap": {
|
||||
"format": "console"
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"logs": {
|
||||
"default_logger_name": "log0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,15 @@
|
||||
|
||||
@matcher3 not method PUT
|
||||
respond @matcher3 "not put"
|
||||
|
||||
@matcher4 vars "{http.request.uri}" "/vars-matcher"
|
||||
respond @matcher4 "from vars matcher"
|
||||
|
||||
@matcher5 vars_regexp static "{http.request.uri}" `\.([a-f0-9]{6})\.(css|js)$`
|
||||
respond @matcher5 "from vars_regexp matcher with name"
|
||||
|
||||
@matcher6 vars_regexp "{http.request.uri}" `\.([a-f0-9]{6})\.(css|js)$`
|
||||
respond @matcher6 "from vars_regexp matcher without name"
|
||||
}
|
||||
----------
|
||||
{
|
||||
@@ -68,6 +77,56 @@
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"vars": {
|
||||
"{http.request.uri}": "/vars-matcher"
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "from vars matcher",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"vars_regexp": {
|
||||
"{http.request.uri}": {
|
||||
"name": "static",
|
||||
"pattern": "\\.([a-f0-9]{6})\\.(css|js)$"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "from vars_regexp matcher with name",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"vars_regexp": {
|
||||
"{http.request.uri}": {
|
||||
"pattern": "\\.([a-f0-9]{6})\\.(css|js)$"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "from vars_regexp matcher without name",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
:80 {
|
||||
route {
|
||||
# unused matchers should not panic
|
||||
# see https://github.com/caddyserver/caddy/issues/3745
|
||||
@matcher1 path /path1
|
||||
@matcher2 path /path2
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
:8886
|
||||
|
||||
route {
|
||||
# Add trailing slash for directory requests
|
||||
@canonicalPath {
|
||||
file {
|
||||
try_files {path}/index.php
|
||||
}
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
|
||||
# If the requested file does not exist, try index files
|
||||
@indexFiles {
|
||||
file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
|
||||
# Proxy PHP files to the FastCGI responder
|
||||
@phpFiles {
|
||||
path *.php
|
||||
}
|
||||
reverse_proxy @phpFiles 127.0.0.1:9000 {
|
||||
transport fastcgi {
|
||||
split .php
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8886"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"try_files": [
|
||||
"{http.request.uri.path}/index.php"
|
||||
]
|
||||
},
|
||||
"not": [
|
||||
{
|
||||
"path": [
|
||||
"*/"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"uri": "{http.matchers.file.relative}"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"split_path": [
|
||||
".php"
|
||||
],
|
||||
"try_files": [
|
||||
"{http.request.uri.path}",
|
||||
"{http.request.uri.path}/index.php",
|
||||
"index.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "fastcgi",
|
||||
"split_path": [
|
||||
".php"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "127.0.0.1:9000"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"*.php"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
whoami.example.com {
|
||||
reverse_proxy whoami
|
||||
}
|
||||
|
||||
app.example.com {
|
||||
reverse_proxy app:80
|
||||
}
|
||||
unix.example.com {
|
||||
reverse_proxy unix//path/to/socket
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"whoami.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "whoami:80"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"unix.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "unix//path/to/socket"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"app.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "app:80"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
:8884
|
||||
|
||||
reverse_proxy 127.0.0.1:65535 {
|
||||
transport fastcgi
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "fastcgi"
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "127.0.0.1:65535"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
|
||||
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"
|
||||
transport http {
|
||||
versions h2c 2
|
||||
compression off
|
||||
}
|
||||
buffer_requests
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"buffer_requests": true,
|
||||
"handler": "reverse_proxy",
|
||||
"headers": {
|
||||
"request": {
|
||||
"set": {
|
||||
"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}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"transport": {
|
||||
"compression": false,
|
||||
"protocol": "http",
|
||||
"versions": [
|
||||
"h2c",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/path"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
:80
|
||||
|
||||
file_server
|
||||
|
||||
@untrusted not remote_ip 10.1.1.0/24
|
||||
file_server @untrusted
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"not": [
|
||||
{
|
||||
"remote_ip": {
|
||||
"ranges": [
|
||||
"10.1.1.0/24"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "file_server",
|
||||
"hide": [
|
||||
"Caddyfile"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "file_server",
|
||||
"hide": [
|
||||
"Caddyfile"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestBrowse(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
http://localhost:9080 {
|
||||
file_server browse
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080/", nil)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
tester.AssertResponseCode(req, 200)
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
)
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
@@ -18,25 +17,24 @@ func TestMap(t *testing.T) {
|
||||
|
||||
localhost:9080 {
|
||||
|
||||
map http.request.method dest-name {
|
||||
default unknown
|
||||
G.T get-called
|
||||
POST post-called
|
||||
map {http.request.method} {dest-1} {dest-2} {
|
||||
default unknown1 unknown2
|
||||
~G.T get-called
|
||||
POST post-called foobar
|
||||
}
|
||||
|
||||
respond /version 200 {
|
||||
body "hello from localhost {dest-name}"
|
||||
body "hello from localhost {dest-1} {dest-2}"
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
|
||||
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
|
||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called unknown2")
|
||||
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called foobar")
|
||||
}
|
||||
|
||||
func TestMapRespondWithDefault(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
@@ -46,9 +44,9 @@ func TestMapRespondWithDefault(t *testing.T) {
|
||||
|
||||
localhost:9080 {
|
||||
|
||||
map http.request.method dest-name {
|
||||
map {http.request.method} {dest-name} {
|
||||
default unknown
|
||||
GET get-called
|
||||
GET get-called
|
||||
}
|
||||
|
||||
respond /version 200 {
|
||||
@@ -63,80 +61,75 @@ func TestMapRespondWithDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMapAsJson(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "map",
|
||||
"source": "http.request.method",
|
||||
"destination": "dest-name",
|
||||
"default": "unknown",
|
||||
"items": [
|
||||
"handle": [
|
||||
{
|
||||
"expression": "GET",
|
||||
"value": "get-called"
|
||||
},
|
||||
{
|
||||
"expression": "POST",
|
||||
"value": "post-called"
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "map",
|
||||
"source": "{http.request.method}",
|
||||
"destinations": ["dest-name"],
|
||||
"defaults": ["unknown"],
|
||||
"mappings": [
|
||||
{
|
||||
"input": "GET",
|
||||
"outputs": ["get-called"]
|
||||
},
|
||||
{
|
||||
"input": "POST",
|
||||
"outputs": ["post-called"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost {dest-name}",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": ["/version"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost {dest-name}",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": ["localhost"]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
}`, "json")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
|
||||
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestSRVReverseProxy(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"lookup_srv": "srv.host.service.consul"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
}
|
||||
|
||||
func TestSRVWithDial(t *testing.T) {
|
||||
caddytest.AssertLoadError(t, `
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "tcp/address.to.upstream:80",
|
||||
"lookup_srv": "srv.host.service.consul"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json", `upstream: specifying dial address is incompatible with lookup_srv: 0: {\"dial\": \"tcp/address.to.upstream:80\", \"lookup_srv\": \"srv.host.service.consul\"}`)
|
||||
}
|
||||
|
||||
func TestDialWithPlaceholderUnix(t *testing.T) {
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
f, err := ioutil.TempFile("", "*.sock")
|
||||
if err != nil {
|
||||
t.Errorf("failed to create TempFile: %s", err)
|
||||
return
|
||||
}
|
||||
// a hack to get a file name within a valid path to use as socket
|
||||
socketName := f.Name()
|
||||
os.Remove(f.Name())
|
||||
|
||||
server := http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Write([]byte("Hello, World!"))
|
||||
}),
|
||||
}
|
||||
|
||||
unixListener, err := net.Listen("unix", socketName)
|
||||
if err != nil {
|
||||
t.Errorf("failed to listen on the socket: %s", err)
|
||||
return
|
||||
}
|
||||
go server.Serve(unixListener)
|
||||
t.Cleanup(func() {
|
||||
server.Close()
|
||||
})
|
||||
runtime.Gosched() // Allow other goroutines to run
|
||||
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "unix/{http.request.header.X-Caddy-Upstream-Dial}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Caddy-Upstream-Dial", socketName)
|
||||
tester.AssertResponse(req, 200, "Hello, World!")
|
||||
}
|
||||
|
||||
func TestReverseProxyWithPlaceholderDialAddress(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"body": "Hello, World!"
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
},
|
||||
"srv1": {
|
||||
"listen": [
|
||||
":9080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "{http.request.header.X-Caddy-Upstream-Dial}"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Caddy-Upstream-Dial", "localhost:8080")
|
||||
tester.AssertResponse(req, 200, "Hello, World!")
|
||||
}
|
||||
|
||||
func TestReverseProxyWithPlaceholderTCPDialAddress(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"body": "Hello, World!"
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
},
|
||||
"srv1": {
|
||||
"listen": [
|
||||
":9080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "tcp/{http.request.header.X-Caddy-Upstream-Dial}:8080"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:9080", nil)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-Caddy-Upstream-Dial", "localhost")
|
||||
tester.AssertResponse(req, 200, "Hello, World!")
|
||||
}
|
||||
|
||||
func TestSRVWithActiveHealthcheck(t *testing.T) {
|
||||
caddytest.AssertLoadError(t, `
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"health_checks": {
|
||||
"active": {
|
||||
"path": "/ok"
|
||||
}
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"lookup_srv": "srv.host.service.consul"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json", `upstream: lookup_srv is incompatible with active health checks: 0: {\"dial\": \"\", \"lookup_srv\": \"srv.host.service.consul\"}`)
|
||||
}
|
||||
|
||||
func TestReverseProxyHealthCheck(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
http://localhost:2020 {
|
||||
respond "Hello, World!"
|
||||
}
|
||||
http://localhost:2021 {
|
||||
respond "ok"
|
||||
}
|
||||
http://localhost:9080 {
|
||||
reverse_proxy {
|
||||
to localhost:2020
|
||||
|
||||
health_path /health
|
||||
health_port 2021
|
||||
health_interval 2s
|
||||
health_timeout 5s
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
||||
}
|
||||
|
||||
func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.SkipNow()
|
||||
}
|
||||
tester := caddytest.NewTester(t)
|
||||
f, err := ioutil.TempFile("", "*.sock")
|
||||
if err != nil {
|
||||
t.Errorf("failed to create TempFile: %s", err)
|
||||
return
|
||||
}
|
||||
// a hack to get a file name within a valid path to use as socket
|
||||
socketName := f.Name()
|
||||
os.Remove(f.Name())
|
||||
|
||||
server := http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasPrefix(req.URL.Path, "/health") {
|
||||
w.Write([]byte("ok"))
|
||||
return
|
||||
}
|
||||
w.Write([]byte("Hello, World!"))
|
||||
}),
|
||||
}
|
||||
|
||||
unixListener, err := net.Listen("unix", socketName)
|
||||
if err != nil {
|
||||
t.Errorf("failed to listen on the socket: %s", err)
|
||||
return
|
||||
}
|
||||
go server.Serve(unixListener)
|
||||
t.Cleanup(func() {
|
||||
server.Close()
|
||||
})
|
||||
runtime.Gosched() // Allow other goroutines to run
|
||||
|
||||
tester.InitServer(fmt.Sprintf(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
http://localhost:9080 {
|
||||
reverse_proxy {
|
||||
to unix/%s
|
||||
|
||||
health_path /health
|
||||
health_port 2021
|
||||
health_interval 2s
|
||||
health_timeout 5s
|
||||
}
|
||||
}
|
||||
`, socketName), "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func TestDefaultSNI(t *testing.T) {
|
||||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost")
|
||||
}
|
||||
|
||||
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
||||
@@ -204,7 +204,6 @@ func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
@@ -273,7 +272,7 @@ func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
||||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost")
|
||||
}
|
||||
|
||||
func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
// (see https://github.com/caddyserver/caddy/issues/3556 for use case)
|
||||
func TestH2ToH2CStream(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"compression": false,
|
||||
"versions": [
|
||||
"h2c",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/tov2ray"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
expectedBody := "some data to be echoed"
|
||||
// start the server
|
||||
server := testH2ToH2CStreamServeH2C(t)
|
||||
go server.ListenAndServe()
|
||||
defer func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
||||
defer cancel()
|
||||
server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
r, w := io.Pipe()
|
||||
req := &http.Request{
|
||||
Method: "PUT",
|
||||
Body: ioutil.NopCloser(r),
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "127.0.0.1:9443",
|
||||
Path: "/tov2ray",
|
||||
},
|
||||
Proto: "HTTP/2",
|
||||
ProtoMajor: 2,
|
||||
ProtoMinor: 0,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
// Disable any compression method from server.
|
||||
req.Header.Set("Accept-Encoding", "identity")
|
||||
|
||||
resp := tester.AssertResponseCode(req, 200)
|
||||
if 200 != resp.StatusCode {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
fmt.Fprint(w, expectedBody)
|
||||
w.Close()
|
||||
}()
|
||||
|
||||
defer resp.Body.Close()
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read the response body %s", err)
|
||||
}
|
||||
|
||||
body := string(bytes)
|
||||
|
||||
if !strings.Contains(body, expectedBody) {
|
||||
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
|
||||
h2s := &http2.Server{}
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rstring, err := httputil.DumpRequest(r, false)
|
||||
if err == nil {
|
||||
t.Logf("h2c server received req: %s", rstring)
|
||||
}
|
||||
// We only accept HTTP/2!
|
||||
if r.ProtoMajor != 2 {
|
||||
t.Error("Not a HTTP/2 request, rejected!")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Host != "127.0.0.1:9443" {
|
||||
t.Errorf("r.Host doesn't match, %v!", r.Host)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(r.URL.Path, "/tov2ray") {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(200)
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
buf := make([]byte, 4*1024)
|
||||
|
||||
for {
|
||||
n, err := r.Body.Read(buf)
|
||||
if n > 0 {
|
||||
w.Write(buf[:n])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
r.Body.Close()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "127.0.0.1:54321",
|
||||
Handler: h2c.NewHandler(handler, h2s),
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// (see https://github.com/caddyserver/caddy/issues/3606 for use case)
|
||||
func TestH2ToH1ChunkedResponse(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "DEBUG"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"encodings": {
|
||||
"gzip": {}
|
||||
},
|
||||
"handler": "encode"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/tov2ray"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": [
|
||||
"cert0"
|
||||
]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
// need a large body here to trigger caddy's compression, larger than gzip.miniLength
|
||||
expectedBody, err := GenerateRandomString(1024)
|
||||
if err != nil {
|
||||
t.Fatalf("generate expected body failed, err: %s", err)
|
||||
}
|
||||
|
||||
// start the server
|
||||
server := testH2ToH1ChunkedResponseServeH1(t)
|
||||
go server.ListenAndServe()
|
||||
defer func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
||||
defer cancel()
|
||||
server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
r, w := io.Pipe()
|
||||
req := &http.Request{
|
||||
Method: "PUT",
|
||||
Body: ioutil.NopCloser(r),
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "127.0.0.1:9443",
|
||||
Path: "/tov2ray",
|
||||
},
|
||||
Proto: "HTTP/2",
|
||||
ProtoMajor: 2,
|
||||
ProtoMinor: 0,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
// underlying transport will automaticlly add gzip
|
||||
// req.Header.Set("Accept-Encoding", "gzip")
|
||||
go func() {
|
||||
fmt.Fprint(w, expectedBody)
|
||||
w.Close()
|
||||
}()
|
||||
resp := tester.AssertResponseCode(req, 200)
|
||||
if 200 != resp.StatusCode {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read the response body %s", err)
|
||||
}
|
||||
|
||||
body := string(bytes)
|
||||
|
||||
if body != expectedBody {
|
||||
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Host != "127.0.0.1:9443" {
|
||||
t.Errorf("r.Host doesn't match, %v!", r.Host)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(r.URL.Path, "/tov2ray") {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
bytes, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read the response body %s", err)
|
||||
}
|
||||
|
||||
n := len(bytes)
|
||||
|
||||
var writer io.Writer
|
||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
gw, err := gzip.NewWriterLevel(w, 5)
|
||||
if err != nil {
|
||||
t.Error("can't return gzip data")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer gw.Close()
|
||||
writer = gw
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
w.Header().Del("Content-Length")
|
||||
w.WriteHeader(200)
|
||||
} else {
|
||||
writer = w
|
||||
}
|
||||
if n > 0 {
|
||||
writer.Write(bytes[:])
|
||||
}
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "127.0.0.1:54321",
|
||||
Handler: handler,
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// GenerateRandomBytes returns securely generated random bytes.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
// Note that err == nil only if we read len(b) bytes.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// GenerateRandomString returns a securely generated random string.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomString(n int) (string, error) {
|
||||
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
bytes, err := GenerateRandomBytes(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for i, b := range bytes {
|
||||
bytes[i] = letters[b%byte(len(letters))]
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
+13
-4
@@ -533,7 +533,17 @@ func cmdFmt(fl Flags) (int, error) {
|
||||
if formatCmdConfigFile == "" {
|
||||
formatCmdConfigFile = "Caddyfile"
|
||||
}
|
||||
overwrite := fl.Bool("overwrite")
|
||||
|
||||
// as a special case, read from stdin if the file name is "-"
|
||||
if formatCmdConfigFile == "-" {
|
||||
input, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("reading stdin: %v", err)
|
||||
}
|
||||
fmt.Print(string(caddyfile.Format(input)))
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
input, err := ioutil.ReadFile(formatCmdConfigFile)
|
||||
if err != nil {
|
||||
@@ -543,9 +553,8 @@ func cmdFmt(fl Flags) (int, error) {
|
||||
|
||||
output := caddyfile.Format(input)
|
||||
|
||||
if overwrite {
|
||||
err = ioutil.WriteFile(formatCmdConfigFile, output, 0644)
|
||||
if err != nil {
|
||||
if fl.Bool("overwrite") {
|
||||
if err := ioutil.WriteFile(formatCmdConfigFile, output, 0600); err != nil {
|
||||
return caddy.ExitCodeFailedStartup, nil
|
||||
}
|
||||
} else {
|
||||
|
||||
+6
-2
@@ -263,8 +263,12 @@ provisioning stages.`,
|
||||
Formats the Caddyfile by adding proper indentation and spaces to improve
|
||||
human readability. It prints the result to stdout.
|
||||
|
||||
If --write is specified, the output will be written to the config file
|
||||
directly instead of printing it.`,
|
||||
If --overwrite is specified, the output will be written to the config file
|
||||
directly instead of printing it.
|
||||
|
||||
If you wish you use stdin instead of a regular file, use - as the path.
|
||||
When reading from stdin, the --overwrite flag has no effect: the result
|
||||
is always printed to stdout.`,
|
||||
Flags: func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("format", flag.ExitOnError)
|
||||
fs.Bool("overwrite", false, "Overwrite the input file with the results")
|
||||
|
||||
@@ -410,6 +410,7 @@ func printEnvironment() {
|
||||
fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir())
|
||||
fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir())
|
||||
fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath)
|
||||
fmt.Printf("caddy.Version=%s\n", caddy.GoModule().Version)
|
||||
fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS)
|
||||
fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH)
|
||||
fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler)
|
||||
|
||||
@@ -4,31 +4,31 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.1.0
|
||||
github.com/alecthomas/chroma v0.7.4-0.20200517063913-500529fd43c1
|
||||
github.com/alecthomas/chroma v0.8.0
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
|
||||
github.com/caddyserver/certmagic v0.11.2
|
||||
github.com/caddyserver/certmagic v0.12.0
|
||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
|
||||
github.com/go-acme/lego/v3 v3.7.0
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/google/cel-go v0.5.1
|
||||
github.com/jsternberg/zap-logfmt v1.2.0
|
||||
github.com/klauspost/compress v1.10.10
|
||||
github.com/klauspost/cpuid v1.3.0
|
||||
github.com/libdns/libdns v0.0.0-20200501023120-186724ffc821
|
||||
github.com/lucas-clemente/quic-go v0.17.1
|
||||
github.com/klauspost/compress v1.11.0
|
||||
github.com/klauspost/cpuid v1.2.5 // cannot upgrade until arm is fixed: https://github.com/klauspost/cpuid/issues/52
|
||||
github.com/lucas-clemente/quic-go v0.18.0
|
||||
github.com/mholt/acmez v0.1.1
|
||||
github.com/naoina/go-stringutil v0.1.0 // indirect
|
||||
github.com/naoina/toml v0.1.1
|
||||
github.com/smallstep/certificates v0.15.0-rc.1.0.20200506212953-e855707dc274
|
||||
github.com/smallstep/cli v0.14.4
|
||||
github.com/smallstep/nosql v0.3.0
|
||||
github.com/smallstep/truststore v0.9.5
|
||||
github.com/yuin/goldmark v1.1.32
|
||||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/smallstep/certificates v0.15.4
|
||||
github.com/smallstep/cli v0.15.2
|
||||
github.com/smallstep/nosql v0.3.0 // cannot upgrade until protobuf warning is fixed
|
||||
github.com/smallstep/truststore v0.9.6
|
||||
github.com/yuin/goldmark v1.2.1
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
||||
go.uber.org/zap v1.15.0
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
|
||||
google.golang.org/protobuf v1.25.0
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98
|
||||
google.golang.org/protobuf v1.24.0 // cannot upgrade until warning is fixed
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
||||
@@ -7,24 +7,12 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM=
|
||||
cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
|
||||
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
@@ -35,19 +23,6 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
|
||||
github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@@ -60,30 +35,24 @@ github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RP
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig/v3 v3.0.0/go.mod h1:NEUY/Qq8Gdm2xgYA+NwJM6wmfdRV9xkh8h/Rld20R0U=
|
||||
github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y=
|
||||
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
|
||||
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/ThomasRooney/gexpect v0.0.0-20161231170123-5482f0350944/go.mod h1:sPML5WwI6oxLRLPuuqbtoOKhtmpVDCYtwsps+I+vjIY=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8/go.mod h1:aVvklgKsPENRkl29bNwrHISa1F+YLGTHArMxZMBqWM8=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
|
||||
github.com/alecthomas/chroma v0.7.4-0.20200517063913-500529fd43c1 h1:9jsf8ot7rz1BywS9yYLkSiIeqU8LQw1D8gXVgdqsbvs=
|
||||
github.com/alecthomas/chroma v0.7.4-0.20200517063913-500529fd43c1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/chroma v0.8.0 h1:HS+HE97sgcqjQGu5uVr8jIE55Mmh5UeQ7kckAhHg2pY=
|
||||
github.com/alecthomas/chroma v0.8.0/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
@@ -96,34 +65,33 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.112/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
|
||||
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/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.30.20/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0=
|
||||
github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U=
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/caddyserver/certmagic v0.11.2 h1:nPBqyuFNHJEf2FwC1ixJjArtTKWyPqpaH6k4jl7gxYI=
|
||||
github.com/caddyserver/certmagic v0.11.2/go.mod h1:fqY1IZk5iqhsj5FU3Vw20Sjq66tEKaanTFYNZ74soMY=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/caddyserver/certmagic v0.12.0 h1:1f7kxykaJkOVVpXJ8ZrC6RAO5F6+kKm9U7dBFbLNeug=
|
||||
github.com/caddyserver/certmagic v0.12.0/go.mod h1:tr26xh+9fY5dN0J6IPAlMj07qpog22PJKa7Nw7j835U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
@@ -134,7 +102,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
|
||||
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
@@ -148,7 +115,6 @@ github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
|
||||
github.com/cpu/goacmedns v0.0.2/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
@@ -171,52 +137,38 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v0.60.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg=
|
||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-acme/lego/v3 v3.7.0 h1:qC5/8/CbltyAE8fGLE6bGlqucj7pXc/vBxiLwLOsmAQ=
|
||||
github.com/go-acme/lego/v3 v3.7.0/go.mod h1:4eDjjYkAsDXyNcwN8IhhZAwxz9Ltiks1Zmpv0q20J7A=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
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=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-critic/go-critic v0.4.0/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||
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-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-piv/piv-go v1.5.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
@@ -236,43 +188,37 @@ github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslW
|
||||
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
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=
|
||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1 h1:ocYkMQY5RrXTYgXl7ICpV0IXwlEQGwKIsery4gyXa1U=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
|
||||
@@ -308,8 +254,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@@ -319,8 +263,6 @@ github.com/google/monologue v0.0.0-20191220140058-35abc9683a6c/go.mod h1:6NTfaQo
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/trillian v1.2.2-0.20190612132142-05461f4df60a/go.mod h1:YPmUVn5NGwgnDUgqlVyFGMTgaWlnSvH7W5p+NdOG8UA=
|
||||
github.com/google/trillian-examples v0.0.0-20190603134952-4e75ba15216c/go.mod h1:WgL3XZ3pA8/9cm7yxqWrZE6iZkESB2ItGxy5Fo6k2lk=
|
||||
@@ -333,12 +275,9 @@ github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE0
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
@@ -350,38 +289,30 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
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/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
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/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o=
|
||||
@@ -398,15 +329,12 @@ github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7IL
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
|
||||
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
|
||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.3.0 h1:2JqaNE1hGdABW2YbA3TenkO7RiPFRvSWnEnGqWh9sHE=
|
||||
github.com/klauspost/cpuid v1.3.0/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||||
github.com/klauspost/cpuid v1.2.5 h1:VBd9MyVIiJHzzgnrLQG5Bcv75H4YaWrlKqWHjurxCGo=
|
||||
github.com/klauspost/cpuid v1.2.5/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
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 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -420,18 +348,14 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
||||
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
||||
github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible/go.mod h1:iGYXKqDXt0cpBthCHdr9ZdsQwyGlYFh/+8xa4WzIQ34=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/libdns/libdns v0.0.0-20200501023120-186724ffc821 h1:663opx/RKxiISi1ozf0WbvweQpYBgf34dx8hKSIau3w=
|
||||
github.com/libdns/libdns v0.0.0-20200501023120-186724ffc821/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/libdns/libdns v0.1.0 h1:0ctCOrVJsVzj53mop1angHp/pE3hmAhP7KiHvR0HD04=
|
||||
github.com/libdns/libdns v0.1.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lucas-clemente/quic-go v0.17.1 h1:ezsH76xpn6hKugfsXUy6voIJBFmAOwnM/Oy9F4b/n+M=
|
||||
github.com/lucas-clemente/quic-go v0.17.1/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE=
|
||||
github.com/lucas-clemente/quic-go v0.18.0 h1:JhQDdqxdwdmGdKsKgXi1+coHRoGhvU6z0rNzOJqZ/4o=
|
||||
github.com/lucas-clemente/quic-go v0.18.0/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
|
||||
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -440,33 +364,34 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/manifoldco/promptui v0.3.1 h1:BxqNa7q1hVHXIXy3iupJMkXYS3aHhbubJWv2Jmg6x64=
|
||||
github.com/manifoldco/promptui v0.3.1/go.mod h1:zoCNXiJnyM03LlBgTsWv8mq28s7aTC71UgKasqRJHww=
|
||||
github.com/marten-seemann/qpack v0.1.0 h1:/0M7lkda/6mus9B8u34Asqm8ZhHAAt9Ho0vniNuVSVg=
|
||||
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
|
||||
github.com/marten-seemann/qtls v0.9.1 h1:O0YKQxNVPaiFgMng0suWEOY2Sb4LT2sRn9Qimq3Z1IQ=
|
||||
github.com/marten-seemann/qtls v0.9.1/go.mod h1:T1MmAdDPyISzxlK6kjRr0pcZFBVd1OZbBb/j3cvzHhk=
|
||||
github.com/marten-seemann/qpack v0.2.0 h1:/r1rhZoOmgxVKBqPNnYilZBDEyw+6OUHCbBzA5jc2y0=
|
||||
github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
||||
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg=
|
||||
github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/acmez v0.1.1 h1:KQODCqk+hBn3O7qfCRPj6L96uG65T5BSS95FKNEqtdA=
|
||||
github.com/mholt/acmez v0.1.1/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
|
||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
@@ -474,7 +399,6 @@ 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 v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
@@ -488,12 +412,10 @@ github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:
|
||||
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
|
||||
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
|
||||
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
|
||||
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
@@ -502,38 +424,33 @@ github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a
|
||||
github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ=
|
||||
github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/pierrec/lz4 v2.0.5+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 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -542,35 +459,34 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/pquerna/otp v1.0.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@@ -584,7 +500,6 @@ 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/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg=
|
||||
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
|
||||
@@ -624,29 +539,27 @@ github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
|
||||
github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15 h1:kSImCuenAkXtCaBeQ1UhmzzJGRhSm8sVH7I3sHE2Qdg=
|
||||
github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||
github.com/smallstep/certificates v0.14.4 h1:oMxSrsjMKfEDqzBooMuwsBtk0QgYEXrb6fr8YPcP1WI=
|
||||
github.com/smallstep/certificates v0.14.4/go.mod h1:Y9ug0+ZTB0k22BBV/2K+LAZIVDCMjAAtbQ0XWS+E870=
|
||||
github.com/smallstep/certificates v0.15.0-rc.1.0.20200506212953-e855707dc274 h1:3rzfukPUfHp5K4gscalOzpCDAfnZJKETfZpmGLprFt4=
|
||||
github.com/smallstep/certificates v0.15.0-rc.1.0.20200506212953-e855707dc274/go.mod h1:tARNIrrKV9x+uiTVESAONGfS3GA2GdE3pGfNscMPvDc=
|
||||
github.com/smallstep/certinfo v1.2.1/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU=
|
||||
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.15.0/go.mod h1:awyVXYIVn4J9ANSSnDuA3FjKia7+QixaqorGW8CKGq8=
|
||||
github.com/smallstep/certificates v0.15.1 h1:Es2IGgU4D0RE4OKys/VB1KvAuGuZwkxR7D9A0DeM/Ow=
|
||||
github.com/smallstep/certificates v0.15.1/go.mod h1:hDmNejfuVTDDe1NBIKrhL9xjELRJb2ZFM+bDXwSW9xI=
|
||||
github.com/smallstep/certificates v0.15.4 h1:bBGb2GqrQ8wKHVOhcUfgRlaTHsO1S5rqsq/z93/mRSc=
|
||||
github.com/smallstep/certificates v0.15.4/go.mod h1:uMrxSjDsPBxCPLV58WQhj3L1R3zdnW7mvJloiC+cyws=
|
||||
github.com/smallstep/certinfo v1.3.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU=
|
||||
github.com/smallstep/cli v0.14.3 h1:GghXS0NBoj7psz10Cds86vvcrz6Pmww8aMGKEiD04AI=
|
||||
github.com/smallstep/cli v0.14.3/go.mod h1:U3WVQBS6udgpyHg+d0FQxodNGXBwikoXbERyfgcAhs8=
|
||||
github.com/smallstep/cli v0.14.4 h1:oaaGwY1iNSHmIC6baiJhdZoLGmXJ0rXyRcxl3Q73SDE=
|
||||
github.com/smallstep/cli v0.14.4/go.mod h1:cI1F2o1ugIcP1qklLPBECfeR7v7mFxVE6pVNQIMNWpA=
|
||||
github.com/smallstep/cli v0.15.0 h1:ZBU1o4vm4FvcOFnfiT09jxuE/OsZX1BsZl5E/l2gv/c=
|
||||
github.com/smallstep/cli v0.15.0/go.mod h1:trYpP49s+XF4fROcNgmPi4yO1EIKfqsc/eEck6SosO4=
|
||||
github.com/smallstep/cli v0.15.2 h1:bOrYD1w0Vu82XN3r7mHuXoEI9RyczHyzKjzDHQ7V7EE=
|
||||
github.com/smallstep/cli v0.15.2/go.mod h1:7SDI+reLecUkYdaluSt3ASH2vQYHxdeMXtsIGD9AQXA=
|
||||
github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U=
|
||||
github.com/smallstep/nosql v0.3.0/go.mod h1:QG7gNOpidifn99MjZaiNbm7HPesIyBd97F/OfacNz8Q=
|
||||
github.com/smallstep/truststore v0.9.3/go.mod h1:PRSkpRIhAYBK/KLWkHNgRdYgzWMEy45bN7PSJCfKKGE=
|
||||
github.com/smallstep/truststore v0.9.5 h1:KQ6bFXUadu3PG57sFSIBsu2pb/35NqO+MyS2Pvi62bA=
|
||||
github.com/smallstep/truststore v0.9.5/go.mod h1:HwHKRcBi0RUxxw1LYDpTRhYC4jZUuxPpkHdVonlkoDM=
|
||||
github.com/smallstep/truststore v0.9.6 h1:vNzEJmaJL0XOZD8uouXLmYu4/aP1UQ/wHUopH3qKeYA=
|
||||
github.com/smallstep/truststore v0.9.6/go.mod h1:HwHKRcBi0RUxxw1LYDpTRhYC4jZUuxPpkHdVonlkoDM=
|
||||
github.com/smallstep/zcrypto v0.0.0-20200203191936-fbc32cf76bce/go.mod h1:+F24VU3UCxfVFvvqgm5jNUFQOm/L6ed13ImwWGFgg/g=
|
||||
github.com/smallstep/zlint v0.0.0-20180727184541-d84eaafe274f/go.mod h1:GeHHT7sJDI9ti3oEaFnvx1F4N8n3ZSw2YM1+sbEoxc4=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
@@ -685,19 +598,15 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
|
||||
github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
|
||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
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/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
|
||||
@@ -709,16 +618,12 @@ github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOV
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
|
||||
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32 h1:5tjfNdR2ki3yYQ842+eX2sQHeiwpKJ0RnHO4IYOc4V8=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
|
||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
@@ -732,13 +637,16 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
|
||||
go.etcd.io/etcd v3.3.18+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.step.sm/crypto v0.0.0-20200805202904-ec18b6df3cf0/go.mod h1:8VYxmvSKt5yOTBx3MGsD2Gk4F1Es/3FIxrjnfeYWE8U=
|
||||
go.step.sm/crypto v0.1.1/go.mod h1:cIoSWTfTQ5xqvwTeZH9ZXZzi6jdMepjK4A/TDWMUvw8=
|
||||
go.step.sm/crypto v0.2.0 h1:Rx+XqrNO4ZGHWlT9QCXls2L9pvcNiI23zEpAq0fctvY=
|
||||
go.step.sm/crypto v0.2.0/go.mod h1:YNLnHj4JgABFoRkUq8brkscIB9THdiJUFoDxLQw1tww=
|
||||
go.step.sm/crypto v0.6.0 h1:fbGUG5VJmDetC+RQ/T0tb6Sx0wCOgqKZcZYzTpUa7eo=
|
||||
go.step.sm/crypto v0.6.0/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0=
|
||||
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=
|
||||
@@ -750,7 +658,6 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+
|
||||
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
@@ -760,37 +667,27 @@ go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -801,20 +698,14 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/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=
|
||||
@@ -824,9 +715,7 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -837,27 +726,22 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -868,22 +752,18 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -891,27 +771,24 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -921,7 +798,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -959,26 +835,13 @@ golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc h1:MR2F33ipDGog0C4eMhU6u9o3q6c3dvYis2aG6Jl12Wg=
|
||||
golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb h1:iKlO7ROJc6SttHKlxzwGytRtBUqX4VARrNTgP2YLX5M=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
@@ -986,20 +849,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1022,23 +879,15 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28=
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1054,11 +903,10 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
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=
|
||||
@@ -1067,22 +915,18 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
|
||||
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A=
|
||||
gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
@@ -1092,8 +936,6 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
@@ -1102,12 +944,10 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc=
|
||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
// define and register the metrics used in this package.
|
||||
func init() {
|
||||
prometheus.MustRegister(prometheus.NewBuildInfoCollector())
|
||||
|
||||
const ns, sub = "caddy", "admin"
|
||||
|
||||
adminMetrics.requestCount = 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{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "http_request_errors_total",
|
||||
Help: "Number of requests resulting in middleware errors.",
|
||||
}, []string{"handler", "path", "method"})
|
||||
}
|
||||
|
||||
// adminMetrics is a collection of metrics that can be tracked for the admin API.
|
||||
var adminMetrics = struct {
|
||||
requestCount *prometheus.CounterVec
|
||||
requestErrors *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": sanitizeCode(d.status),
|
||||
"method": strings.ToUpper(r.Method),
|
||||
}).Inc()
|
||||
})
|
||||
}
|
||||
|
||||
func newDelegator(w http.ResponseWriter) *delegator {
|
||||
return &delegator{
|
||||
ResponseWriter: w,
|
||||
}
|
||||
}
|
||||
|
||||
type delegator struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (d *delegator) WriteHeader(code int) {
|
||||
d.status = code
|
||||
d.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func sanitizeCode(s int) string {
|
||||
switch s {
|
||||
case 0, 200:
|
||||
return "200"
|
||||
default:
|
||||
return strconv.Itoa(s)
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ func init() {
|
||||
//
|
||||
// Placeholder | Description
|
||||
// ------------|---------------
|
||||
// `{http.request.body}` | The request body (⚠️ inefficient; use only for debugging)
|
||||
// `{http.request.cookie.*}` | HTTP request cookie
|
||||
// `{http.request.header.*}` | Specific request header field
|
||||
// `{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo
|
||||
@@ -154,6 +155,7 @@ func (app *App) Provision(ctx caddy.Context) error {
|
||||
|
||||
// prepare each server
|
||||
for srvName, srv := range app.Servers {
|
||||
srv.name = srvName
|
||||
srv.tlsApp = app.tlsApp
|
||||
srv.logger = app.logger.Named("log")
|
||||
srv.errorLogger = app.logger.Named("log.error")
|
||||
@@ -280,6 +282,12 @@ func (app *App) Validate() error {
|
||||
// Start runs the app. It finishes automatic HTTPS if enabled,
|
||||
// including management of certificates.
|
||||
func (app *App) Start() error {
|
||||
// get a logger compatible with http.Server
|
||||
serverLogger, err := zap.NewStdLogAt(app.logger.Named("stdlib"), zap.DebugLevel)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set up server logger: %v", err)
|
||||
}
|
||||
|
||||
for srvName, srv := range app.Servers {
|
||||
s := &http.Server{
|
||||
ReadTimeout: time.Duration(srv.ReadTimeout),
|
||||
@@ -288,6 +296,7 @@ func (app *App) Start() error {
|
||||
IdleTimeout: time.Duration(srv.IdleTimeout),
|
||||
MaxHeaderBytes: srv.MaxHeaderBytes,
|
||||
Handler: srv,
|
||||
ErrorLog: serverLogger,
|
||||
}
|
||||
|
||||
// enable h2c if configured
|
||||
@@ -343,6 +352,7 @@ func (app *App) Start() error {
|
||||
Addr: hostport,
|
||||
Handler: srv,
|
||||
TLSConfig: tlsCfg,
|
||||
ErrorLog: serverLogger,
|
||||
},
|
||||
}
|
||||
go h3srv.Serve(h3ln)
|
||||
@@ -381,7 +391,7 @@ func (app *App) Start() error {
|
||||
|
||||
// finish automatic HTTPS by finally beginning
|
||||
// certificate management
|
||||
err := app.automaticHTTPSPhase2()
|
||||
err = app.automaticHTTPSPhase2()
|
||||
if err != nil {
|
||||
return fmt.Errorf("finalizing automatic HTTPS: %v", err)
|
||||
}
|
||||
|
||||
@@ -451,8 +451,8 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
|
||||
if ap.Issuer == nil {
|
||||
ap.Issuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
if acmeIssuer, ok := ap.Issuer.(*caddytls.ACMEIssuer); ok {
|
||||
err := app.fillInACMEIssuer(acmeIssuer)
|
||||
if acmeIssuer, ok := ap.Issuer.(acmeCapable); ok {
|
||||
err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -470,9 +470,13 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
|
||||
basePolicy = new(caddytls.AutomationPolicy)
|
||||
}
|
||||
|
||||
// if the basePolicy has an existing ACMEIssuer, let's
|
||||
// use it, otherwise we'll make one
|
||||
baseACMEIssuer, _ := basePolicy.Issuer.(*caddytls.ACMEIssuer)
|
||||
// if the basePolicy has an existing ACMEIssuer (particularly to
|
||||
// include any type that embeds/wraps an ACMEIssuer), let's use it,
|
||||
// otherwise we'll make one
|
||||
var baseACMEIssuer *caddytls.ACMEIssuer
|
||||
if acmeWrapper, ok := basePolicy.Issuer.(acmeCapable); ok {
|
||||
baseACMEIssuer = acmeWrapper.GetACMEIssuer()
|
||||
}
|
||||
if baseACMEIssuer == nil {
|
||||
// note that this happens if basePolicy.Issuer is nil
|
||||
// OR if it is not nil but is not an ACMEIssuer
|
||||
@@ -630,3 +634,5 @@ func (app *App) automaticHTTPSPhase2() error {
|
||||
app.allCertDomains = nil // no longer needed; allow GC to deallocate
|
||||
return nil
|
||||
}
|
||||
|
||||
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
|
||||
|
||||
@@ -116,7 +116,7 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
|
||||
var hash []byte
|
||||
switch algorithm {
|
||||
case "bcrypt":
|
||||
hash, err = bcrypt.GenerateFromPassword(plaintext, bcrypt.DefaultCost)
|
||||
hash, err = bcrypt.GenerateFromPassword(plaintext, 14)
|
||||
case "scrypt":
|
||||
def := ScryptHash{}
|
||||
def.SetDefaults()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -199,6 +200,27 @@ func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
|
||||
func (celHTTPRequest) Type() ref.Type { return httpRequestCELType }
|
||||
func (cr celHTTPRequest) Value() interface{} { return cr }
|
||||
|
||||
var pkixNameCELType = types.NewTypeValue("pkix.Name", traits.ReceiverType)
|
||||
|
||||
// celPkixName wraps an pkix.Name with
|
||||
// methods to satisfy the ref.Val interface.
|
||||
type celPkixName struct{ *pkix.Name }
|
||||
|
||||
func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
return pn.Name, nil
|
||||
}
|
||||
func (celPkixName) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (pn celPkixName) Equal(other ref.Val) ref.Val {
|
||||
if o, ok := other.Value().(string); ok {
|
||||
return types.Bool(pn.Name.String() == o)
|
||||
}
|
||||
return types.ValOrErr(other, "%v is not comparable type", other)
|
||||
}
|
||||
func (celPkixName) Type() ref.Type { return pkixNameCELType }
|
||||
func (pn celPkixName) Value() interface{} { return pn }
|
||||
|
||||
// celTypeAdapter can adapt our custom types to a CEL value.
|
||||
type celTypeAdapter struct{}
|
||||
|
||||
@@ -206,6 +228,8 @@ func (celTypeAdapter) NativeToValue(value interface{}) ref.Val {
|
||||
switch v := value.(type) {
|
||||
case celHTTPRequest:
|
||||
return v
|
||||
case pkix.Name:
|
||||
return celPkixName{&v}
|
||||
case time.Time:
|
||||
// TODO: eliminate direct protobuf dependency, sigh -- just wrap stdlib time.Time instead...
|
||||
return types.Timestamp{Timestamp: ×tamp.Timestamp{Seconds: v.Unix(), Nanos: int32(v.Nanosecond())}}
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
@@ -27,7 +32,7 @@ func TestMatchExpressionProvision(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "boolean mtaches succeed",
|
||||
name: "boolean matches succeed",
|
||||
expression: &MatchExpression{
|
||||
Expr: "{http.request.uri.query} != ''",
|
||||
},
|
||||
@@ -49,3 +54,71 @@ 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)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re
|
||||
sortParam := r.URL.Query().Get("sort")
|
||||
orderParam := r.URL.Query().Get("order")
|
||||
limitParam := r.URL.Query().Get("limit")
|
||||
offsetParam := r.URL.Query().Get("offset")
|
||||
|
||||
// first figure out what to sort by
|
||||
switch sortParam {
|
||||
@@ -130,7 +131,7 @@ func (fsrv *FileServer) browseApplyQueryParams(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// finally, apply the sorting and limiting
|
||||
listing.applySortAndLimit(sortParam, orderParam, limitParam)
|
||||
listing.applySortAndLimit(sortParam, orderParam, limitParam, offsetParam)
|
||||
}
|
||||
|
||||
func (fsrv *FileServer) browseWriteJSON(listing browseListing) (*bytes.Buffer, error) {
|
||||
|
||||
@@ -11,15 +11,15 @@ func BenchmarkBrowseWriteJSON(b *testing.B) {
|
||||
fsrv := new(FileServer)
|
||||
fsrv.Provision(caddy.Context{})
|
||||
listing := browseListing{
|
||||
Name: "test",
|
||||
Path: "test",
|
||||
CanGoUp: false,
|
||||
Items: make([]fileInfo, 100),
|
||||
NumDirs: 42,
|
||||
NumFiles: 420,
|
||||
Sort: "",
|
||||
Order: "",
|
||||
ItemsLimitedTo: 42,
|
||||
Name: "test",
|
||||
Path: "test",
|
||||
CanGoUp: false,
|
||||
Items: make([]fileInfo, 100),
|
||||
NumDirs: 42,
|
||||
NumFiles: 420,
|
||||
Sort: "",
|
||||
Order: "",
|
||||
Limit: 42,
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
@@ -36,15 +36,15 @@ func BenchmarkBrowseWriteHTML(b *testing.B) {
|
||||
template: template.New("test"),
|
||||
}
|
||||
listing := browseListing{
|
||||
Name: "test",
|
||||
Path: "test",
|
||||
CanGoUp: false,
|
||||
Items: make([]fileInfo, 100),
|
||||
NumDirs: 42,
|
||||
NumFiles: 420,
|
||||
Sort: "",
|
||||
Order: "",
|
||||
ItemsLimitedTo: 42,
|
||||
Name: "test",
|
||||
Path: "test",
|
||||
CanGoUp: false,
|
||||
Items: make([]fileInfo, 100),
|
||||
NumDirs: 42,
|
||||
NumFiles: 420,
|
||||
Sort: "",
|
||||
Order: "",
|
||||
Limit: 42,
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
|
||||
@@ -76,31 +76,34 @@ func (fsrv *FileServer) directoryListing(files []os.FileInfo, canGoUp bool, urlP
|
||||
|
||||
type browseListing struct {
|
||||
// The name of the directory (the last element of the path).
|
||||
Name string
|
||||
Name string `json:"name"`
|
||||
|
||||
// The full path of the request.
|
||||
Path string
|
||||
Path string `json:"path"`
|
||||
|
||||
// Whether the parent directory is browseable.
|
||||
CanGoUp bool
|
||||
CanGoUp bool `json:"can_go_up"`
|
||||
|
||||
// The items (files and folders) in the path.
|
||||
Items []fileInfo
|
||||
Items []fileInfo `json:"items,omitempty"`
|
||||
|
||||
// The number of directories in the listing.
|
||||
NumDirs int
|
||||
|
||||
// The number of files (items that aren't directories) in the listing.
|
||||
NumFiles int
|
||||
|
||||
// Sort column used
|
||||
Sort string
|
||||
|
||||
// Sorting order
|
||||
Order string
|
||||
// If ≠0 then Items starting from that many elements.
|
||||
Offset int `json:"offset,omitempty"`
|
||||
|
||||
// If ≠0 then Items have been limited to that many elements.
|
||||
ItemsLimitedTo int
|
||||
Limit int `json:"limit,omitempty"`
|
||||
|
||||
// The number of directories in the listing.
|
||||
NumDirs int `json:"num_dirs"`
|
||||
|
||||
// The number of files (items that aren't directories) in the listing.
|
||||
NumFiles int `json:"num_files"`
|
||||
|
||||
// Sort column used
|
||||
Sort string `json:"sort,omitempty"`
|
||||
|
||||
// Sorting order
|
||||
Order string `json:"order,omitempty"`
|
||||
}
|
||||
|
||||
// Breadcrumbs returns l.Path where every element maps
|
||||
@@ -131,7 +134,7 @@ func (l browseListing) Breadcrumbs() []crumb {
|
||||
return result
|
||||
}
|
||||
|
||||
func (l *browseListing) applySortAndLimit(sortParam, orderParam, limitParam string) {
|
||||
func (l *browseListing) applySortAndLimit(sortParam, orderParam, limitParam string, offsetParam string) {
|
||||
l.Sort = sortParam
|
||||
l.Order = orderParam
|
||||
|
||||
@@ -159,11 +162,20 @@ func (l *browseListing) applySortAndLimit(sortParam, orderParam, limitParam stri
|
||||
}
|
||||
}
|
||||
|
||||
if offsetParam != "" {
|
||||
offset, _ := strconv.Atoi(offsetParam)
|
||||
if offset > 0 && offset <= len(l.Items) {
|
||||
l.Items = l.Items[offset:]
|
||||
l.Offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
if limitParam != "" {
|
||||
limit, _ := strconv.Atoi(limitParam)
|
||||
|
||||
if limit > 0 && limit <= len(l.Items) {
|
||||
l.Items = l.Items[:limit]
|
||||
l.ItemsLimitedTo = limit
|
||||
l.Limit = limit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,8 +283,8 @@ footer {
|
||||
<div id="summary">
|
||||
<span class="meta-item"><b>{{.NumDirs}}</b> director{{if eq 1 .NumDirs}}y{{else}}ies{{end}}</span>
|
||||
<span class="meta-item"><b>{{.NumFiles}}</b> file{{if ne 1 .NumFiles}}s{{end}}</span>
|
||||
{{- if ne 0 .ItemsLimitedTo}}
|
||||
<span class="meta-item">(of which only <b>{{.ItemsLimitedTo}}</b> are displayed)</span>
|
||||
{{- if ne 0 .Limit}}
|
||||
<span class="meta-item">(of which only <b>{{.Limit}}</b> are displayed)</span>
|
||||
{{- end}}
|
||||
<span class="meta-item"><input type="text" placeholder="filter" id="filter" onkeyup='filter()'></span>
|
||||
</div>
|
||||
@@ -296,37 +296,37 @@ footer {
|
||||
<th></th>
|
||||
<th>
|
||||
{{- if and (eq .Sort "namedirfirst") (ne .Order "desc")}}
|
||||
<a href="?sort=namedirfirst&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}" class="icon"><svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
<a href="?sort=namedirfirst&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}" class="icon"><svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
{{- else if and (eq .Sort "namedirfirst") (ne .Order "asc")}}
|
||||
<a href="?sort=namedirfirst&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}" class="icon"><svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
<a href="?sort=namedirfirst&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}" class="icon"><svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
{{- else}}
|
||||
<a href="?sort=namedirfirst&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}" class="icon sort"><svg class="top" width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg><svg class="bottom" width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
<a href="?sort=namedirfirst&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}" class="icon sort"><svg class="top" width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg><svg class="bottom" width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
{{- end}}
|
||||
|
||||
{{- if and (eq .Sort "name") (ne .Order "desc")}}
|
||||
<a href="?sort=name&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
<a href="?sort=name&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Name <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
{{- else if and (eq .Sort "name") (ne .Order "asc")}}
|
||||
<a href="?sort=name&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
<a href="?sort=name&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Name <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
{{- else}}
|
||||
<a href="?sort=name&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name</a>
|
||||
<a href="?sort=name&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Name</a>
|
||||
{{- end}}
|
||||
</th>
|
||||
<th>
|
||||
{{- if and (eq .Sort "size") (ne .Order "desc")}}
|
||||
<a href="?sort=size&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
<a href="?sort=size&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Size <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
{{- else if and (eq .Sort "size") (ne .Order "asc")}}
|
||||
<a href="?sort=size&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
<a href="?sort=size&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Size <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
{{- else}}
|
||||
<a href="?sort=size&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size</a>
|
||||
<a href="?sort=size&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Size</a>
|
||||
{{- end}}
|
||||
</th>
|
||||
<th class="hideable">
|
||||
{{- if and (eq .Sort "time") (ne .Order "desc")}}
|
||||
<a href="?sort=time&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
<a href="?sort=time&order=desc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Modified <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
{{- else if and (eq .Sort "time") (ne .Order "asc")}}
|
||||
<a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
<a href="?sort=time&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Modified <svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
{{- else}}
|
||||
<a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified</a>
|
||||
<a href="?sort=time&order=asc{{if ne 0 .Limit}}&limit={{.Limit}}{{end}}{{if ne 0 .Offset}}&offset={{.Offset}}{{end}}">Modified</a>
|
||||
{{- end}}
|
||||
</th>
|
||||
<th class="hideable"></th>
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -34,7 +35,7 @@ func init() {
|
||||
// MatchFile is an HTTP request matcher that can match
|
||||
// requests based upon file existence.
|
||||
//
|
||||
// Upon matching, two new placeholders will be made
|
||||
// Upon matching, three new placeholders will be made
|
||||
// available:
|
||||
//
|
||||
// - `{http.matchers.file.relative}` The root-relative
|
||||
@@ -42,6 +43,8 @@ func init() {
|
||||
// requests.
|
||||
// - `{http.matchers.file.absolute}` The absolute path
|
||||
// of the matched file.
|
||||
// - `{http.matchers.file.type}` Set to "directory" if
|
||||
// the matched file is a directory, "file" otherwise.
|
||||
type MatchFile struct {
|
||||
// The root directory, used for creating absolute
|
||||
// file paths, and required when working with
|
||||
@@ -117,11 +120,13 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return d.ArgErr()
|
||||
}
|
||||
m.TryPolicy = d.Val()
|
||||
case "split":
|
||||
case "split_path":
|
||||
m.SplitPath = d.RemainingArgs()
|
||||
if len(m.SplitPath) == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
default:
|
||||
return d.Errf("unrecognized subdirective: %s", d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,25 +156,18 @@ func (m MatchFile) Validate() error {
|
||||
}
|
||||
|
||||
// Match returns true if r matches m. Returns true
|
||||
// if a file was matched. If so, two placeholders
|
||||
// if a file was matched. If so, three placeholders
|
||||
// will be available:
|
||||
// - http.matchers.file.relative
|
||||
// - http.matchers.file.absolute
|
||||
// - http.matchers.file.type
|
||||
func (m MatchFile) Match(r *http.Request) bool {
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
rel, abs, matched := m.selectFile(r)
|
||||
if matched {
|
||||
repl.Set("http.matchers.file.relative", rel)
|
||||
repl.Set("http.matchers.file.absolute", abs)
|
||||
}
|
||||
return matched
|
||||
return m.selectFile(r)
|
||||
}
|
||||
|
||||
// selectFile chooses a file according to m.TryPolicy by appending
|
||||
// the paths in m.TryFiles to m.Root, with placeholder replacements.
|
||||
// It returns the root-relative path to the matched file, the full
|
||||
// or absolute path, and whether a match was made.
|
||||
func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
||||
func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
root := repl.ReplaceAll(m.Root, ".")
|
||||
@@ -181,13 +179,35 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
||||
m.TryFiles = []string{r.URL.Path}
|
||||
}
|
||||
|
||||
// common preparation of the file into parts
|
||||
prepareFilePath := func(file string) (string, string) {
|
||||
suffix := m.firstSplit(path.Clean(repl.ReplaceAll(file, "")))
|
||||
if strings.HasSuffix(file, "/") {
|
||||
suffix += "/"
|
||||
}
|
||||
fullpath := sanitizedPathJoin(root, suffix)
|
||||
return suffix, fullpath
|
||||
}
|
||||
|
||||
// sets up the placeholders for the matched file
|
||||
setPlaceholders := func(info os.FileInfo, rel string, abs string) {
|
||||
repl.Set("http.matchers.file.relative", rel)
|
||||
repl.Set("http.matchers.file.absolute", abs)
|
||||
|
||||
fileType := "file"
|
||||
if info.IsDir() {
|
||||
fileType = "directory"
|
||||
}
|
||||
repl.Set("http.matchers.file.type", fileType)
|
||||
}
|
||||
|
||||
switch m.TryPolicy {
|
||||
case "", tryPolicyFirstExist:
|
||||
for _, f := range m.TryFiles {
|
||||
suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, "")))
|
||||
fullpath := sanitizedPathJoin(root, suffix)
|
||||
if strictFileExists(fullpath) {
|
||||
return suffix, fullpath, true
|
||||
suffix, fullpath := prepareFilePath(f)
|
||||
if info, exists := strictFileExists(fullpath); exists {
|
||||
setPlaceholders(info, suffix, fullpath)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,9 +215,9 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
||||
var largestSize int64
|
||||
var largestFilename string
|
||||
var largestSuffix string
|
||||
var info os.FileInfo
|
||||
for _, f := range m.TryFiles {
|
||||
suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, "")))
|
||||
fullpath := sanitizedPathJoin(root, suffix)
|
||||
suffix, fullpath := prepareFilePath(f)
|
||||
info, err := os.Stat(fullpath)
|
||||
if err == nil && info.Size() > largestSize {
|
||||
largestSize = info.Size()
|
||||
@@ -205,15 +225,16 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
||||
largestSuffix = suffix
|
||||
}
|
||||
}
|
||||
return largestSuffix, largestFilename, true
|
||||
setPlaceholders(info, largestSuffix, largestFilename)
|
||||
return true
|
||||
|
||||
case tryPolicySmallestSize:
|
||||
var smallestSize int64
|
||||
var smallestFilename string
|
||||
var smallestSuffix string
|
||||
var info os.FileInfo
|
||||
for _, f := range m.TryFiles {
|
||||
suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, "")))
|
||||
fullpath := sanitizedPathJoin(root, suffix)
|
||||
suffix, fullpath := prepareFilePath(f)
|
||||
info, err := os.Stat(fullpath)
|
||||
if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
|
||||
smallestSize = info.Size()
|
||||
@@ -221,15 +242,16 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
||||
smallestSuffix = suffix
|
||||
}
|
||||
}
|
||||
return smallestSuffix, smallestFilename, true
|
||||
setPlaceholders(info, smallestSuffix, smallestFilename)
|
||||
return true
|
||||
|
||||
case tryPolicyMostRecentlyMod:
|
||||
var recentDate time.Time
|
||||
var recentFilename string
|
||||
var recentSuffix string
|
||||
var info os.FileInfo
|
||||
for _, f := range m.TryFiles {
|
||||
suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, "")))
|
||||
fullpath := sanitizedPathJoin(root, suffix)
|
||||
suffix, fullpath := prepareFilePath(f)
|
||||
info, err := os.Stat(fullpath)
|
||||
if err == nil &&
|
||||
(recentDate.IsZero() || info.ModTime().After(recentDate)) {
|
||||
@@ -238,7 +260,8 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
||||
recentSuffix = suffix
|
||||
}
|
||||
}
|
||||
return recentSuffix, recentFilename, true
|
||||
setPlaceholders(info, recentSuffix, recentFilename)
|
||||
return true
|
||||
}
|
||||
|
||||
return
|
||||
@@ -250,7 +273,7 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
|
||||
// the file must also be a directory; if it does
|
||||
// NOT end in a forward slash, the file must NOT
|
||||
// be a directory.
|
||||
func strictFileExists(file string) bool {
|
||||
func strictFileExists(file string) (os.FileInfo, bool) {
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
// in reality, this can be any error
|
||||
@@ -261,16 +284,16 @@ func strictFileExists(file string) bool {
|
||||
// the file exists, so we just treat any
|
||||
// error as if it does not exist; see
|
||||
// https://stackoverflow.com/a/12518877/1048862
|
||||
return false
|
||||
return nil, false
|
||||
}
|
||||
if strings.HasSuffix(file, "/") {
|
||||
if strings.HasSuffix(file, string(filepath.Separator)) {
|
||||
// by convention, file paths ending
|
||||
// in a slash must be a directory
|
||||
return stat.IsDir()
|
||||
// in a path separator must be a directory
|
||||
return stat, stat.IsDir()
|
||||
}
|
||||
// by convention, file paths NOT ending
|
||||
// in a slash must NOT be a directory
|
||||
return !stat.IsDir()
|
||||
// in a path separator must NOT be a directory
|
||||
return stat, !stat.IsDir()
|
||||
}
|
||||
|
||||
// firstSplit returns the first result where the path
|
||||
@@ -279,9 +302,8 @@ func strictFileExists(file string) bool {
|
||||
// in the split value. Returns the path as-is if the
|
||||
// path cannot be split.
|
||||
func (m MatchFile) firstSplit(path string) string {
|
||||
lowerPath := strings.ToLower(path)
|
||||
for _, split := range m.SplitPath {
|
||||
if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
|
||||
if idx := indexFold(path, split); idx > -1 {
|
||||
pos := idx + len(split)
|
||||
// skip the split if it's not the final part of the filename
|
||||
if pos != len(path) && !strings.HasPrefix(path[pos:], "/") {
|
||||
@@ -293,6 +315,19 @@ func (m MatchFile) firstSplit(path string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
// There is no strings.IndexFold() function like there is strings.EqualFold(),
|
||||
// but we can use strings.EqualFold() to build our own case-insensitive
|
||||
// substring search (as of Go 1.14).
|
||||
func indexFold(haystack, needle string) int {
|
||||
nlen := len(needle)
|
||||
for i := 0; i+nlen < len(haystack); i++ {
|
||||
if strings.EqualFold(haystack[i:i+nlen], needle) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
const (
|
||||
tryPolicyFirstExist = "first_exist"
|
||||
tryPolicyLargestSize = "largest_size"
|
||||
|
||||
@@ -22,69 +22,64 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
func TestPhpFileMatcher(t *testing.T) {
|
||||
|
||||
func TestFileMatcher(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
path string
|
||||
path string
|
||||
expectedPath string
|
||||
matched bool
|
||||
expectedType string
|
||||
matched bool
|
||||
}{
|
||||
{
|
||||
path: "/index.php",
|
||||
expectedPath: "/index.php",
|
||||
matched: true,
|
||||
path: "/foo.txt",
|
||||
expectedPath: "/foo.txt",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/index.php/somewhere",
|
||||
expectedPath: "/index.php",
|
||||
matched: true,
|
||||
path: "/foo.txt/",
|
||||
expectedPath: "/foo.txt",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/remote.php",
|
||||
expectedPath: "/remote.php",
|
||||
matched: true,
|
||||
path: "/foodir",
|
||||
expectedPath: "/foodir/",
|
||||
expectedType: "directory",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/remote.php/somewhere",
|
||||
expectedPath: "/remote.php",
|
||||
matched: true,
|
||||
path: "/foodir/",
|
||||
expectedPath: "/foodir/",
|
||||
expectedType: "directory",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/missingfile.php",
|
||||
path: "/foodir/foo.txt",
|
||||
expectedPath: "/foodir/foo.txt",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/missingfile.php",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
path: "/notphp.php.txt",
|
||||
expectedPath: "/notphp.php.txt",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/notphp.php.txt/",
|
||||
expectedPath: "/notphp.php.txt",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/notphp.php.txt.suffixed",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
path: "/foo.php.php/index.php",
|
||||
expectedPath: "/foo.php.php/index.php",
|
||||
matched: true,
|
||||
},
|
||||
} {
|
||||
m := &MatchFile{
|
||||
Root: "./testdata",
|
||||
TryFiles: []string{"{http.request.uri.path}"},
|
||||
SplitPath: []string{".php"},
|
||||
Root: "./testdata",
|
||||
TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/"},
|
||||
}
|
||||
|
||||
req := &http.Request{URL: &url.URL{Path: tc.path}}
|
||||
u, err := url.Parse(tc.path)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: parsing path: %v", i, err)
|
||||
}
|
||||
|
||||
req := &http.Request{URL: u}
|
||||
repl := caddyhttp.NewTestReplacer(req)
|
||||
|
||||
result := m.Match(req)
|
||||
if result != tc.matched {
|
||||
t.Fatalf("Test %d: match bool result: %v, expected: %v", i, result, tc.matched)
|
||||
t.Fatalf("Test %d: expected match=%t, got %t", i, tc.matched, result)
|
||||
}
|
||||
|
||||
rel, ok := repl.Get("http.matchers.file.relative")
|
||||
@@ -98,5 +93,122 @@ func TestPhpFileMatcher(t *testing.T) {
|
||||
if rel != tc.expectedPath {
|
||||
t.Fatalf("Test %d: actual path: %v, expected: %v", i, rel, tc.expectedPath)
|
||||
}
|
||||
|
||||
fileType, ok := repl.Get("http.matchers.file.type")
|
||||
if fileType != tc.expectedType {
|
||||
t.Fatalf("Test %d: actual file type: %v, expected: %v", i, fileType, tc.expectedType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPHPFileMatcher(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
path string
|
||||
expectedPath string
|
||||
expectedType string
|
||||
matched bool
|
||||
}{
|
||||
{
|
||||
path: "/index.php",
|
||||
expectedPath: "/index.php",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/index.php/somewhere",
|
||||
expectedPath: "/index.php",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/remote.php",
|
||||
expectedPath: "/remote.php",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/remote.php/somewhere",
|
||||
expectedPath: "/remote.php",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/missingfile.php",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
path: "/notphp.php.txt",
|
||||
expectedPath: "/notphp.php.txt",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/notphp.php.txt/",
|
||||
expectedPath: "/notphp.php.txt",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
path: "/notphp.php.txt.suffixed",
|
||||
matched: false,
|
||||
},
|
||||
{
|
||||
path: "/foo.php.php/index.php",
|
||||
expectedPath: "/foo.php.php/index.php",
|
||||
expectedType: "file",
|
||||
matched: true,
|
||||
},
|
||||
{
|
||||
// See https://github.com/caddyserver/caddy/issues/3623
|
||||
path: "/%E2%C3",
|
||||
expectedPath: "/%E2%C3",
|
||||
expectedType: "file",
|
||||
matched: false,
|
||||
},
|
||||
} {
|
||||
m := &MatchFile{
|
||||
Root: "./testdata",
|
||||
TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php"},
|
||||
SplitPath: []string{".php"},
|
||||
}
|
||||
|
||||
u, err := url.Parse(tc.path)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: parsing path: %v", i, err)
|
||||
}
|
||||
|
||||
req := &http.Request{URL: u}
|
||||
repl := caddyhttp.NewTestReplacer(req)
|
||||
|
||||
result := m.Match(req)
|
||||
if result != tc.matched {
|
||||
t.Fatalf("Test %d: expected match=%t, got %t", i, tc.matched, result)
|
||||
}
|
||||
|
||||
rel, ok := repl.Get("http.matchers.file.relative")
|
||||
if !ok && result {
|
||||
t.Fatalf("Test %d: expected replacer value", i)
|
||||
}
|
||||
if !result {
|
||||
continue
|
||||
}
|
||||
|
||||
if rel != tc.expectedPath {
|
||||
t.Fatalf("Test %d: actual path: %v, expected: %v", i, rel, tc.expectedPath)
|
||||
}
|
||||
|
||||
fileType, ok := repl.Get("http.matchers.file.type")
|
||||
if fileType != tc.expectedType {
|
||||
t.Fatalf("Test %d: actual file type: %v, expected: %v", i, fileType, tc.expectedType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstSplit(t *testing.T) {
|
||||
m := MatchFile{SplitPath: []string{".php"}}
|
||||
actual := m.firstSplit("index.PHP/somewhere")
|
||||
expected := "index.PHP"
|
||||
if actual != expected {
|
||||
t.Errorf("Expected %s but got %s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -323,34 +322,54 @@ func sanitizedPathJoin(root, reqPath string) string {
|
||||
if root == "" {
|
||||
root = "."
|
||||
}
|
||||
return filepath.Join(root, filepath.FromSlash(path.Clean("/"+reqPath)))
|
||||
|
||||
path := filepath.Join(root, filepath.Clean("/"+reqPath))
|
||||
|
||||
// filepath.Join also cleans the path, and cleaning strips
|
||||
// the trailing slash, so we need to re-add it afterwards.
|
||||
// if the length is 1, then it's a path to the root,
|
||||
// and that should return ".", so we don't append the separator.
|
||||
if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
|
||||
path += string(filepath.Separator)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// fileHidden returns true if filename is hidden
|
||||
// according to the hide list.
|
||||
func fileHidden(filename string, hide []string) bool {
|
||||
nameOnly := filepath.Base(filename)
|
||||
sep := string(filepath.Separator)
|
||||
var components []string
|
||||
|
||||
for _, h := range hide {
|
||||
// assuming h is a glob/shell-like pattern,
|
||||
// use it to compare the whole file path;
|
||||
// but if there is no separator in h, then
|
||||
// just compare against the file's name
|
||||
compare := filename
|
||||
if !strings.Contains(h, sep) {
|
||||
compare = nameOnly
|
||||
}
|
||||
|
||||
hidden, err := filepath.Match(h, compare)
|
||||
if err != nil {
|
||||
// malformed pattern; fallback by checking prefix
|
||||
if strings.HasPrefix(filename, h) {
|
||||
// if there is no separator in h, then we assume the user
|
||||
// wants to hide any files or folders that match that
|
||||
// name; thus we have to compare against each component
|
||||
// of the filename, e.g. hiding "bar" would hide "/bar"
|
||||
// as well as "/foo/bar/baz" but not "/barstool".
|
||||
if len(components) == 0 {
|
||||
components = strings.Split(filename, sep)
|
||||
}
|
||||
for _, c := range components {
|
||||
if c == h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if strings.HasPrefix(filename, h) {
|
||||
// otherwise, if there is a separator in h, and
|
||||
// filename is exactly prefixed with h, then we
|
||||
// can do a prefix match so that "/foo" matches
|
||||
// "/foo/bar" but not "/foobar".
|
||||
withoutPrefix := strings.TrimPrefix(filename, h)
|
||||
if strings.HasPrefix(withoutPrefix, sep) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if hidden {
|
||||
// file name or path matches hide pattern
|
||||
|
||||
// in the general case, a glob match will suffice
|
||||
if hidden, _ := filepath.Match(h, filename); hidden {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ func TestSanitizedPathJoin(t *testing.T) {
|
||||
inputPath: "/foo",
|
||||
expect: "foo",
|
||||
},
|
||||
{
|
||||
inputPath: "/foo/",
|
||||
expect: "foo" + string(filepath.Separator),
|
||||
},
|
||||
{
|
||||
inputPath: "/foo/bar",
|
||||
expect: filepath.Join("foo", "bar"),
|
||||
@@ -73,7 +77,7 @@ func TestSanitizedPathJoin(t *testing.T) {
|
||||
{
|
||||
inputRoot: "/a/b",
|
||||
inputPath: "/%2e%2e%2f%2e%2e%2f",
|
||||
expect: filepath.Join("/", "a", "b"),
|
||||
expect: filepath.Join("/", "a", "b") + string(filepath.Separator),
|
||||
},
|
||||
{
|
||||
inputRoot: "C:\\www",
|
||||
@@ -93,9 +97,84 @@ func TestSanitizedPathJoin(t *testing.T) {
|
||||
}
|
||||
actual := sanitizedPathJoin(tc.inputRoot, u.Path)
|
||||
if actual != tc.expect {
|
||||
t.Errorf("Test %d: [%s %s] => %s (expected %s)", i, tc.inputRoot, tc.inputPath, actual, tc.expect)
|
||||
t.Errorf("Test %d: [%s %s] => %s (expected %s)",
|
||||
i, tc.inputRoot, tc.inputPath, actual, tc.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test fileHidden
|
||||
func TestFileHidden(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
inputHide []string
|
||||
inputPath string
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
inputHide: nil,
|
||||
inputPath: "",
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
inputHide: []string{".gitignore"},
|
||||
inputPath: "/.gitignore",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
inputHide: []string{".git"},
|
||||
inputPath: "/.gitignore",
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
inputHide: []string{"/.git"},
|
||||
inputPath: "/.gitignore",
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
inputHide: []string{".git"},
|
||||
inputPath: "/.git",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
inputHide: []string{".git"},
|
||||
inputPath: "/.git/foo",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
inputHide: []string{".git"},
|
||||
inputPath: "/foo/.git/bar",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
inputHide: []string{"/prefix"},
|
||||
inputPath: "/prefix/foo",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
inputHide: []string{"/foo/*/bar"},
|
||||
inputPath: "/foo/asdf/bar",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
inputHide: []string{"/foo"},
|
||||
inputPath: "/foo",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
inputHide: []string{"/foo"},
|
||||
inputPath: "/foobar",
|
||||
expect: false,
|
||||
},
|
||||
} {
|
||||
// for Windows' sake
|
||||
tc.inputPath = filepath.FromSlash(tc.inputPath)
|
||||
for i := range tc.inputHide {
|
||||
tc.inputHide[i] = filepath.FromSlash(tc.inputHide[i])
|
||||
}
|
||||
|
||||
actual := fileHidden(tc.inputPath, tc.inputHide)
|
||||
if actual != tc.expect {
|
||||
t.Errorf("Test %d: Is %s hidden in %v? Got %t but expected %t",
|
||||
i, tc.inputPath, tc.inputHide, actual, tc.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
foo.txt
|
||||
@@ -0,0 +1 @@
|
||||
foodir/foo.txt
|
||||
@@ -54,15 +54,15 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
|
||||
// Provision sets up h's configuration.
|
||||
func (h *Handler) Provision(_ caddy.Context) error {
|
||||
func (h *Handler) Provision(ctx caddy.Context) error {
|
||||
if h.Request != nil {
|
||||
err := h.Request.provision()
|
||||
err := h.Request.Provision(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if h.Response != nil {
|
||||
err := h.Response.provision()
|
||||
err := h.Response.Provision(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -125,7 +125,8 @@ type HeaderOps struct {
|
||||
Replace map[string][]Replacement `json:"replace,omitempty"`
|
||||
}
|
||||
|
||||
func (ops *HeaderOps) provision() error {
|
||||
// Provision sets up the header operations.
|
||||
func (ops *HeaderOps) Provision(_ caddy.Context) error {
|
||||
for fieldName, replacements := range ops.Replace {
|
||||
for i, r := range replacements {
|
||||
if r.SearchRegexp != "" {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package maphandler
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
@@ -23,49 +25,85 @@ func init() {
|
||||
httpcaddyfile.RegisterHandlerDirective("map", parseCaddyfile)
|
||||
}
|
||||
|
||||
// parseCaddyfile sets up the handler for a map from Caddyfile tokens. Syntax:
|
||||
// parseCaddyfile sets up the map handler from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// map <source> <dest> {
|
||||
// [default <default>] - used if not match is found
|
||||
// [<regexp> <replacement>] - regular expression to match against the source find and the matching replacement value
|
||||
// ...
|
||||
// map [<matcher>] <source> <destinations...> {
|
||||
// [~]<input> <outputs...>
|
||||
// default <defaults...>
|
||||
// }
|
||||
//
|
||||
// The map takes a source variable and maps it into the dest variable. The mapping process
|
||||
// will check the source variable for the first successful match against a list of regular expressions.
|
||||
// If a successful match is found the dest variable will contain the replacement value.
|
||||
// If no successful match is found and the default is specified then the dest will contain the default value.
|
||||
// If the input value is prefixed with a tilde (~), then the input will be parsed as a
|
||||
// regular expression.
|
||||
//
|
||||
// The Caddyfile adapter treats outputs that are a literal hyphen (-) as a null/nil
|
||||
// value. This is useful if you want to fall back to default for that particular output.
|
||||
//
|
||||
// The number of outputs for each mapping must not be more than the number of destinations.
|
||||
// However, for convenience, there may be fewer outputs than destinations and any missing
|
||||
// outputs will be filled in implicitly.
|
||||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
m := new(Handler)
|
||||
var handler Handler
|
||||
|
||||
for h.Next() {
|
||||
// first see if source and dest are configured
|
||||
if h.NextArg() {
|
||||
m.Source = h.Val()
|
||||
if h.NextArg() {
|
||||
m.Destination = h.Val()
|
||||
}
|
||||
// source
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
handler.Source = h.Val()
|
||||
|
||||
// destinations
|
||||
handler.Destinations = h.RemainingArgs()
|
||||
if len(handler.Destinations) == 0 {
|
||||
return nil, h.Err("missing destination argument(s)")
|
||||
}
|
||||
|
||||
// load the rules
|
||||
// mappings
|
||||
for h.NextBlock(0) {
|
||||
expression := h.Val()
|
||||
if expression == "default" {
|
||||
args := h.RemainingArgs()
|
||||
if len(args) != 1 {
|
||||
return m, h.ArgErr()
|
||||
// defaults are a special case
|
||||
if h.Val() == "default" {
|
||||
if len(handler.Defaults) > 0 {
|
||||
return nil, h.Err("defaults already defined")
|
||||
}
|
||||
m.Default = args[0]
|
||||
} else {
|
||||
args := h.RemainingArgs()
|
||||
if len(args) != 1 {
|
||||
return m, h.ArgErr()
|
||||
handler.Defaults = h.RemainingArgs()
|
||||
for len(handler.Defaults) < len(handler.Destinations) {
|
||||
handler.Defaults = append(handler.Defaults, "")
|
||||
}
|
||||
m.Items = append(m.Items, Item{Expression: expression, Value: args[0]})
|
||||
continue
|
||||
}
|
||||
|
||||
// every other line maps one input to one or more outputs
|
||||
in := h.Val()
|
||||
var outs []interface{}
|
||||
for _, out := range h.RemainingArgs() {
|
||||
if out == "-" {
|
||||
outs = append(outs, nil)
|
||||
} else {
|
||||
outs = append(outs, out)
|
||||
}
|
||||
}
|
||||
|
||||
// cannot have more outputs than destinations
|
||||
if len(outs) > len(handler.Destinations) {
|
||||
return nil, h.Err("too many outputs")
|
||||
}
|
||||
|
||||
// for convenience, can have fewer outputs than destinations, but the
|
||||
// underlying handler won't accept that, so we fill in nil values
|
||||
for len(outs) < len(handler.Destinations) {
|
||||
outs = append(outs, nil)
|
||||
}
|
||||
|
||||
// create the mapping
|
||||
mapping := Mapping{Outputs: outs}
|
||||
if strings.HasPrefix(in, "~") {
|
||||
mapping.InputRegexp = in[1:]
|
||||
} else {
|
||||
mapping.Input = in
|
||||
}
|
||||
|
||||
handler.Mappings = append(handler.Mappings, mapping)
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
+134
-42
@@ -15,8 +15,10 @@
|
||||
package maphandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
@@ -26,27 +28,27 @@ func init() {
|
||||
caddy.RegisterModule(Handler{})
|
||||
}
|
||||
|
||||
// Handler is a middleware that maps a source placeholder to a destination
|
||||
// placeholder.
|
||||
//
|
||||
// The mapping process happens early in the request handling lifecycle so that
|
||||
// the Destination placeholder is calculated and available for substitution.
|
||||
// The Items array contains pairs of regex expressions and values, the
|
||||
// Source is matched against the expression, if they match then the destination
|
||||
// placeholder is set to the value.
|
||||
//
|
||||
// The Default is optional, if no Item expression is matched then the value of
|
||||
// the Default will be used.
|
||||
// Handler implements a middleware that maps inputs to outputs. Specifically, it
|
||||
// compares a source value against the map inputs, and for one that matches, it
|
||||
// applies the output values to each destination. Destinations become placeholder
|
||||
// names.
|
||||
//
|
||||
// Mapped placeholders are not evaluated until they are used, so even for very
|
||||
// large mappings, this handler is quite efficient.
|
||||
type Handler struct {
|
||||
// Source is a placeholder
|
||||
// Source is the placeholder from which to get the input value.
|
||||
Source string `json:"source,omitempty"`
|
||||
// Destination is a new placeholder
|
||||
Destination string `json:"destination,omitempty"`
|
||||
// Default is an optional value to use if no other was found
|
||||
Default string `json:"default,omitempty"`
|
||||
// Items is an array of regex expressions and values
|
||||
Items []Item `json:"items,omitempty"`
|
||||
|
||||
// Destinations are the names of placeholders in which to store the outputs.
|
||||
Destinations []string `json:"destinations,omitempty"`
|
||||
|
||||
// Mappings from source values (inputs) to destination values (outputs).
|
||||
// The first matching, non-nil mapping will be applied.
|
||||
Mappings []Mapping `json:"mappings,omitempty"`
|
||||
|
||||
// If no mappings match or if the mapped output is null/nil, the associated
|
||||
// default output will be applied (optional).
|
||||
Defaults []string
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -57,10 +59,58 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// Provision will compile all regular expressions
|
||||
// Provision sets up h.
|
||||
func (h *Handler) Provision(_ caddy.Context) error {
|
||||
for i := 0; i < len(h.Items); i++ {
|
||||
h.Items[i].compiled = regexp.MustCompile(h.Items[i].Expression)
|
||||
for j, dest := range h.Destinations {
|
||||
h.Destinations[j] = strings.Trim(dest, "{}")
|
||||
}
|
||||
|
||||
for i, m := range h.Mappings {
|
||||
if m.InputRegexp == "" {
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
h.Mappings[i].re, err = regexp.Compile(m.InputRegexp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compiling regexp for mapping %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: improve efficiency even further by using an actual map type
|
||||
// for the non-regexp mappings, OR sort them and do a binary search
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate ensures that h is configured properly.
|
||||
func (h *Handler) Validate() error {
|
||||
nDest, nDef := len(h.Destinations), len(h.Defaults)
|
||||
if nDef > 0 && nDef != nDest {
|
||||
return fmt.Errorf("%d destinations != %d defaults", nDest, nDef)
|
||||
}
|
||||
|
||||
seen := make(map[string]int)
|
||||
for i, m := range h.Mappings {
|
||||
// prevent confusing/ambiguous mappings
|
||||
if m.Input != "" && m.InputRegexp != "" {
|
||||
return fmt.Errorf("mapping %d has both input and input_regexp fields specified, which is confusing", i)
|
||||
}
|
||||
|
||||
// prevent duplicate mappings
|
||||
input := m.Input
|
||||
if m.InputRegexp != "" {
|
||||
input = m.InputRegexp
|
||||
}
|
||||
if prev, ok := seen[input]; ok {
|
||||
return fmt.Errorf("mapping %d has a duplicate input '%s' previously used with mapping %d", i, input, prev)
|
||||
}
|
||||
seen[input] = i
|
||||
|
||||
// ensure mappings have 1:1 output-to-destination correspondence
|
||||
nOut := len(m.Outputs)
|
||||
if nOut != nDest {
|
||||
return fmt.Errorf("mapping %d has %d outputs but there are %d destinations defined", i, nOut, nDest)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -68,38 +118,80 @@ func (h *Handler) Provision(_ caddy.Context) error {
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
// get the source value, if the source value was not found do no
|
||||
// replacement.
|
||||
val, ok := repl.GetString(h.Source)
|
||||
if ok {
|
||||
found := false
|
||||
for i := 0; i < len(h.Items); i++ {
|
||||
if h.Items[i].compiled.MatchString(val) {
|
||||
found = true
|
||||
repl.Set(h.Destination, h.Items[i].Value)
|
||||
break
|
||||
// defer work until a variable is actually evaluated by using replacer's Map callback
|
||||
repl.Map(func(key string) (interface{}, bool) {
|
||||
// return early if the variable is not even a configured destination
|
||||
destIdx := h.destinationIndex(key)
|
||||
if destIdx < 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
input := repl.ReplaceAll(h.Source, "")
|
||||
|
||||
// find the first mapping matching the input and return
|
||||
// the requested destination/output value
|
||||
for _, m := range h.Mappings {
|
||||
if m.re != nil {
|
||||
if m.re.MatchString(input) {
|
||||
if output := m.Outputs[destIdx]; output == nil {
|
||||
continue
|
||||
} else {
|
||||
return output, true
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if input == m.Input {
|
||||
if output := m.Outputs[destIdx]; output == nil {
|
||||
continue
|
||||
} else {
|
||||
return output, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found && h.Default != "" {
|
||||
repl.Set(h.Destination, h.Default)
|
||||
// fall back to default if no match or if matched nil value
|
||||
if len(h.Defaults) > destIdx {
|
||||
return h.Defaults[destIdx], true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, true
|
||||
})
|
||||
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Item defines each entry in the map
|
||||
type Item struct {
|
||||
// Expression is the regular expression searched for
|
||||
Expression string `json:"expression,omitempty"`
|
||||
// Value to use once the expression has been found
|
||||
Value string `json:"value,omitempty"`
|
||||
// compiled expression, internal use
|
||||
compiled *regexp.Regexp
|
||||
// destinationIndex returns the positional index of the destination
|
||||
// is name is a known destination; otherwise it returns -1.
|
||||
func (h Handler) destinationIndex(name string) int {
|
||||
for i, dest := range h.Destinations {
|
||||
if dest == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Mapping describes a mapping from input to outputs.
|
||||
type Mapping struct {
|
||||
// The input value to match. Must be distinct from other mappings.
|
||||
// Mutually exclusive to input_regexp.
|
||||
Input string `json:"input,omitempty"`
|
||||
|
||||
// The input regular expression to match. Mutually exclusive to input.
|
||||
InputRegexp string `json:"input_regexp,omitempty"`
|
||||
|
||||
// Upon a match with the input, each output is positionally correlated
|
||||
// with each destination of the parent handler. An output that is null
|
||||
// (nil) will be treated as if it was not mapped at all.
|
||||
Outputs []interface{} `json:"outputs,omitempty"`
|
||||
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*Handler)(nil)
|
||||
_ caddy.Validator = (*Handler)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*Handler)(nil)
|
||||
)
|
||||
|
||||
@@ -26,11 +26,11 @@ type LoggableHTTPRequest struct{ *http.Request }
|
||||
|
||||
// MarshalLogObject satisfies the zapcore.ObjectMarshaler interface.
|
||||
func (r LoggableHTTPRequest) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
||||
enc.AddString("method", r.Method)
|
||||
enc.AddString("uri", r.RequestURI)
|
||||
enc.AddString("proto", r.Proto)
|
||||
enc.AddString("remote_addr", r.RemoteAddr)
|
||||
enc.AddString("proto", r.Proto)
|
||||
enc.AddString("method", r.Method)
|
||||
enc.AddString("host", r.Host)
|
||||
enc.AddString("uri", r.RequestURI)
|
||||
enc.AddObject("headers", LoggableHTTPHeader(r.Header))
|
||||
if r.TLS != nil {
|
||||
enc.AddObject("tls", LoggableTLSConnState(*r.TLS))
|
||||
@@ -73,10 +73,14 @@ type LoggableTLSConnState tls.ConnectionState
|
||||
func (t LoggableTLSConnState) MarshalLogObject(enc zapcore.ObjectEncoder) error {
|
||||
enc.AddBool("resumed", t.DidResume)
|
||||
enc.AddUint16("version", t.Version)
|
||||
enc.AddUint16("ciphersuite", t.CipherSuite)
|
||||
enc.AddUint16("cipher_suite", t.CipherSuite)
|
||||
enc.AddString("proto", t.NegotiatedProtocol)
|
||||
enc.AddBool("proto_mutual", t.NegotiatedProtocolIsMutual)
|
||||
enc.AddString("server_name", t.ServerName)
|
||||
if len(t.PeerCertificates) > 0 {
|
||||
enc.AddString("client_common_name", t.PeerCertificates[0].Subject.CommonName)
|
||||
enc.AddString("client_serial", t.PeerCertificates[0].SerialNumber.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -927,6 +927,8 @@ var (
|
||||
_ caddyfile.Unmarshaler = (*MatchHeaderRE)(nil)
|
||||
_ caddyfile.Unmarshaler = (*MatchProtocol)(nil)
|
||||
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
|
||||
_ caddyfile.Unmarshaler = (*VarsMatcher)(nil)
|
||||
_ caddyfile.Unmarshaler = (*MatchVarsRE)(nil)
|
||||
|
||||
_ json.Marshaler = (*MatchNot)(nil)
|
||||
_ json.Unmarshaler = (*MatchNot)(nil)
|
||||
|
||||
@@ -122,6 +122,11 @@ func TestHostMatcher(t *testing.T) {
|
||||
input: "sub.foo.example.net",
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
match: MatchHost{"www.*.*"},
|
||||
input: "www.example.com",
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
match: MatchHost{"example.com"},
|
||||
input: "example.com:5555",
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var httpMetrics = struct {
|
||||
init sync.Once
|
||||
requestInFlight *prometheus.GaugeVec
|
||||
requestCount *prometheus.CounterVec
|
||||
requestErrors *prometheus.CounterVec
|
||||
requestDuration *prometheus.HistogramVec
|
||||
requestSize *prometheus.HistogramVec
|
||||
responseSize *prometheus.HistogramVec
|
||||
responseDuration *prometheus.HistogramVec
|
||||
}{
|
||||
init: sync.Once{},
|
||||
}
|
||||
|
||||
func initHTTPMetrics() {
|
||||
const ns, sub = "caddy", "http"
|
||||
|
||||
basicLabels := []string{"server", "handler"}
|
||||
httpMetrics.requestInFlight = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "requests_in_flight",
|
||||
Help: "Number of requests currently handled by this server.",
|
||||
}, basicLabels)
|
||||
httpMetrics.requestErrors = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "request_errors_total",
|
||||
Help: "Number of requests resulting in middleware errors.",
|
||||
}, basicLabels)
|
||||
httpMetrics.requestCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "requests_total",
|
||||
Help: "Counter of HTTP(S) requests made.",
|
||||
}, basicLabels)
|
||||
|
||||
// TODO: allow these to be customized in the config
|
||||
durationBuckets := prometheus.DefBuckets
|
||||
sizeBuckets := prometheus.ExponentialBuckets(256, 4, 8)
|
||||
|
||||
httpLabels := []string{"server", "handler", "code", "method"}
|
||||
httpMetrics.requestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "request_duration_seconds",
|
||||
Help: "Histogram of round-trip request durations.",
|
||||
Buckets: durationBuckets,
|
||||
}, httpLabels)
|
||||
httpMetrics.requestSize = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "request_size_bytes",
|
||||
Help: "Total size of the request. Includes body",
|
||||
Buckets: sizeBuckets,
|
||||
}, httpLabels)
|
||||
httpMetrics.responseSize = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "response_size_bytes",
|
||||
Help: "Size of the returned response.",
|
||||
Buckets: sizeBuckets,
|
||||
}, httpLabels)
|
||||
httpMetrics.responseDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: ns,
|
||||
Subsystem: sub,
|
||||
Name: "response_duration_seconds",
|
||||
Help: "Histogram of times to first byte in response bodies.",
|
||||
Buckets: durationBuckets,
|
||||
}, httpLabels)
|
||||
}
|
||||
|
||||
// serverNameFromContext extracts the current server name from the context.
|
||||
// Returns "UNKNOWN" if none is available (should probably never happen).
|
||||
func serverNameFromContext(ctx context.Context) string {
|
||||
srv, ok := ctx.Value(ServerCtxKey).(*Server)
|
||||
if !ok || srv == nil || srv.name == "" {
|
||||
return "UNKNOWN"
|
||||
}
|
||||
return srv.name
|
||||
}
|
||||
|
||||
type metricsInstrumentedHandler struct {
|
||||
handler string
|
||||
mh MiddlewareHandler
|
||||
}
|
||||
|
||||
func newMetricsInstrumentedHandler(handler string, mh MiddlewareHandler) *metricsInstrumentedHandler {
|
||||
httpMetrics.init.Do(func() {
|
||||
initHTTPMetrics()
|
||||
})
|
||||
|
||||
return &metricsInstrumentedHandler{handler, mh}
|
||||
}
|
||||
|
||||
func (h *metricsInstrumentedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error {
|
||||
server := serverNameFromContext(r.Context())
|
||||
labels := prometheus.Labels{"server": server, "handler": h.handler}
|
||||
method := strings.ToUpper(r.Method)
|
||||
// the "code" value is set later, but initialized here to eliminate the possibility
|
||||
// of a panic
|
||||
statusLabels := prometheus.Labels{"server": server, "handler": h.handler, "method": method, "code": ""}
|
||||
|
||||
inFlight := httpMetrics.requestInFlight.With(labels)
|
||||
inFlight.Inc()
|
||||
defer inFlight.Dec()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
// This is a _bit_ of a hack - it depends on the ShouldBufferFunc always
|
||||
// being called when the headers are written.
|
||||
// Effectively the same behaviour as promhttp.InstrumentHandlerTimeToWriteHeader.
|
||||
writeHeaderRecorder := ShouldBufferFunc(func(status int, header http.Header) bool {
|
||||
statusLabels["code"] = sanitizeCode(status)
|
||||
ttfb := time.Since(start).Seconds()
|
||||
httpMetrics.responseDuration.With(statusLabels).Observe(ttfb)
|
||||
return false
|
||||
})
|
||||
wrec := NewResponseRecorder(w, nil, writeHeaderRecorder)
|
||||
err := h.mh.ServeHTTP(wrec, r, next)
|
||||
dur := time.Since(start).Seconds()
|
||||
httpMetrics.requestCount.With(labels).Inc()
|
||||
if err != nil {
|
||||
httpMetrics.requestErrors.With(labels).Inc()
|
||||
return err
|
||||
}
|
||||
|
||||
// If the code hasn't been set yet, and we didn't encounter an error, we're
|
||||
// probably falling through with an empty handler.
|
||||
if statusLabels["code"] == "" {
|
||||
// we still sanitize it, even though it's likely to be 0. A 200 is
|
||||
// returned on fallthrough so we want to reflect that.
|
||||
statusLabels["code"] = sanitizeCode(wrec.Status())
|
||||
}
|
||||
|
||||
httpMetrics.requestDuration.With(statusLabels).Observe(dur)
|
||||
httpMetrics.requestSize.With(statusLabels).Observe(float64(computeApproximateRequestSize(r)))
|
||||
httpMetrics.responseSize.With(statusLabels).Observe(float64(wrec.Size()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sanitizeCode(code int) string {
|
||||
if code == 0 {
|
||||
return "200"
|
||||
}
|
||||
return strconv.Itoa(code)
|
||||
}
|
||||
|
||||
// taken from https://github.com/prometheus/client_golang/blob/6007b2b5cae01203111de55f753e76d8dac1f529/prometheus/promhttp/instrument_server.go#L298
|
||||
func computeApproximateRequestSize(r *http.Request) int {
|
||||
s := 0
|
||||
if r.URL != nil {
|
||||
s += len(r.URL.String())
|
||||
}
|
||||
|
||||
s += len(r.Method)
|
||||
s += len(r.Proto)
|
||||
for name, values := range r.Header {
|
||||
s += len(name)
|
||||
for _, value := range values {
|
||||
s += len(value)
|
||||
}
|
||||
}
|
||||
s += len(r.Host)
|
||||
|
||||
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
|
||||
|
||||
if r.ContentLength != -1 {
|
||||
s += int(r.ContentLength)
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
)
|
||||
|
||||
func TestServerNameFromContext(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
expected := "UNKNOWN"
|
||||
if actual := serverNameFromContext(ctx); actual != expected {
|
||||
t.Errorf("Not equal: expected %q, but got %q", expected, actual)
|
||||
}
|
||||
|
||||
in := "foo"
|
||||
ctx = context.WithValue(ctx, ServerCtxKey, &Server{name: in})
|
||||
if actual := serverNameFromContext(ctx); actual != in {
|
||||
t.Errorf("Not equal: expected %q, but got %q", in, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsInstrumentedHandler(t *testing.T) {
|
||||
handlerErr := errors.New("oh noes")
|
||||
response := []byte("hello world!")
|
||||
h := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
if actual := testutil.ToFloat64(httpMetrics.requestInFlight); actual != 1.0 {
|
||||
t.Errorf("Not same: expected %#v, but got %#v", 1.0, actual)
|
||||
}
|
||||
if handlerErr == nil {
|
||||
w.Write(response)
|
||||
}
|
||||
return handlerErr
|
||||
})
|
||||
|
||||
mh := middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
|
||||
return h.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
ih := newMetricsInstrumentedHandler("bar", mh)
|
||||
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
if actual := ih.ServeHTTP(w, r, h); actual != handlerErr {
|
||||
t.Errorf("Not same: expected %#v, but got %#v", handlerErr, actual)
|
||||
}
|
||||
if actual := testutil.ToFloat64(httpMetrics.requestInFlight); actual != 0.0 {
|
||||
t.Errorf("Not same: expected %#v, but got %#v", 0.0, actual)
|
||||
}
|
||||
|
||||
handlerErr = nil
|
||||
if err := ih.ServeHTTP(w, r, h); err != nil {
|
||||
t.Errorf("Received unexpected error: %w", err)
|
||||
}
|
||||
|
||||
// an empty handler - no errors, no header written
|
||||
mh = middlewareHandlerFunc(func(w http.ResponseWriter, r *http.Request, h Handler) error {
|
||||
return nil
|
||||
})
|
||||
ih = newMetricsInstrumentedHandler("empty", mh)
|
||||
r = httptest.NewRequest("GET", "/", nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
if err := ih.ServeHTTP(w, r, h); err != nil {
|
||||
t.Errorf("Received unexpected error: %w", err)
|
||||
}
|
||||
if actual := w.Result().StatusCode; actual != 200 {
|
||||
t.Errorf("Not same: expected status code %#v, but got %#v", 200, actual)
|
||||
}
|
||||
if actual := w.Result().Header; len(actual) != 0 {
|
||||
t.Errorf("Not empty: expected headers to be empty, but got %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
type middlewareHandlerFunc func(http.ResponseWriter, *http.Request, Handler) error
|
||||
|
||||
func (f middlewareHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, h Handler) error {
|
||||
return f(w, r, h)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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 push
|
||||
|
||||
import (
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
httpcaddyfile.RegisterHandlerDirective("push", parseCaddyfile)
|
||||
}
|
||||
|
||||
// parseCaddyfile sets up the push handler. Syntax:
|
||||
//
|
||||
// push [<matcher>] [<resource>] {
|
||||
// [GET|HEAD] <resource>
|
||||
// headers {
|
||||
// [+]<field> [<value|regexp> [<replacement>]]
|
||||
// -<field>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// A single resource can be specified inline without opening a
|
||||
// block for the most common/simple case. Or, a block can be
|
||||
// opened and multiple resources can be specified, one per
|
||||
// line, optionally preceded by the method. The headers
|
||||
// subdirective can be used to customize the headers that
|
||||
// are set on each (synthetic) push request, using the same
|
||||
// syntax as the 'header' directive for request headers.
|
||||
// Placeholders are accepted in resource and header field
|
||||
// name and value and replacement tokens.
|
||||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
handler := new(Handler)
|
||||
|
||||
for h.Next() {
|
||||
if h.NextArg() {
|
||||
handler.Resources = append(handler.Resources, Resource{Target: h.Val()})
|
||||
}
|
||||
|
||||
// optional block
|
||||
for outerNesting := h.Nesting(); h.NextBlock(outerNesting); {
|
||||
switch h.Val() {
|
||||
case "headers":
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
for innerNesting := h.Nesting(); h.NextBlock(innerNesting); {
|
||||
// include current token, which we treat as an argument here
|
||||
args := []string{h.Val()}
|
||||
args = append(args, h.RemainingArgs()...)
|
||||
|
||||
if handler.Headers == nil {
|
||||
handler.Headers = new(HeaderConfig)
|
||||
}
|
||||
switch len(args) {
|
||||
case 1:
|
||||
headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], "", "")
|
||||
case 2:
|
||||
headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], args[1], "")
|
||||
case 3:
|
||||
headers.CaddyfileHeaderOp(&handler.Headers.HeaderOps, args[0], args[1], args[2])
|
||||
default:
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
}
|
||||
|
||||
case "GET", "HEAD":
|
||||
method := h.Val()
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
target := h.Val()
|
||||
handler.Resources = append(handler.Resources, Resource{
|
||||
Method: method,
|
||||
Target: target,
|
||||
})
|
||||
|
||||
default:
|
||||
handler.Resources = append(handler.Resources, Resource{Target: h.Val()})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handler, nil
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// 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 push
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(Handler{})
|
||||
}
|
||||
|
||||
// Handler is a middleware for manipulating the request body.
|
||||
type Handler struct {
|
||||
Resources []Resource `json:"resources,omitempty"`
|
||||
Headers *HeaderConfig `json:"headers,omitempty"`
|
||||
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (Handler) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "http.handlers.push",
|
||||
New: func() caddy.Module { return new(Handler) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up h.
|
||||
func (h *Handler) Provision(ctx caddy.Context) error {
|
||||
h.logger = ctx.Logger(h)
|
||||
if h.Headers != nil {
|
||||
err := h.Headers.Provision(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("provisioning header operations: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||
pusher, ok := w.(http.Pusher)
|
||||
if !ok {
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// short-circuit recursive pushes
|
||||
if _, ok := r.Header[pushHeader]; ok {
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
|
||||
// create header for push requests
|
||||
hdr := h.initializePushHeaders(r, repl)
|
||||
|
||||
// push first!
|
||||
for _, resource := range h.Resources {
|
||||
h.logger.Debug("pushing resource",
|
||||
zap.String("uri", r.RequestURI),
|
||||
zap.String("push_method", resource.Method),
|
||||
zap.String("push_target", resource.Target),
|
||||
zap.Object("push_headers", caddyhttp.LoggableHTTPHeader(hdr)))
|
||||
err := pusher.Push(repl.ReplaceAll(resource.Target, "."), &http.PushOptions{
|
||||
Method: resource.Method,
|
||||
Header: hdr,
|
||||
})
|
||||
if err != nil {
|
||||
// usually this means either that push is not
|
||||
// supported or concurrent streams are full
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the response writer so that we can initiate push of any resources
|
||||
// described in Link header fields before the response is written
|
||||
lp := linkPusher{
|
||||
ResponseWriterWrapper: &caddyhttp.ResponseWriterWrapper{ResponseWriter: w},
|
||||
handler: h,
|
||||
pusher: pusher,
|
||||
header: hdr,
|
||||
request: r,
|
||||
}
|
||||
|
||||
// serve only after pushing!
|
||||
if err := next.ServeHTTP(lp, r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h Handler) initializePushHeaders(r *http.Request, repl *caddy.Replacer) http.Header {
|
||||
hdr := make(http.Header)
|
||||
|
||||
// prevent recursive pushes
|
||||
hdr.Set(pushHeader, "1")
|
||||
|
||||
// set initial header fields; since exactly how headers should
|
||||
// be implemented for server push is not well-understood, we
|
||||
// are being conservative for now like httpd is:
|
||||
// https://httpd.apache.org/docs/2.4/en/howto/http2.html#push
|
||||
// we only copy some well-known, safe headers that are likely
|
||||
// crucial when requesting certain kinds of content
|
||||
for _, fieldName := range safeHeaders {
|
||||
if vals, ok := r.Header[fieldName]; ok {
|
||||
hdr[fieldName] = vals
|
||||
}
|
||||
}
|
||||
|
||||
// user can customize the push request headers
|
||||
if h.Headers != nil {
|
||||
h.Headers.ApplyTo(hdr, repl)
|
||||
}
|
||||
|
||||
return hdr
|
||||
}
|
||||
|
||||
// servePreloadLinks parses Link headers from upstream and pushes
|
||||
// resources described by them. If a resource has the "nopush"
|
||||
// attribute or describes an external entity (meaning, the resource
|
||||
// URI includes a scheme), it will not be pushed.
|
||||
func (h Handler) servePreloadLinks(pusher http.Pusher, hdr http.Header, resources []string) {
|
||||
for _, resource := range resources {
|
||||
for _, resource := range parseLinkHeader(resource) {
|
||||
if _, ok := resource.params["nopush"]; ok {
|
||||
continue
|
||||
}
|
||||
if isRemoteResource(resource.uri) {
|
||||
continue
|
||||
}
|
||||
err := pusher.Push(resource.uri, &http.PushOptions{
|
||||
Header: hdr,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resource represents a request for a resource to push.
|
||||
type Resource struct {
|
||||
// Method is the request method, which must be GET or HEAD.
|
||||
// Default is GET.
|
||||
Method string `json:"method,omitempty"`
|
||||
|
||||
// Target is the path to the resource being pushed.
|
||||
Target string `json:"target,omitempty"`
|
||||
}
|
||||
|
||||
// HeaderConfig configures headers for synthetic push requests.
|
||||
type HeaderConfig struct {
|
||||
headers.HeaderOps
|
||||
}
|
||||
|
||||
// linkPusher is a http.ResponseWriter that intercepts
|
||||
// the WriteHeader() call to ensure that any resources
|
||||
// described by Link response headers get pushed before
|
||||
// the response is allowed to be written.
|
||||
type linkPusher struct {
|
||||
*caddyhttp.ResponseWriterWrapper
|
||||
handler Handler
|
||||
pusher http.Pusher
|
||||
header http.Header
|
||||
request *http.Request
|
||||
}
|
||||
|
||||
func (lp linkPusher) WriteHeader(statusCode int) {
|
||||
if links, ok := lp.ResponseWriter.Header()["Link"]; ok {
|
||||
// only initiate these pushes if it hasn't been done yet
|
||||
if val := caddyhttp.GetVar(lp.request.Context(), pushedLink); val == nil {
|
||||
lp.handler.logger.Debug("pushing Link resources", zap.Strings("linked", links))
|
||||
caddyhttp.SetVar(lp.request.Context(), pushedLink, true)
|
||||
lp.handler.servePreloadLinks(lp.pusher, lp.header, links)
|
||||
}
|
||||
}
|
||||
lp.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
// isRemoteResource returns true if resource starts with
|
||||
// a scheme or is a protocol-relative URI.
|
||||
func isRemoteResource(resource string) bool {
|
||||
return strings.HasPrefix(resource, "//") ||
|
||||
strings.HasPrefix(resource, "http://") ||
|
||||
strings.HasPrefix(resource, "https://")
|
||||
}
|
||||
|
||||
// safeHeaders is a list of header fields that are
|
||||
// safe to copy to push requests implicitly. It is
|
||||
// assumed that requests for certain kinds of content
|
||||
// would fail without these fields present.
|
||||
var safeHeaders = []string{
|
||||
"Accept-Encoding",
|
||||
"Accept-Language",
|
||||
"Accept",
|
||||
"Cache-Control",
|
||||
"User-Agent",
|
||||
}
|
||||
|
||||
// pushHeader is a header field that gets added to push requests
|
||||
// in order to avoid recursive/infinite pushes.
|
||||
const pushHeader = "Caddy-Push"
|
||||
|
||||
// pushedLink is the key for the variable on the request
|
||||
// context that we use to remember whether we have already
|
||||
// pushed resources from Link headers yet; otherwise, if
|
||||
// multiple push handlers are invoked, it would repeat the
|
||||
// pushing of Link headers.
|
||||
const pushedLink = "http.handlers.push.pushed_link"
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*Handler)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*Handler)(nil)
|
||||
_ caddyhttp.HTTPInterfaces = (*linkPusher)(nil)
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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 push
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// linkResource contains the results of a parsed Link header.
|
||||
type linkResource struct {
|
||||
uri string
|
||||
params map[string]string
|
||||
}
|
||||
|
||||
// parseLinkHeader is responsible for parsing Link header
|
||||
// and returning list of found resources.
|
||||
//
|
||||
// Accepted formats are:
|
||||
//
|
||||
// Link: <resource>; as=script
|
||||
// Link: <resource>; as=script,<resource>; as=style
|
||||
// Link: <resource>;<resource2>
|
||||
//
|
||||
// where <resource> begins with a forward slash (/).
|
||||
func parseLinkHeader(header string) []linkResource {
|
||||
resources := []linkResource{}
|
||||
|
||||
if header == "" {
|
||||
return resources
|
||||
}
|
||||
|
||||
for _, link := range strings.Split(header, comma) {
|
||||
l := linkResource{params: make(map[string]string)}
|
||||
|
||||
li, ri := strings.Index(link, "<"), strings.Index(link, ">")
|
||||
if li == -1 || ri == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
l.uri = strings.TrimSpace(link[li+1 : ri])
|
||||
|
||||
for _, param := range strings.Split(strings.TrimSpace(link[ri+1:]), semicolon) {
|
||||
parts := strings.SplitN(strings.TrimSpace(param), equal, 2)
|
||||
key := strings.TrimSpace(parts[0])
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
l.params[key] = key
|
||||
}
|
||||
if len(parts) == 2 {
|
||||
l.params[key] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
resources = append(resources, l)
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
const (
|
||||
comma = ","
|
||||
semicolon = ";"
|
||||
equal = "="
|
||||
)
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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 push
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseLinkHeader(t *testing.T) {
|
||||
testCases := []struct {
|
||||
header string
|
||||
expectedResources []linkResource
|
||||
}{
|
||||
{
|
||||
header: "</resource>; as=script",
|
||||
expectedResources: []linkResource{{uri: "/resource", params: map[string]string{"as": "script"}}},
|
||||
},
|
||||
{
|
||||
header: "</resource>",
|
||||
expectedResources: []linkResource{{uri: "/resource", params: map[string]string{}}},
|
||||
},
|
||||
{
|
||||
header: "</resource>; nopush",
|
||||
expectedResources: []linkResource{{uri: "/resource", params: map[string]string{"nopush": "nopush"}}},
|
||||
},
|
||||
{
|
||||
header: "</resource>;nopush;rel=next",
|
||||
expectedResources: []linkResource{{uri: "/resource", params: map[string]string{"nopush": "nopush", "rel": "next"}}},
|
||||
},
|
||||
{
|
||||
header: "</resource>;nopush;rel=next,</resource2>;nopush",
|
||||
expectedResources: []linkResource{
|
||||
{uri: "/resource", params: map[string]string{"nopush": "nopush", "rel": "next"}},
|
||||
{uri: "/resource2", params: map[string]string{"nopush": "nopush"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "</resource>,</resource2>",
|
||||
expectedResources: []linkResource{
|
||||
{uri: "/resource", params: map[string]string{}},
|
||||
{uri: "/resource2", params: map[string]string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "malformed",
|
||||
expectedResources: []linkResource{},
|
||||
},
|
||||
{
|
||||
header: "<malformed",
|
||||
expectedResources: []linkResource{},
|
||||
},
|
||||
{
|
||||
header: ",",
|
||||
expectedResources: []linkResource{},
|
||||
},
|
||||
{
|
||||
header: ";",
|
||||
expectedResources: []linkResource{},
|
||||
},
|
||||
{
|
||||
header: "</resource> ; ",
|
||||
expectedResources: []linkResource{{uri: "/resource", params: map[string]string{}}},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range testCases {
|
||||
actualResources := parseLinkHeader(test.header)
|
||||
if !reflect.DeepEqual(actualResources, test.expectedResources) {
|
||||
t.Errorf("Test %d (header: %s) - expected resources %v, got %v",
|
||||
i, test.header, test.expectedResources, actualResources)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
package caddyhttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
@@ -24,7 +25,10 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
@@ -136,6 +140,24 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
|
||||
return dir, true
|
||||
case "http.request.uri.query":
|
||||
return req.URL.RawQuery, true
|
||||
case "http.request.body":
|
||||
if req.Body == nil {
|
||||
return "", true
|
||||
}
|
||||
// normally net/http will close the body for us, but since we
|
||||
// are replacing it with a fake one, we have to ensure we close
|
||||
// the real body ourselves when we're done
|
||||
defer req.Body.Close()
|
||||
// read the request body into a buffer (can't pool because we
|
||||
// don't know its lifetime and would have to make a copy anyway)
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := io.Copy(buf, req.Body)
|
||||
if err != nil {
|
||||
return "", true
|
||||
}
|
||||
// replace real body with buffered data
|
||||
req.Body = ioutil.NopCloser(buf)
|
||||
return buf.String(), true
|
||||
|
||||
// original request, before any internal changes
|
||||
case "http.request.orig_method":
|
||||
@@ -322,6 +344,9 @@ func getReqTLSReplacement(req *http.Request, key string) (interface{}, bool) {
|
||||
return cert.SerialNumber, true
|
||||
case "client.subject":
|
||||
return cert.Subject, true
|
||||
case "client.certificate_pem":
|
||||
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
|
||||
return pem.EncodeToMemory(&block), true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -171,6 +171,10 @@ eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
||||
input: "{http.request.tls.client.san.ips.0}",
|
||||
expect: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
input: "{http.request.tls.client.certificate_pem}",
|
||||
expect: string(clientCert) + "\n", // returned value comes with a newline appended to it
|
||||
},
|
||||
} {
|
||||
actual := repl.ReplaceAll(tc.input, "<empty>")
|
||||
if actual != tc.expect {
|
||||
|
||||
@@ -155,6 +155,9 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
if err != nil {
|
||||
host = upstreamAddr
|
||||
}
|
||||
if port == "" {
|
||||
port = "80"
|
||||
}
|
||||
}
|
||||
|
||||
// the underlying JSON does not yet support different
|
||||
@@ -467,6 +470,12 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
h.FlushInterval = caddy.Duration(dur)
|
||||
}
|
||||
|
||||
case "buffer_requests":
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
h.BufferRequests = true
|
||||
|
||||
case "header_up":
|
||||
if h.Headers == nil {
|
||||
h.Headers = new(headers.Handler)
|
||||
@@ -561,7 +570,9 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
} else if commonScheme == "https" {
|
||||
return d.Errf("upstreams are configured for HTTPS but transport module does not support TLS: %T", transport)
|
||||
}
|
||||
if !reflect.DeepEqual(transport, reflect.New(reflect.TypeOf(transport).Elem()).Interface()) {
|
||||
|
||||
// no need to encode empty default transport
|
||||
if !reflect.DeepEqual(transport, new(HTTPTransport)) {
|
||||
h.TransportRaw = caddyconfig.JSONModuleObject(transport, "protocol", transportModuleName, nil)
|
||||
}
|
||||
}
|
||||
@@ -717,6 +728,14 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "compression":
|
||||
if d.NextArg() {
|
||||
if d.Val() == "off" {
|
||||
var disable bool
|
||||
h.Compression = &disable
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ func init() {
|
||||
// root <path>
|
||||
// split <at>
|
||||
// env <key> <value>
|
||||
// resolve_root_symlink
|
||||
// }
|
||||
//
|
||||
func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
@@ -67,6 +68,9 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
}
|
||||
t.EnvVars[args[0]] = args[1]
|
||||
|
||||
case "resolve_root_symlink":
|
||||
t.ResolveRootSymlink = true
|
||||
|
||||
default:
|
||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||
}
|
||||
@@ -196,6 +200,14 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
|
||||
return nil, dispenser.ArgErr()
|
||||
}
|
||||
indexFile = args[0]
|
||||
|
||||
case "resolve_root_symlink":
|
||||
args := dispenser.RemainingArgs()
|
||||
dispenser.Delete()
|
||||
for range args {
|
||||
dispenser.Delete()
|
||||
}
|
||||
fcgiTransport.ResolveRootSymlink = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ func DisabledTest(t *testing.T) {
|
||||
log.Println("test:", "post data (more than 60KB)")
|
||||
data := ""
|
||||
for i := 0x00; i < 0xff; i++ {
|
||||
v0 := strings.Repeat(string(i), 256)
|
||||
v0 := strings.Repeat(fmt.Sprint(i), 256)
|
||||
h := md5.New()
|
||||
_, _ = io.WriteString(h, v0)
|
||||
k0 := fmt.Sprintf("%x", h.Sum(nil))
|
||||
@@ -266,7 +266,7 @@ func DisabledTest(t *testing.T) {
|
||||
log.Println("test:", "post forms (256 keys, more than 1MB)")
|
||||
p1 := make(map[string]string, 1)
|
||||
for i := 0x00; i < 0xff; i++ {
|
||||
v0 := strings.Repeat(string(i), 4096)
|
||||
v0 := strings.Repeat(fmt.Sprint(i), 4096)
|
||||
h := md5.New()
|
||||
_, _ = io.WriteString(h, v0)
|
||||
k0 := fmt.Sprintf("%x", h.Sum(nil))
|
||||
|
||||
@@ -54,6 +54,14 @@ type Transport struct {
|
||||
// that 404s if the fastcgi path info is not found.
|
||||
SplitPath []string `json:"split_path,omitempty"`
|
||||
|
||||
// Path declared as root directory will be resolved to its absolute value
|
||||
// after the evaluation of any symbolic links.
|
||||
// Due to the nature of PHP opcache, root directory path is cached: when
|
||||
// using a symlinked directory as root this could generate errors when
|
||||
// symlink is changed without php-fpm being restarted; enabling this
|
||||
// directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path.
|
||||
ResolveRootSymlink bool `json:"resolve_root_symlink,omitempty"`
|
||||
|
||||
// Extra environment variables.
|
||||
EnvVars map[string]string `json:"env,omitempty"`
|
||||
|
||||
@@ -179,6 +187,13 @@ func (t Transport) buildEnv(r *http.Request) (map[string]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.ResolveRootSymlink {
|
||||
root, err = filepath.EvalSymlinks(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
fpath := r.URL.Path
|
||||
|
||||
// split "actual path" from "path info" if configured
|
||||
@@ -202,6 +217,12 @@ func (t Transport) buildEnv(r *http.Request) (map[string]string, error) {
|
||||
pathPrefix, _ := r.Context().Value(caddy.CtxKey("path_prefix")).(string)
|
||||
scriptName = path.Join(pathPrefix, scriptName)
|
||||
|
||||
// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
|
||||
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
|
||||
if scriptName != "" && !strings.HasPrefix(scriptName, "/") {
|
||||
scriptName = "/" + scriptName
|
||||
}
|
||||
|
||||
// Get the request URL from context. The context stores the original URL in case
|
||||
// it was changed by a middleware such as rewrite. By default, we pass the
|
||||
// original URI in as the value of REQUEST_URI (the user can overwrite this
|
||||
@@ -226,6 +247,11 @@ func (t Transport) buildEnv(r *http.Request) (map[string]string, error) {
|
||||
reqHost = r.Host
|
||||
}
|
||||
|
||||
authUser := ""
|
||||
if val, ok := repl.Get("http.auth.user.id"); ok {
|
||||
authUser = val.(string)
|
||||
}
|
||||
|
||||
// Some variables are unused but cleared explicitly to prevent
|
||||
// the parent environment from interfering.
|
||||
env = map[string]string{
|
||||
@@ -240,11 +266,10 @@ func (t Transport) buildEnv(r *http.Request) (map[string]string, error) {
|
||||
"REMOTE_HOST": ip, // For speed, remote host lookups disabled
|
||||
"REMOTE_PORT": port,
|
||||
"REMOTE_IDENT": "", // Not used
|
||||
"REMOTE_USER": "", // TODO: once there are authentication handlers, populate this
|
||||
"REMOTE_USER": authUser,
|
||||
"REQUEST_METHOD": r.Method,
|
||||
"REQUEST_SCHEME": requestScheme,
|
||||
"SERVER_NAME": reqHost,
|
||||
"SERVER_PORT": reqPort,
|
||||
"SERVER_PROTOCOL": r.Proto,
|
||||
"SERVER_SOFTWARE": t.serverSoftware,
|
||||
|
||||
@@ -264,6 +289,13 @@ func (t Transport) buildEnv(r *http.Request) (map[string]string, error) {
|
||||
env["PATH_TRANSLATED"] = filepath.Join(root, pathInfo) // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html
|
||||
}
|
||||
|
||||
// compliance with the CGI specification requires that
|
||||
// SERVER_PORT should only exist if it's a valid numeric value.
|
||||
// Info: https://www.ietf.org/rfc/rfc3875 Page 18
|
||||
if reqPort != "" {
|
||||
env["SERVER_PORT"] = reqPort
|
||||
}
|
||||
|
||||
// Some web apps rely on knowing HTTPS or not
|
||||
if r.TLS != nil {
|
||||
env["HTTPS"] = "on"
|
||||
|
||||
@@ -78,7 +78,6 @@ type ActiveHealthChecks struct {
|
||||
// body of a healthy backend.
|
||||
ExpectBody string `json:"expect_body,omitempty"`
|
||||
|
||||
stopChan chan struct{}
|
||||
httpClient *http.Client
|
||||
bodyRegexp *regexp.Regexp
|
||||
logger *zap.Logger
|
||||
@@ -137,8 +136,7 @@ func (h *Handler) activeHealthChecker() {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
h.doActiveHealthCheckForAllHosts()
|
||||
case <-h.HealthChecks.Active.stopChan:
|
||||
// TODO: consider using a Context for cancellation instead
|
||||
case <-h.ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
@@ -155,7 +153,15 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
|
||||
log.Printf("[PANIC] active health check: %v\n%s", err, debug.Stack())
|
||||
}
|
||||
}()
|
||||
networkAddr := upstream.Dial
|
||||
|
||||
networkAddr, err := caddy.NewReplacer().ReplaceOrErr(upstream.Dial, true, true)
|
||||
if err != nil {
|
||||
h.HealthChecks.Active.logger.Error("invalid use of placeholders in dial address for active health checks",
|
||||
zap.String("address", networkAddr),
|
||||
zap.Error(err),
|
||||
)
|
||||
return
|
||||
}
|
||||
addr, err := caddy.ParseNetworkAddress(networkAddr)
|
||||
if err != nil {
|
||||
h.HealthChecks.Active.logger.Error("bad network address",
|
||||
@@ -164,7 +170,13 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
|
||||
)
|
||||
return
|
||||
}
|
||||
if addr.PortRangeSize() != 1 {
|
||||
if hcp := uint(upstream.activeHealthCheckPort); hcp != 0 {
|
||||
if addr.IsUnixNetwork() {
|
||||
addr.Network = "tcp" // I guess we just assume TCP since we are using a port??
|
||||
}
|
||||
addr.StartPort, addr.EndPort = hcp, hcp
|
||||
}
|
||||
if upstream.LookupSRV == "" && addr.PortRangeSize() != 1 {
|
||||
h.HealthChecks.Active.logger.Error("multiple addresses (upstream must map to only one address)",
|
||||
zap.String("address", networkAddr),
|
||||
)
|
||||
@@ -180,7 +192,7 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
|
||||
err = h.doActiveHealthCheck(DialInfo{Network: addr.Network, Address: hostAddr}, hostAddr, upstream.Host)
|
||||
if err != nil {
|
||||
h.HealthChecks.Active.logger.Error("active health check failed",
|
||||
zap.String("address", networkAddr),
|
||||
zap.String("address", hostAddr),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
@@ -218,9 +230,8 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, host H
|
||||
u.Host = net.JoinHostPort(host, portStr)
|
||||
}
|
||||
|
||||
// attach dialing information to this request - TODO: use caddy.Context's context
|
||||
// so it can be canceled on config reload
|
||||
ctx := context.Background()
|
||||
// attach dialing information to this request
|
||||
ctx := h.ctx.Context
|
||||
ctx = context.WithValue(ctx, caddy.ReplacerCtxKey, caddy.NewReplacer())
|
||||
ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]interface{}{
|
||||
dialInfoVarKey: dialInfo,
|
||||
@@ -341,8 +352,8 @@ func (h *Handler) countFailure(upstream *Upstream) {
|
||||
if err != nil {
|
||||
h.HealthChecks.Passive.logger.Error("could not count failure",
|
||||
zap.String("host", upstream.Dial),
|
||||
zap.Error(err),
|
||||
)
|
||||
zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// forget it later
|
||||
@@ -357,8 +368,7 @@ func (h *Handler) countFailure(upstream *Upstream) {
|
||||
if err != nil {
|
||||
h.HealthChecks.Passive.logger.Error("could not forget failure",
|
||||
zap.String("host", upstream.Dial),
|
||||
zap.Error(err),
|
||||
)
|
||||
zap.Error(err))
|
||||
}
|
||||
}(upstream.Host, failDuration)
|
||||
}
|
||||
|
||||
@@ -92,8 +92,9 @@ type Upstream struct {
|
||||
// HeaderAffinity string
|
||||
// IPAffinity string
|
||||
|
||||
healthCheckPolicy *PassiveHealthChecks
|
||||
cb CircuitBreaker
|
||||
activeHealthCheckPort int
|
||||
healthCheckPolicy *PassiveHealthChecks
|
||||
cb CircuitBreaker
|
||||
}
|
||||
|
||||
func (u Upstream) String() string {
|
||||
@@ -177,7 +178,7 @@ func (u *Upstream) fillDialInfo(r *http.Request) (DialInfo, error) {
|
||||
// of the state of a remote host. It implements the
|
||||
// Host interface.
|
||||
type upstreamHost struct {
|
||||
numRequests int64 // must be first field to be 64-bit aligned on 32-bit systems (see https://golang.org/pkg/sync/atomic/#pkg-note-BUG)
|
||||
numRequests int64 // must be 64-bit aligned on 32-bit systems (see https://golang.org/pkg/sync/atomic/#pkg-note-BUG)
|
||||
fails int64
|
||||
unhealthy int32
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -43,6 +44,9 @@ type HTTPTransport struct {
|
||||
// able to borrow/use at least some of these config fields; if so,
|
||||
// maybe move them into a type called CommonTransport and embed it?
|
||||
|
||||
// Configures the DNS resolver used to resolve the IP address of upstream hostnames.
|
||||
Resolver *UpstreamResolver `json:"resolver,omitempty"`
|
||||
|
||||
// Configures TLS to the upstream. Setting this to an empty struct
|
||||
// is sufficient to enable TLS with reasonable defaults.
|
||||
TLS *TLSConfig `json:"tls,omitempty"`
|
||||
@@ -146,7 +150,30 @@ func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error)
|
||||
dialer := &net.Dialer{
|
||||
Timeout: time.Duration(h.DialTimeout),
|
||||
FallbackDelay: time.Duration(h.FallbackDelay),
|
||||
// TODO: Resolver
|
||||
}
|
||||
|
||||
if h.Resolver != nil {
|
||||
for _, v := range h.Resolver.Addresses {
|
||||
addr, err := caddy.ParseNetworkAddress(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addr.PortRangeSize() != 1 {
|
||||
return nil, fmt.Errorf("resolver address must have exactly one address; cannot call %v", addr)
|
||||
}
|
||||
h.Resolver.netAddrs = append(h.Resolver.netAddrs, addr)
|
||||
}
|
||||
d := &net.Dialer{
|
||||
Timeout: time.Duration(h.DialTimeout),
|
||||
FallbackDelay: time.Duration(h.FallbackDelay),
|
||||
}
|
||||
dialer.Resolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
addr := h.Resolver.netAddrs[weakrand.Intn(len(h.Resolver.netAddrs))]
|
||||
return d.DialContext(ctx, addr.Network, addr.JoinHostPort(0))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
rt := &http.Transport{
|
||||
@@ -359,6 +386,18 @@ func (t TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// UpstreamResolver holds the set of addresses of DNS resolvers of
|
||||
// upstream addresses
|
||||
type UpstreamResolver struct {
|
||||
// The addresses of DNS resolvers to use when looking up the addresses of proxy upstreams.
|
||||
// It accepts [network addresses](/docs/conventions#network-addresses)
|
||||
// with port range of only 1. If the host is an IP address, it will be dialed directly to resolve the upstream server.
|
||||
// If the host is not an IP address, the addresses are resolved using the [name resolution convention](https://golang.org/pkg/net/#hdr-Name_Resolution) of the Go standard library.
|
||||
// If the array contains more than 1 resolver address, one is chosen at random.
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
netAddrs []caddy.NetworkAddress
|
||||
}
|
||||
|
||||
// KeepAlive holds configuration pertaining to HTTP Keep-Alive.
|
||||
type KeepAlive struct {
|
||||
// Whether HTTP Keep-Alive is enabled. Default: true
|
||||
|
||||
@@ -112,6 +112,7 @@ type Handler struct {
|
||||
Transport http.RoundTripper `json:"-"`
|
||||
CB CircuitBreaker `json:"-"`
|
||||
|
||||
ctx caddy.Context
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
@@ -125,8 +126,22 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
|
||||
|
||||
// Provision ensures that h is set up properly before use.
|
||||
func (h *Handler) Provision(ctx caddy.Context) error {
|
||||
h.ctx = ctx
|
||||
h.logger = ctx.Logger(h)
|
||||
|
||||
// verify SRV compatibility
|
||||
for i, v := range h.Upstreams {
|
||||
if v.LookupSRV == "" {
|
||||
continue
|
||||
}
|
||||
if h.HealthChecks != nil && h.HealthChecks.Active != nil {
|
||||
return fmt.Errorf(`upstream: lookup_srv is incompatible with active health checks: %d: {"dial": %q, "lookup_srv": %q}`, i, v.Dial, v.LookupSRV)
|
||||
}
|
||||
if v.Dial != "" {
|
||||
return fmt.Errorf(`upstream: specifying dial address is incompatible with lookup_srv: %d: {"dial": %q, "lookup_srv": %q}`, i, v.Dial, v.LookupSRV)
|
||||
}
|
||||
}
|
||||
|
||||
// start by loading modules
|
||||
if h.TransportRaw != nil {
|
||||
mod, err := ctx.LoadModule(h, "TransportRaw")
|
||||
@@ -235,36 +250,51 @@ func (h *Handler) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// if active health checks are enabled, configure them and start a worker
|
||||
if h.HealthChecks != nil &&
|
||||
h.HealthChecks.Active != nil &&
|
||||
(h.HealthChecks.Active.Path != "" || h.HealthChecks.Active.Port != 0) {
|
||||
h.HealthChecks.Active.logger = h.logger.Named("health_checker.active")
|
||||
|
||||
timeout := time.Duration(h.HealthChecks.Active.Timeout)
|
||||
if timeout == 0 {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
|
||||
h.HealthChecks.Active.stopChan = make(chan struct{})
|
||||
h.HealthChecks.Active.httpClient = &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: h.Transport,
|
||||
}
|
||||
|
||||
if h.HealthChecks.Active.Interval == 0 {
|
||||
h.HealthChecks.Active.Interval = caddy.Duration(30 * time.Second)
|
||||
}
|
||||
|
||||
if h.HealthChecks.Active.ExpectBody != "" {
|
||||
var err error
|
||||
h.HealthChecks.Active.bodyRegexp, err = regexp.Compile(h.HealthChecks.Active.ExpectBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expect_body: compiling regular expression: %v", err)
|
||||
if h.HealthChecks != nil {
|
||||
// set defaults on passive health checks, if necessary
|
||||
if h.HealthChecks.Passive != nil {
|
||||
if h.HealthChecks.Passive.FailDuration > 0 && h.HealthChecks.Passive.MaxFails == 0 {
|
||||
h.HealthChecks.Passive.MaxFails = 1
|
||||
}
|
||||
}
|
||||
|
||||
go h.activeHealthChecker()
|
||||
// if active health checks are enabled, configure them and start a worker
|
||||
if h.HealthChecks.Active != nil &&
|
||||
(h.HealthChecks.Active.Path != "" || h.HealthChecks.Active.Port != 0) {
|
||||
h.HealthChecks.Active.logger = h.logger.Named("health_checker.active")
|
||||
|
||||
timeout := time.Duration(h.HealthChecks.Active.Timeout)
|
||||
if timeout == 0 {
|
||||
timeout = 5 * time.Second
|
||||
}
|
||||
|
||||
h.HealthChecks.Active.httpClient = &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: h.Transport,
|
||||
}
|
||||
|
||||
for _, upstream := range h.Upstreams {
|
||||
// if there's an alternative port for health-check provided in the config,
|
||||
// then use it, otherwise use the port of upstream.
|
||||
if h.HealthChecks.Active.Port != 0 {
|
||||
upstream.activeHealthCheckPort = h.HealthChecks.Active.Port
|
||||
}
|
||||
}
|
||||
|
||||
if h.HealthChecks.Active.Interval == 0 {
|
||||
h.HealthChecks.Active.Interval = caddy.Duration(30 * time.Second)
|
||||
}
|
||||
|
||||
if h.HealthChecks.Active.ExpectBody != "" {
|
||||
var err error
|
||||
h.HealthChecks.Active.bodyRegexp, err = regexp.Compile(h.HealthChecks.Active.ExpectBody)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expect_body: compiling regular expression: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
go h.activeHealthChecker()
|
||||
}
|
||||
}
|
||||
|
||||
// set up any response routes
|
||||
@@ -280,14 +310,6 @@ func (h *Handler) Provision(ctx caddy.Context) error {
|
||||
|
||||
// Cleanup cleans up the resources made by h during provisioning.
|
||||
func (h *Handler) Cleanup() error {
|
||||
// stop the active health checker
|
||||
if h.HealthChecks != nil &&
|
||||
h.HealthChecks.Active != nil &&
|
||||
h.HealthChecks.Active.stopChan != nil {
|
||||
// TODO: consider using context cancellation, could be much simpler
|
||||
close(h.HealthChecks.Active.stopChan)
|
||||
}
|
||||
|
||||
// TODO: Close keepalive connections on reload? https://github.com/caddyserver/caddy/pull/2507/files#diff-70219fd88fe3f36834f474ce6537ed26R762
|
||||
|
||||
// remove hosts from our config from the pool
|
||||
@@ -329,10 +351,17 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
||||
fmt.Errorf("preparing request for upstream round-trip: %v", err))
|
||||
}
|
||||
|
||||
// we will need the original headers and Host
|
||||
// value if header operations are configured
|
||||
reqHeader := r.Header
|
||||
// we will need the original headers and Host value if
|
||||
// header operations are configured; and we should
|
||||
// restore them after we're done if they are changed
|
||||
// (for example, changing the outbound Host header
|
||||
// should not permanently change r.Host; issue #3509)
|
||||
reqHost := r.Host
|
||||
reqHeader := r.Header
|
||||
defer func() {
|
||||
r.Host = reqHost
|
||||
r.Header = reqHeader
|
||||
}()
|
||||
|
||||
start := time.Now()
|
||||
|
||||
@@ -344,7 +373,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
||||
if proxyErr == nil {
|
||||
proxyErr = fmt.Errorf("no upstreams available")
|
||||
}
|
||||
if !h.LoadBalancing.tryAgain(start, proxyErr, r) {
|
||||
if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, r) {
|
||||
break
|
||||
}
|
||||
continue
|
||||
@@ -355,7 +384,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
||||
// DialInfo struct should have valid network address syntax
|
||||
dialInfo, err := upstream.fillDialInfo(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("making dial info: %v", err)
|
||||
err = fmt.Errorf("making dial info: %v", err)
|
||||
return caddyhttp.Error(http.StatusBadGateway, err)
|
||||
}
|
||||
|
||||
// attach to the request information about how to dial the upstream;
|
||||
@@ -403,7 +433,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
||||
h.countFailure(upstream)
|
||||
|
||||
// if we've tried long enough, break
|
||||
if !h.LoadBalancing.tryAgain(start, proxyErr, r) {
|
||||
if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -604,6 +634,14 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
|
||||
|
||||
rw.WriteHeader(res.StatusCode)
|
||||
|
||||
// some apps need the response headers before starting to stream content with http2,
|
||||
// so it's important to explicitly flush the headers to the client before streaming the data.
|
||||
// (see https://github.com/caddyserver/caddy/issues/3556 for use case and nuances)
|
||||
if h.isBidirectionalStream(req, res) {
|
||||
if wf, ok := rw.(http.Flusher); ok {
|
||||
wf.Flush()
|
||||
}
|
||||
}
|
||||
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 {
|
||||
@@ -646,7 +684,7 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
|
||||
// long enough before the next retry (i.e. no more sleeping is
|
||||
// needed). If false is returned, the handler should stop trying to
|
||||
// proxy the request.
|
||||
func (lb LoadBalancing) tryAgain(start time.Time, proxyErr error, req *http.Request) bool {
|
||||
func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, proxyErr error, req *http.Request) bool {
|
||||
// if we've tried long enough, break
|
||||
if time.Since(start) >= time.Duration(lb.TryDuration) {
|
||||
return false
|
||||
@@ -672,8 +710,12 @@ func (lb LoadBalancing) tryAgain(start time.Time, proxyErr error, req *http.Requ
|
||||
}
|
||||
|
||||
// otherwise, wait and try the next available host
|
||||
time.Sleep(time.Duration(lb.TryInterval))
|
||||
return true
|
||||
select {
|
||||
case <-time.After(time.Duration(lb.TryInterval)):
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// directRequest modifies only req.URL so that it points to the upstream
|
||||
|
||||
@@ -362,6 +362,13 @@ func (s HeaderHashSelection) Select(pool UpstreamPool, req *http.Request) *Upstr
|
||||
if s.Field == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The Host header should be obtained from the req.Host field
|
||||
// since net/http removes it from the header map.
|
||||
if s.Field == "Host" && req.Host != "" {
|
||||
return hostByHashing(pool, req.Host)
|
||||
}
|
||||
|
||||
val := req.Header.Get(s.Field)
|
||||
if val == "" {
|
||||
return RandomSelection{}.Select(pool, req)
|
||||
@@ -401,6 +408,9 @@ func leastRequests(upstreams []*Upstream) *Upstream {
|
||||
best = append(best, upstream)
|
||||
}
|
||||
}
|
||||
if len(best) == 0 {
|
||||
return nil
|
||||
}
|
||||
return best[weakrand.Intn(len(best))]
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ package reverseproxy
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -88,18 +89,42 @@ func (h Handler) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request
|
||||
// flushInterval returns the p.FlushInterval value, conditionally
|
||||
// overriding its value for a specific request/response.
|
||||
func (h Handler) flushInterval(req *http.Request, res *http.Response) time.Duration {
|
||||
resCT := res.Header.Get("Content-Type")
|
||||
resCTHeader := res.Header.Get("Content-Type")
|
||||
resCT, _, err := mime.ParseMediaType(resCTHeader)
|
||||
|
||||
// For Server-Sent Events responses, flush immediately.
|
||||
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
|
||||
if resCT == "text/event-stream" {
|
||||
if err == nil && resCT == "text/event-stream" {
|
||||
return -1 // negative means immediately
|
||||
}
|
||||
|
||||
// TODO: more specific cases? e.g. res.ContentLength == -1? (this TODO is from the std lib)
|
||||
// for h2 and h2c upstream streaming data to client (issues #3556 and #3606)
|
||||
if h.isBidirectionalStream(req, res) {
|
||||
return -1
|
||||
}
|
||||
|
||||
// TODO: more specific cases? e.g. res.ContentLength == -1? (this TODO is from the std lib, but
|
||||
// strangely similar to our isBidirectionalStream function that we implemented ourselves)
|
||||
return time.Duration(h.FlushInterval)
|
||||
}
|
||||
|
||||
// isBidirectionalStream returns whether we should work in bi-directional stream mode.
|
||||
//
|
||||
// See https://github.com/caddyserver/caddy/pull/3620 for discussion of nuances.
|
||||
func (h Handler) isBidirectionalStream(req *http.Request, res *http.Response) bool {
|
||||
// We have to check the encoding here; only flush headers with identity encoding.
|
||||
// Non-identity encoding might combine with "encode" directive, and in that case,
|
||||
// if body size larger than enc.MinLength, upper level encode handle might have
|
||||
// Content-Encoding header to write.
|
||||
// (see https://github.com/caddyserver/caddy/issues/3606 for use case)
|
||||
ae := req.Header.Get("Accept-Encoding")
|
||||
|
||||
return req.ProtoMajor == 2 &&
|
||||
res.ProtoMajor == 2 &&
|
||||
res.ContentLength == -1 &&
|
||||
(ae == "identity" || ae == "")
|
||||
}
|
||||
|
||||
func (h Handler) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error {
|
||||
if flushInterval != 0 {
|
||||
if wf, ok := dst.(writeFlusher); ok {
|
||||
@@ -122,7 +147,7 @@ func (h Handler) copyResponse(dst io.Writer, src io.Reader, flushInterval time.D
|
||||
// of bytes written.
|
||||
func (h Handler) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
||||
if len(buf) == 0 {
|
||||
buf = make([]byte, 32*1024)
|
||||
buf = make([]byte, defaultBufferSize)
|
||||
}
|
||||
var written int64
|
||||
for {
|
||||
@@ -229,6 +254,8 @@ func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
|
||||
|
||||
var streamingBufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, 32*1024)
|
||||
return make([]byte, defaultBufferSize)
|
||||
},
|
||||
}
|
||||
|
||||
const defaultBufferSize = 32 * 1024
|
||||
|
||||
@@ -157,7 +157,7 @@ func (routes RouteList) ProvisionHandlers(ctx caddy.Context) error {
|
||||
|
||||
// pre-compile the middleware handler chain
|
||||
for _, midhandler := range routes[i].Handlers {
|
||||
routes[i].middleware = append(routes[i].middleware, wrapMiddleware(midhandler))
|
||||
routes[i].middleware = append(routes[i].middleware, wrapMiddleware(ctx, midhandler))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -242,7 +242,10 @@ func wrapRoute(route Route) Middleware {
|
||||
// we need to pull this particular MiddlewareHandler
|
||||
// pointer into its own stack frame to preserve it so it
|
||||
// won't be overwritten in future loop iterations.
|
||||
func wrapMiddleware(mh MiddlewareHandler) Middleware {
|
||||
func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware {
|
||||
// wrap the middleware with metrics instrumentation
|
||||
metricsHandler := newMetricsInstrumentedHandler(caddy.GetModuleName(mh), mh)
|
||||
|
||||
return func(next Handler) Handler {
|
||||
// copy the next handler (it's an interface, so it's
|
||||
// just a very lightweight copy of a pointer); this
|
||||
@@ -253,7 +256,7 @@ func wrapMiddleware(mh MiddlewareHandler) Middleware {
|
||||
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
// TODO: This is where request tracing could be implemented
|
||||
// TODO: see what the std lib gives us in terms of stack tracing too
|
||||
return mh.ServeHTTP(w, r, nextCopy)
|
||||
return metricsHandler.ServeHTTP(w, r, nextCopy)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,8 @@ type Server struct {
|
||||
// ⚠️ Experimental feature; subject to change or removal.
|
||||
AllowH2C bool `json:"allow_h2c,omitempty"`
|
||||
|
||||
name string
|
||||
|
||||
primaryHandlerChain Handler
|
||||
errorHandlerChain Handler
|
||||
listenerWrappers []caddy.ListenerWrapper
|
||||
@@ -207,6 +209,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// restore original request before invoking error handler chain (issue #3717)
|
||||
// TODO: this does not restore original headers, if modified (for efficiency)
|
||||
origReq := r.Context().Value(OriginalRequestCtxKey).(http.Request)
|
||||
r.Method = origReq.Method
|
||||
r.RemoteAddr = origReq.RemoteAddr
|
||||
r.RequestURI = origReq.RequestURI
|
||||
cloneURL(origReq.URL, r.URL)
|
||||
|
||||
// prepare the error log
|
||||
logger := errLog
|
||||
if s.Logs != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/map"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/push"
|
||||
_ "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"
|
||||
|
||||
@@ -56,6 +56,7 @@ func extractFrontMatter(input string) (map[string]interface{}, string, error) {
|
||||
if index >= 0 {
|
||||
fmEndFenceStart = index
|
||||
fmEndFence = fence
|
||||
break
|
||||
}
|
||||
}
|
||||
if fmEndFenceStart < 0 {
|
||||
|
||||
@@ -64,6 +64,16 @@ func init() {
|
||||
// {{env "VAR_NAME"}}
|
||||
// ```
|
||||
//
|
||||
// ##### `placeholder`
|
||||
//
|
||||
// Gets an [placeholder variable](/docs/conventions#placeholders).
|
||||
// The braces (`{}`) have to be omitted.
|
||||
//
|
||||
// ```
|
||||
// {{placeholder "http.request.uri.path"}}
|
||||
// {{placeholder "http.error.status_code"}}
|
||||
// ```
|
||||
//
|
||||
// ##### `.Host`
|
||||
//
|
||||
// Returns the hostname portion (no port) of the Host header of the HTTP request.
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
"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/yuin/goldmark"
|
||||
highlighting "github.com/yuin/goldmark-highlighting"
|
||||
@@ -152,6 +153,7 @@ func (c templateContext) executeTemplateInBuffer(tplName string, buf *bytes.Buff
|
||||
"splitFrontMatter": c.funcSplitFrontMatter,
|
||||
"listFiles": c.funcListFiles,
|
||||
"env": c.funcEnv,
|
||||
"placeholder": c.placeholder,
|
||||
})
|
||||
|
||||
parsedTpl, err := tpl.Parse(buf.String())
|
||||
@@ -164,6 +166,12 @@ func (c templateContext) executeTemplateInBuffer(tplName string, buf *bytes.Buff
|
||||
return parsedTpl.Execute(buf, c)
|
||||
}
|
||||
|
||||
func (c templateContext) placeholder(name string) string {
|
||||
repl := c.Req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
value, _ := repl.GetString(name)
|
||||
return value
|
||||
}
|
||||
|
||||
func (templateContext) funcEnv(varName string) string {
|
||||
return os.Getenv(varName)
|
||||
}
|
||||
@@ -254,7 +262,6 @@ func (templateContext) funcMarkdown(input interface{}) (string, error) {
|
||||
parser.WithAutoHeadingID(),
|
||||
),
|
||||
goldmark.WithRendererOptions(
|
||||
gmhtml.WithHardWraps(),
|
||||
gmhtml.WithUnsafe(), // TODO: this is not awesome, maybe should be configurable?
|
||||
),
|
||||
)
|
||||
|
||||
@@ -90,7 +90,7 @@ func TestCookie(t *testing.T) {
|
||||
},
|
||||
{
|
||||
// cookie with optional fields
|
||||
cookie: &http.Cookie{Name: "cookie", Value: "cookieValue", Path: "/path", Domain: "https://localhost", Expires: (time.Now().Add(10 * time.Minute)), MaxAge: 120},
|
||||
cookie: &http.Cookie{Name: "cookie", Value: "cookieValue", Path: "/path", Domain: "https://localhost", Expires: time.Now().Add(10 * time.Minute), MaxAge: 120},
|
||||
cookieName: "cookie",
|
||||
expect: "cookieValue",
|
||||
},
|
||||
@@ -316,6 +316,17 @@ title: Welcome
|
||||
expect: `Welcome`,
|
||||
body: "\n### Test",
|
||||
},
|
||||
{
|
||||
// yaml with non-fence '...' line after closing fence (i.e. first matching closing fence should be used)
|
||||
input: `---
|
||||
title: Welcome
|
||||
---
|
||||
### Test
|
||||
...
|
||||
yeah`,
|
||||
expect: `Welcome`,
|
||||
body: "\n### Test\n...\nyeah",
|
||||
},
|
||||
{
|
||||
// toml
|
||||
input: `+++
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -64,6 +65,24 @@ func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (m *VarsMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
if *m == nil {
|
||||
*m = make(map[string]string)
|
||||
}
|
||||
for d.Next() {
|
||||
var field, val string
|
||||
if !d.Args(&field, &val) {
|
||||
return d.Errf("malformed vars matcher: expected both field and value")
|
||||
}
|
||||
(*m)[field] = val
|
||||
if d.NextBlock(0) {
|
||||
return d.Err("malformed vars matcher: blocks are not supported")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match matches a request based on variables in the context.
|
||||
func (m VarsMatcher) Match(r *http.Request) bool {
|
||||
vars := r.Context().Value(VarsCtxKey).(map[string]interface{})
|
||||
@@ -106,6 +125,35 @@ func (MatchVarsRE) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (m *MatchVarsRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
if *m == nil {
|
||||
*m = make(map[string]*MatchRegexp)
|
||||
}
|
||||
for d.Next() {
|
||||
var first, second, third string
|
||||
if !d.Args(&first, &second) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
var name, field, val string
|
||||
if d.Args(&third) {
|
||||
name = first
|
||||
field = second
|
||||
val = third
|
||||
} else {
|
||||
field = first
|
||||
val = second
|
||||
}
|
||||
|
||||
(*m)[field] = &MatchRegexp{Pattern: val, Name: name}
|
||||
if d.NextBlock(0) {
|
||||
return d.Err("malformed vars_regexp matcher: blocks are not supported")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provision compiles m's regular expressions.
|
||||
func (m MatchVarsRE) Provision(ctx caddy.Context) error {
|
||||
for _, rm := range m {
|
||||
|
||||
+230
-74
@@ -20,11 +20,16 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -56,7 +61,7 @@ type ACMEIssuer struct {
|
||||
|
||||
// If using an ACME CA that requires an external account
|
||||
// binding, specify the CA-provided credentials here.
|
||||
ExternalAccount *ExternalAccountBinding `json:"external_account,omitempty"`
|
||||
ExternalAccount *acme.EAB `json:"external_account,omitempty"`
|
||||
|
||||
// Time to wait before timing out an ACME operation.
|
||||
ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
|
||||
@@ -72,6 +77,7 @@ type ACMEIssuer struct {
|
||||
rootPool *x509.CertPool
|
||||
template certmagic.ACMEManager
|
||||
magic *certmagic.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
@@ -82,47 +88,52 @@ func (ACMEIssuer) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up m.
|
||||
func (m *ACMEIssuer) Provision(ctx caddy.Context) error {
|
||||
// Provision sets up iss.
|
||||
func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
|
||||
iss.logger = ctx.Logger(iss)
|
||||
|
||||
// DNS providers
|
||||
if m.Challenges != nil && m.Challenges.DNS != nil && m.Challenges.DNS.ProviderRaw != nil {
|
||||
val, err := ctx.LoadModule(m.Challenges.DNS, "ProviderRaw")
|
||||
if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.ProviderRaw != nil {
|
||||
val, err := ctx.LoadModule(iss.Challenges.DNS, "ProviderRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading DNS provider module: %v", err)
|
||||
}
|
||||
// TODO: For a temporary amount of time, we are allowing the use of
|
||||
// DNS providers from go-acme/lego since there are so many implemented
|
||||
// for it -- they are adapted as Caddy modules in this repository:
|
||||
// https://github.com/caddy-dns/lego-deprecated - that module is
|
||||
// a challenge.Provider value, so we use it directly. The user must set
|
||||
// environment variables to configure it. Remove this shim once a sufficient
|
||||
// number of DNS providers are implemented for the libdns APIs instead.
|
||||
if grandfatheredProvider, ok := val.(challenge.Provider); ok {
|
||||
m.Challenges.DNS.provider = grandfatheredProvider
|
||||
|
||||
if deprecatedProvider, ok := val.(acmez.Solver); ok {
|
||||
// TODO: For a temporary amount of time, we are allowing the use of DNS
|
||||
// providers from go-acme/lego since there are so many providers implemented
|
||||
// using that API -- they are adapted as an all-in-one Caddy module in this
|
||||
// repository: https://github.com/caddy-dns/lego-deprecated - the module is a
|
||||
// acmez.Solver type, so we use it directly. The user must set environment
|
||||
// variables to configure it. Remove this shim once a sufficient number of
|
||||
// DNS providers are implemented for the libdns APIs instead.
|
||||
iss.Challenges.DNS.solver = deprecatedProvider
|
||||
} else {
|
||||
m.Challenges.DNS.provider = &solver{
|
||||
recordManager: val.(recordManager),
|
||||
TTL: time.Duration(m.Challenges.DNS.TTL),
|
||||
iss.Challenges.DNS.solver = &certmagic.DNS01Solver{
|
||||
DNSProvider: val.(certmagic.ACMEDNSProvider),
|
||||
TTL: time.Duration(iss.Challenges.DNS.TTL),
|
||||
PropagationTimeout: time.Duration(iss.Challenges.DNS.PropagationTimeout),
|
||||
Resolvers: iss.Challenges.DNS.Resolvers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add any custom CAs to trust store
|
||||
if len(m.TrustedRootsPEMFiles) > 0 {
|
||||
m.rootPool = x509.NewCertPool()
|
||||
for _, pemFile := range m.TrustedRootsPEMFiles {
|
||||
if len(iss.TrustedRootsPEMFiles) > 0 {
|
||||
iss.rootPool = x509.NewCertPool()
|
||||
for _, pemFile := range iss.TrustedRootsPEMFiles {
|
||||
pemData, err := ioutil.ReadFile(pemFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading trusted root CA's PEM file: %s: %v", pemFile, err)
|
||||
}
|
||||
if !m.rootPool.AppendCertsFromPEM(pemData) {
|
||||
if !iss.rootPool.AppendCertsFromPEM(pemData) {
|
||||
return fmt.Errorf("unable to add %s to trust pool: %v", pemFile, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
m.template, err = m.makeIssuerTemplate()
|
||||
iss.template, err = iss.makeIssuerTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -130,38 +141,30 @@ func (m *ACMEIssuer) Provision(ctx caddy.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEManager, error) {
|
||||
func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEManager, error) {
|
||||
template := certmagic.ACMEManager{
|
||||
CA: m.CA,
|
||||
TestCA: m.TestCA,
|
||||
Email: m.Email,
|
||||
CertObtainTimeout: time.Duration(m.ACMETimeout),
|
||||
TrustedRoots: m.rootPool,
|
||||
CA: iss.CA,
|
||||
TestCA: iss.TestCA,
|
||||
Email: iss.Email,
|
||||
CertObtainTimeout: time.Duration(iss.ACMETimeout),
|
||||
TrustedRoots: iss.rootPool,
|
||||
ExternalAccount: iss.ExternalAccount,
|
||||
Logger: iss.logger,
|
||||
}
|
||||
|
||||
if m.ExternalAccount != nil {
|
||||
if m.ExternalAccount.KeyID == "" || m.ExternalAccount.HMAC == "" {
|
||||
return template, fmt.Errorf("when an external account binding is specified, both key ID and HMAC are required")
|
||||
if iss.Challenges != nil {
|
||||
if iss.Challenges.HTTP != nil {
|
||||
template.DisableHTTPChallenge = iss.Challenges.HTTP.Disabled
|
||||
template.AltHTTPPort = iss.Challenges.HTTP.AlternatePort
|
||||
}
|
||||
template.ExternalAccount = &certmagic.ExternalAccountBinding{
|
||||
KeyID: m.ExternalAccount.KeyID,
|
||||
HMAC: m.ExternalAccount.HMAC,
|
||||
if iss.Challenges.TLSALPN != nil {
|
||||
template.DisableTLSALPNChallenge = iss.Challenges.TLSALPN.Disabled
|
||||
template.AltTLSALPNPort = iss.Challenges.TLSALPN.AlternatePort
|
||||
}
|
||||
}
|
||||
|
||||
if m.Challenges != nil {
|
||||
if m.Challenges.HTTP != nil {
|
||||
template.DisableHTTPChallenge = m.Challenges.HTTP.Disabled
|
||||
template.AltHTTPPort = m.Challenges.HTTP.AlternatePort
|
||||
if iss.Challenges.DNS != nil {
|
||||
template.DNS01Solver = iss.Challenges.DNS.solver
|
||||
}
|
||||
if m.Challenges.TLSALPN != nil {
|
||||
template.DisableTLSALPNChallenge = m.Challenges.TLSALPN.Disabled
|
||||
template.AltTLSALPNPort = m.Challenges.TLSALPN.AlternatePort
|
||||
}
|
||||
if m.Challenges.DNS != nil {
|
||||
template.DNSProvider = m.Challenges.DNS.provider
|
||||
}
|
||||
template.ListenHost = m.Challenges.BindHost
|
||||
template.ListenHost = iss.Challenges.BindHost
|
||||
}
|
||||
|
||||
return template, nil
|
||||
@@ -171,8 +174,8 @@ func (m *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEManager, error) {
|
||||
// This is required because ACME needs values from the config in
|
||||
// order to solve the challenges during issuance. This implements
|
||||
// the ConfigSetter interface.
|
||||
func (m *ACMEIssuer) SetConfig(cfg *certmagic.Config) {
|
||||
m.magic = cfg
|
||||
func (iss *ACMEIssuer) SetConfig(cfg *certmagic.Config) {
|
||||
iss.magic = cfg
|
||||
}
|
||||
|
||||
// TODO: I kind of hate how each call to these methods needs to
|
||||
@@ -180,23 +183,185 @@ func (m *ACMEIssuer) SetConfig(cfg *certmagic.Config) {
|
||||
// we find the right place to do that just once and then re-use?
|
||||
|
||||
// PreCheck implements the certmagic.PreChecker interface.
|
||||
func (m *ACMEIssuer) PreCheck(names []string, interactive bool) error {
|
||||
return certmagic.NewACMEManager(m.magic, m.template).PreCheck(names, interactive)
|
||||
func (iss *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error {
|
||||
return certmagic.NewACMEManager(iss.magic, iss.template).PreCheck(ctx, names, interactive)
|
||||
}
|
||||
|
||||
// Issue obtains a certificate for the given csr.
|
||||
func (m *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||
return certmagic.NewACMEManager(m.magic, m.template).Issue(ctx, csr)
|
||||
func (iss *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||
return certmagic.NewACMEManager(iss.magic, iss.template).Issue(ctx, csr)
|
||||
}
|
||||
|
||||
// IssuerKey returns the unique issuer key for the configured CA endpoint.
|
||||
func (m *ACMEIssuer) IssuerKey() string {
|
||||
return certmagic.NewACMEManager(m.magic, m.template).IssuerKey()
|
||||
func (iss *ACMEIssuer) IssuerKey() string {
|
||||
return certmagic.NewACMEManager(iss.magic, iss.template).IssuerKey()
|
||||
}
|
||||
|
||||
// Revoke revokes the given certificate.
|
||||
func (m *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource) error {
|
||||
return certmagic.NewACMEManager(m.magic, m.template).Revoke(ctx, cert)
|
||||
func (iss *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource, reason int) error {
|
||||
return certmagic.NewACMEManager(iss.magic, iss.template).Revoke(ctx, cert, reason)
|
||||
}
|
||||
|
||||
// GetACMEIssuer returns iss. This is useful when other types embed ACMEIssuer, because
|
||||
// type-asserting them to *ACMEIssuer will fail, but type-asserting them to an interface
|
||||
// with only this method will succeed, and will still allow the embedded ACMEIssuer
|
||||
// to be accessed and manipulated.
|
||||
func (iss *ACMEIssuer) GetACMEIssuer() *ACMEIssuer { return iss }
|
||||
|
||||
// UnmarshalCaddyfile deserializes Caddyfile tokens into iss.
|
||||
//
|
||||
// ... acme {
|
||||
// dir <directory_url>
|
||||
// test_dir <test_directory_url>
|
||||
// email <email>
|
||||
// timeout <duration>
|
||||
// disable_http_challenge
|
||||
// disable_tlsalpn_challenge
|
||||
// alt_http_port <port>
|
||||
// alt_tlsalpn_port <port>
|
||||
// eab <key_id> <mac_key>
|
||||
// trusted_roots <pem_files...>
|
||||
// dns <provider_name> [<options>]
|
||||
// resolvers <dns_servers...>
|
||||
// }
|
||||
//
|
||||
func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "dir":
|
||||
if !d.AllArgs(&iss.CA) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "test_dir":
|
||||
if !d.AllArgs(&iss.TestCA) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "email":
|
||||
if !d.AllArgs(&iss.Email) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "timeout":
|
||||
var timeoutStr string
|
||||
if !d.AllArgs(&timeoutStr) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
timeout, err := caddy.ParseDuration(timeoutStr)
|
||||
if err != nil {
|
||||
return d.Errf("invalid timeout duration %s: %v", timeoutStr, err)
|
||||
}
|
||||
iss.ACMETimeout = caddy.Duration(timeout)
|
||||
|
||||
case "disable_http_challenge":
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if iss.Challenges == nil {
|
||||
iss.Challenges = new(ChallengesConfig)
|
||||
}
|
||||
if iss.Challenges.HTTP == nil {
|
||||
iss.Challenges.HTTP = new(HTTPChallengeConfig)
|
||||
}
|
||||
iss.Challenges.HTTP.Disabled = true
|
||||
|
||||
case "disable_tlsalpn_challenge":
|
||||
if d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
if iss.Challenges == nil {
|
||||
iss.Challenges = new(ChallengesConfig)
|
||||
}
|
||||
if iss.Challenges.TLSALPN == nil {
|
||||
iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig)
|
||||
}
|
||||
iss.Challenges.TLSALPN.Disabled = true
|
||||
|
||||
case "alt_http_port":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
port, err := strconv.Atoi(d.Val())
|
||||
if err != nil {
|
||||
return d.Errf("invalid port %s: %v", d.Val(), err)
|
||||
}
|
||||
if iss.Challenges == nil {
|
||||
iss.Challenges = new(ChallengesConfig)
|
||||
}
|
||||
if iss.Challenges.HTTP == nil {
|
||||
iss.Challenges.HTTP = new(HTTPChallengeConfig)
|
||||
}
|
||||
iss.Challenges.HTTP.AlternatePort = port
|
||||
|
||||
case "alt_tlsalpn_port":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
port, err := strconv.Atoi(d.Val())
|
||||
if err != nil {
|
||||
return d.Errf("invalid port %s: %v", d.Val(), err)
|
||||
}
|
||||
if iss.Challenges == nil {
|
||||
iss.Challenges = new(ChallengesConfig)
|
||||
}
|
||||
if iss.Challenges.TLSALPN == nil {
|
||||
iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig)
|
||||
}
|
||||
iss.Challenges.TLSALPN.AlternatePort = port
|
||||
|
||||
case "eab":
|
||||
iss.ExternalAccount = new(acme.EAB)
|
||||
if !d.AllArgs(&iss.ExternalAccount.KeyID, &iss.ExternalAccount.MACKey) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
case "trusted_roots":
|
||||
iss.TrustedRootsPEMFiles = d.RemainingArgs()
|
||||
|
||||
case "dns":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
provName := d.Val()
|
||||
if iss.Challenges == nil {
|
||||
iss.Challenges = new(ChallengesConfig)
|
||||
}
|
||||
if iss.Challenges.DNS == nil {
|
||||
iss.Challenges.DNS = new(DNSChallengeConfig)
|
||||
}
|
||||
dnsProvModule, err := caddy.GetModule("dns.providers." + provName)
|
||||
if err != nil {
|
||||
return d.Errf("getting DNS provider module named '%s': %v", provName, err)
|
||||
}
|
||||
dnsProvModuleInstance := dnsProvModule.New()
|
||||
if unm, ok := dnsProvModuleInstance.(caddyfile.Unmarshaler); ok {
|
||||
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
iss.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(dnsProvModuleInstance, "name", provName, nil)
|
||||
|
||||
case "resolvers":
|
||||
if iss.Challenges == nil {
|
||||
iss.Challenges = new(ChallengesConfig)
|
||||
}
|
||||
if iss.Challenges.DNS == nil {
|
||||
iss.Challenges.DNS = new(DNSChallengeConfig)
|
||||
}
|
||||
iss.Challenges.DNS.Resolvers = d.RemainingArgs()
|
||||
if len(iss.Challenges.DNS.Resolvers) == 0 {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
default:
|
||||
return d.Errf("unrecognized ACME issuer property: %s", d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// onDemandAskRequest makes a request to the ask URL
|
||||
@@ -227,21 +392,12 @@ func onDemandAskRequest(ask string, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExternalAccountBinding contains information for
|
||||
// binding an external account to an ACME account.
|
||||
type ExternalAccountBinding struct {
|
||||
// The key identifier.
|
||||
KeyID string `json:"key_id,omitempty"`
|
||||
|
||||
// The HMAC.
|
||||
HMAC string `json:"hmac,omitempty"`
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ certmagic.PreChecker = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Issuer = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Revoker = (*ACMEIssuer)(nil)
|
||||
_ caddy.Provisioner = (*ACMEIssuer)(nil)
|
||||
_ ConfigSetter = (*ACMEIssuer)(nil)
|
||||
_ certmagic.PreChecker = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Issuer = (*ACMEIssuer)(nil)
|
||||
_ certmagic.Revoker = (*ACMEIssuer)(nil)
|
||||
_ caddy.Provisioner = (*ACMEIssuer)(nil)
|
||||
_ ConfigSetter = (*ACMEIssuer)(nil)
|
||||
_ caddyfile.Unmarshaler = (*ACMEIssuer)(nil)
|
||||
)
|
||||
|
||||
@@ -22,12 +22,11 @@ import (
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/mholt/acmez"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AutomationConfig designates configuration for the
|
||||
// construction and use of ACME clients.
|
||||
// AutomationConfig governs the automated management of TLS certificates.
|
||||
type AutomationConfig struct {
|
||||
// The list of automation policies. The first matching
|
||||
// policy will be applied for a given certificate/name.
|
||||
@@ -208,6 +207,7 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error {
|
||||
OnDemand: ond,
|
||||
Storage: storage,
|
||||
Issuer: ap.Issuer, // if nil, certmagic.New() will create one
|
||||
Logger: tlsApp.logger,
|
||||
}
|
||||
if rev, ok := ap.Issuer.(certmagic.Revoker); ok {
|
||||
template.Revoker = rev
|
||||
@@ -244,6 +244,7 @@ type ChallengesConfig struct {
|
||||
// not enabled by default. This is the only challenge
|
||||
// type which does not require a direct connection
|
||||
// to Caddy from an external server.
|
||||
//
|
||||
// NOTE: DNS providers are currently being upgraded,
|
||||
// and this API is subject to change, but should be
|
||||
// stabilized soon.
|
||||
@@ -281,6 +282,7 @@ type TLSALPNChallengeConfig struct {
|
||||
}
|
||||
|
||||
// DNSChallengeConfig configures the ACME DNS challenge.
|
||||
//
|
||||
// NOTE: This API is still experimental and is subject to change.
|
||||
type DNSChallengeConfig struct {
|
||||
// The DNS provider module to use which will manage
|
||||
@@ -290,7 +292,14 @@ type DNSChallengeConfig struct {
|
||||
// The TTL of the TXT record used for the DNS challenge.
|
||||
TTL caddy.Duration `json:"ttl,omitempty"`
|
||||
|
||||
provider challenge.Provider
|
||||
// How long to wait for DNS record to propagate.
|
||||
PropagationTimeout caddy.Duration `json:"propagation_timeout,omitempty"`
|
||||
|
||||
// Custom DNS resolvers to prefer over system/built-in defaults.
|
||||
// Often necessary to configure when using split-horizon DNS.
|
||||
Resolvers []string `json:"resolvers,omitempty"`
|
||||
|
||||
solver acmez.Solver
|
||||
}
|
||||
|
||||
// OnDemandConfig configures on-demand TLS, for obtaining
|
||||
|
||||
@@ -22,12 +22,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/go-acme/lego/v3/challenge/tlsalpn01"
|
||||
"github.com/mholt/acmez"
|
||||
)
|
||||
|
||||
// ConnectionPolicies is an ordered group of connection policies;
|
||||
// the first matching policy will be used to configure TLS
|
||||
// connections at handshake-time.
|
||||
// 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.
|
||||
type ConnectionPolicies []*ConnectionPolicy
|
||||
|
||||
// Provision sets up each connection policy. It should be called
|
||||
@@ -229,13 +229,13 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error {
|
||||
// ensure ALPN includes the ACME TLS-ALPN protocol
|
||||
var alpnFound bool
|
||||
for _, a := range p.ALPN {
|
||||
if a == tlsalpn01.ACMETLS1Protocol {
|
||||
if a == acmez.ACMETLS1Protocol {
|
||||
alpnFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alpnFound {
|
||||
cfg.NextProtos = append(cfg.NextProtos, tlsalpn01.ACMETLS1Protocol)
|
||||
cfg.NextProtos = append(cfg.NextProtos, acmez.ACMETLS1Protocol)
|
||||
}
|
||||
|
||||
// min and max protocol versions
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
// 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 caddytls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v3/challenge"
|
||||
"github.com/go-acme/lego/v3/challenge/dns01"
|
||||
"github.com/libdns/libdns"
|
||||
)
|
||||
|
||||
// TODO: this is borrowed from https://github.com/mholt/acme - once we
|
||||
// switch to that acme library, this file will go away
|
||||
|
||||
// solver is a type that makes libdns providers usable as ACME challenge solvers.
|
||||
type solver struct {
|
||||
recordManager
|
||||
|
||||
TTL time.Duration
|
||||
|
||||
txtRecords map[string]libdns.Record // keyed by challenge token
|
||||
txtRecordsMu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *solver) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
rec := libdns.Record{
|
||||
Type: "TXT",
|
||||
Name: fqdn,
|
||||
Value: value,
|
||||
TTL: s.TTL,
|
||||
}
|
||||
|
||||
zone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine zone for domain %q: %v", fqdn, err)
|
||||
}
|
||||
|
||||
results, err := s.recordManager.AppendRecords(context.TODO(), zone, []libdns.Record{rec})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) != 1 {
|
||||
return fmt.Errorf("expected one record, got %d: %v", len(results), results)
|
||||
}
|
||||
|
||||
// keep this record handy so we can clean it up more efficiently
|
||||
s.txtRecordsMu.Lock()
|
||||
if s.txtRecords == nil {
|
||||
s.txtRecords = make(map[string]libdns.Record)
|
||||
}
|
||||
s.txtRecords[keyAuth] = results[0]
|
||||
s.txtRecordsMu.Unlock()
|
||||
|
||||
// TODO: check for record propagation before continuing (accordig to config)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *solver) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// retrieve the record we created
|
||||
s.txtRecordsMu.Lock()
|
||||
txtRec, ok := s.txtRecords[keyAuth]
|
||||
if !ok {
|
||||
s.txtRecordsMu.Unlock()
|
||||
return fmt.Errorf("no memory of presenting a DNS record for %v", domain)
|
||||
}
|
||||
s.txtRecordsMu.Unlock()
|
||||
|
||||
// clean up the record
|
||||
_, err = s.recordManager.DeleteRecords(context.TODO(), authZone, []libdns.Record{txtRec})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// once it has been successfully cleaned up, we can forget about it
|
||||
s.txtRecordsMu.Lock()
|
||||
delete(s.txtRecords, keyAuth)
|
||||
s.txtRecordsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// recordManager defines the set of operations required for ACME challenges.
|
||||
type recordManager interface {
|
||||
libdns.RecordAppender
|
||||
libdns.RecordDeleter
|
||||
}
|
||||
|
||||
var _ challenge.Provider = (*solver)(nil)
|
||||
@@ -23,10 +23,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddypki"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/cli/crypto/x509util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -63,25 +63,25 @@ func (InternalIssuer) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
|
||||
// Provision sets up the issuer.
|
||||
func (li *InternalIssuer) Provision(ctx caddy.Context) error {
|
||||
func (iss *InternalIssuer) Provision(ctx caddy.Context) error {
|
||||
// get a reference to the configured CA
|
||||
appModule, err := ctx.App("pki")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkiApp := appModule.(*caddypki.PKI)
|
||||
if li.CA == "" {
|
||||
li.CA = caddypki.DefaultCAID
|
||||
if iss.CA == "" {
|
||||
iss.CA = caddypki.DefaultCAID
|
||||
}
|
||||
ca, ok := pkiApp.CAs[li.CA]
|
||||
ca, ok := pkiApp.CAs[iss.CA]
|
||||
if !ok {
|
||||
return fmt.Errorf("no certificate authority configured with id: %s", li.CA)
|
||||
return fmt.Errorf("no certificate authority configured with id: %s", iss.CA)
|
||||
}
|
||||
li.ca = ca
|
||||
iss.ca = ca
|
||||
|
||||
// set any other default values
|
||||
if li.Lifetime == 0 {
|
||||
li.Lifetime = caddy.Duration(defaultInternalCertLifetime)
|
||||
if iss.Lifetime == 0 {
|
||||
iss.Lifetime = caddy.Duration(defaultInternalCertLifetime)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -89,39 +89,37 @@ func (li *InternalIssuer) Provision(ctx caddy.Context) error {
|
||||
|
||||
// IssuerKey returns the unique issuer key for the
|
||||
// confgured CA endpoint.
|
||||
func (li InternalIssuer) IssuerKey() string {
|
||||
return li.ca.ID()
|
||||
func (iss InternalIssuer) IssuerKey() string {
|
||||
return iss.ca.ID()
|
||||
}
|
||||
|
||||
// Issue issues a certificate to satisfy the CSR.
|
||||
func (li InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||
func (iss InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||
// prepare the signing authority
|
||||
authCfg := caddypki.AuthorityConfig{
|
||||
SignWithRoot: li.SignWithRoot,
|
||||
SignWithRoot: iss.SignWithRoot,
|
||||
}
|
||||
auth, err := li.ca.NewAuthority(authCfg)
|
||||
auth, err := iss.ca.NewAuthority(authCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get the cert (public key) that will be used for signing
|
||||
var issuerCert *x509.Certificate
|
||||
if li.SignWithRoot {
|
||||
issuerCert = li.ca.RootCertificate()
|
||||
if iss.SignWithRoot {
|
||||
issuerCert = iss.ca.RootCertificate()
|
||||
} else {
|
||||
issuerCert = li.ca.IntermediateCertificate()
|
||||
issuerCert = iss.ca.IntermediateCertificate()
|
||||
}
|
||||
|
||||
// ensure issued certificate does not expire later than its issuer
|
||||
lifetime := time.Duration(li.Lifetime)
|
||||
lifetime := time.Duration(iss.Lifetime)
|
||||
if time.Now().Add(lifetime).After(issuerCert.NotAfter) {
|
||||
// TODO: log this
|
||||
lifetime = issuerCert.NotAfter.Sub(time.Now())
|
||||
}
|
||||
|
||||
certChain, err := auth.Sign(csr, provisioner.Options{},
|
||||
profileDefaultDuration(li.Lifetime),
|
||||
)
|
||||
certChain, err := auth.Sign(csr, provisioner.SignOptions{}, customCertLifetime(iss.Lifetime))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -139,33 +137,34 @@ func (li InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest
|
||||
}, nil
|
||||
}
|
||||
|
||||
// profileDefaultDuration is a wrapper against x509util.WithOption to conform
|
||||
// the SignOption interface.
|
||||
// UnmarshalCaddyfile deserializes Caddyfile tokens into iss.
|
||||
//
|
||||
// This type is borrowed from the smallstep libraries:
|
||||
// https://github.com/smallstep/certificates/blob/806abb6232a5691198b891d76b9898ea7f269da0/authority/provisioner/sign_options.go#L191-L211
|
||||
// as per https://github.com/smallstep/certificates/issues/198.
|
||||
// ... internal {
|
||||
// ca <name>
|
||||
// }
|
||||
//
|
||||
// TODO: In the future, this approach to custom cert lifetimes may not be necessary
|
||||
type profileDefaultDuration time.Duration
|
||||
|
||||
func (d profileDefaultDuration) Option(so provisioner.Options) x509util.WithOption {
|
||||
var backdate time.Duration
|
||||
notBefore := so.NotBefore.Time()
|
||||
if notBefore.IsZero() {
|
||||
notBefore = time.Now().Truncate(time.Second)
|
||||
backdate = -1 * so.Backdate
|
||||
}
|
||||
notAfter := so.NotAfter.RelativeTime(notBefore)
|
||||
return func(p x509util.Profile) error {
|
||||
fn := x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(d))
|
||||
if err := fn(p); err != nil {
|
||||
return err
|
||||
func (iss *InternalIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
for d.NextBlock(0) {
|
||||
switch d.Val() {
|
||||
case "ca":
|
||||
if !d.AllArgs(&iss.CA) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
}
|
||||
}
|
||||
crt := p.Subject()
|
||||
crt.NotBefore = crt.NotBefore.Add(backdate)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// customCertLifetime allows us to customize certificates that are issued
|
||||
// by Smallstep libs, particularly the NotBefore & NotAfter dates.
|
||||
type customCertLifetime time.Duration
|
||||
|
||||
func (d customCertLifetime) Modify(cert *x509.Certificate, _ provisioner.SignOptions) error {
|
||||
cert.NotBefore = time.Now()
|
||||
cert.NotAfter = cert.NotBefore.Add(time.Duration(d))
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -174,6 +173,7 @@ const (
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*InternalIssuer)(nil)
|
||||
_ certmagic.Issuer = (*InternalIssuer)(nil)
|
||||
_ caddy.Provisioner = (*InternalIssuer)(nil)
|
||||
_ certmagic.Issuer = (*InternalIssuer)(nil)
|
||||
_ provisioner.CertificateModifier = (*customCertLifetime)(nil)
|
||||
)
|
||||
|
||||
+15
-8
@@ -87,6 +87,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
||||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
||||
return t.getConfigForName(cert.Names[0]), nil
|
||||
},
|
||||
Logger: t.logger.Named("cache"),
|
||||
}
|
||||
if t.Automation != nil {
|
||||
cacheOpts.OCSPCheckInterval = time.Duration(t.Automation.OCSPCheckInterval)
|
||||
@@ -95,6 +96,9 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
||||
if t.Cache != nil {
|
||||
cacheOpts.Capacity = t.Cache.Capacity
|
||||
}
|
||||
if cacheOpts.Capacity <= 0 {
|
||||
cacheOpts.Capacity = 10000
|
||||
}
|
||||
t.certCache = certmagic.NewCache(cacheOpts)
|
||||
|
||||
// certificate loaders
|
||||
@@ -172,6 +176,7 @@ func (t *TLS) Provision(ctx caddy.Context) error {
|
||||
// commands like validate can be a better test
|
||||
magic := certmagic.New(t.certCache, certmagic.Config{
|
||||
Storage: ctx.Storage(),
|
||||
Logger: t.logger,
|
||||
})
|
||||
for _, loader := range t.certificateLoaders {
|
||||
certs, err := loader.LoadCertificates()
|
||||
@@ -308,8 +313,10 @@ func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
|
||||
if ap.magic.Issuer == nil {
|
||||
return false
|
||||
}
|
||||
if am, ok := ap.magic.Issuer.(*ACMEIssuer); ok {
|
||||
return certmagic.NewACMEManager(am.magic, am.template).HandleHTTPChallenge(w, r)
|
||||
type acmeCapable interface{ GetACMEIssuer() *ACMEIssuer }
|
||||
if am, ok := ap.magic.Issuer.(acmeCapable); ok {
|
||||
iss := am.GetACMEIssuer()
|
||||
return certmagic.NewACMEManager(iss.magic, iss.template).HandleHTTPChallenge(w, r)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -373,9 +380,9 @@ func (t *TLS) AllMatchingCertificates(san string) []certmagic.Certificate {
|
||||
return t.certCache.AllMatchingCertificates(san)
|
||||
}
|
||||
|
||||
// keepStorageClean immediately cleans up all known storage units
|
||||
// if it was not recently done, and starts a goroutine that runs
|
||||
// the operation at every tick from t.storageCleanTicker.
|
||||
// keepStorageClean starts a goroutine that immediately cleans up all
|
||||
// known storage units if it was not recently done, and then runs the
|
||||
// operation at every tick from t.storageCleanTicker.
|
||||
func (t *TLS) keepStorageClean() {
|
||||
t.storageCleanTicker = time.NewTicker(storageCleanInterval)
|
||||
t.storageCleanStop = make(chan struct{})
|
||||
@@ -385,6 +392,7 @@ func (t *TLS) keepStorageClean() {
|
||||
log.Printf("[PANIC] storage cleaner: %v\n%s", err, debug.Stack())
|
||||
}
|
||||
}()
|
||||
t.cleanStorageUnits()
|
||||
for {
|
||||
select {
|
||||
case <-t.storageCleanStop:
|
||||
@@ -394,7 +402,6 @@ func (t *TLS) keepStorageClean() {
|
||||
}
|
||||
}
|
||||
}()
|
||||
t.cleanStorageUnits()
|
||||
}
|
||||
|
||||
func (t *TLS) cleanStorageUnits() {
|
||||
@@ -412,13 +419,13 @@ func (t *TLS) cleanStorageUnits() {
|
||||
}
|
||||
|
||||
// start with the default storage
|
||||
certmagic.CleanStorage(t.ctx.Storage(), options)
|
||||
certmagic.CleanStorage(t.ctx, t.ctx.Storage(), options)
|
||||
|
||||
// then clean each storage defined in ACME automation policies
|
||||
if t.Automation != nil {
|
||||
for _, ap := range t.Automation.Policies {
|
||||
if ap.storage != nil {
|
||||
certmagic.CleanStorage(ap.storage, options)
|
||||
certmagic.CleanStorage(t.ctx, ap.storage, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
// 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 caddytls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(new(ZeroSSLIssuer))
|
||||
}
|
||||
|
||||
// ZeroSSLIssuer makes an ACME manager
|
||||
// for managing certificates using ACME.
|
||||
type ZeroSSLIssuer struct {
|
||||
*ACMEIssuer
|
||||
|
||||
// The API key (or "access key") for using the ZeroSSL API.
|
||||
APIKey string `json:"api_key,omitempty"`
|
||||
|
||||
mu sync.Mutex
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (*ZeroSSLIssuer) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "tls.issuance.zerossl",
|
||||
New: func() caddy.Module { return new(ZeroSSLIssuer) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up iss.
|
||||
func (iss *ZeroSSLIssuer) Provision(ctx caddy.Context) error {
|
||||
iss.logger = ctx.Logger(iss)
|
||||
|
||||
if iss.ACMEIssuer == nil {
|
||||
iss.ACMEIssuer = new(ACMEIssuer)
|
||||
}
|
||||
err := iss.ACMEIssuer.Provision(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iss *ZeroSSLIssuer) newAccountCallback(ctx context.Context, am *certmagic.ACMEManager, _ acme.Account) error {
|
||||
if am.ExternalAccount != nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
am.ExternalAccount, err = iss.generateEABCredentials(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (iss *ZeroSSLIssuer) generateEABCredentials(ctx context.Context) (*acme.EAB, error) {
|
||||
var endpoint string
|
||||
var body io.Reader
|
||||
|
||||
// there are two ways to generate EAB credentials: authenticated with
|
||||
// their API key, or unauthenticated with their email address
|
||||
switch {
|
||||
case iss.APIKey != "":
|
||||
apiKey := caddy.NewReplacer().ReplaceAll(iss.APIKey, "")
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("missing API key: '%v'", iss.APIKey)
|
||||
}
|
||||
qs := url.Values{"access_key": []string{apiKey}}
|
||||
endpoint = fmt.Sprintf("%s/eab-credentials?%s", zerosslAPIBase, qs.Encode())
|
||||
|
||||
case iss.Email != "":
|
||||
email := caddy.NewReplacer().ReplaceAll(iss.Email, "")
|
||||
if email == "" {
|
||||
return nil, fmt.Errorf("missing email: '%v'", iss.Email)
|
||||
}
|
||||
endpoint = zerosslAPIBase + "/eab-credentials-email"
|
||||
form := url.Values{"email": []string{email}}
|
||||
body = strings.NewReader(form.Encode())
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("must configure either an API key or email address to use ZeroSSL without explicit EAB")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("forming request: %v", err)
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
req.Header.Set("User-Agent", certmagic.UserAgent)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("performing EAB credentials request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result struct {
|
||||
Success bool `json:"success"`
|
||||
Error struct {
|
||||
Code int `json:"code"`
|
||||
Type string `json:"type"`
|
||||
} `json:"error"`
|
||||
EABKID string `json:"eab_kid"`
|
||||
EABHMACKey string `json:"eab_hmac_key"`
|
||||
}
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding API response: %v", err)
|
||||
}
|
||||
if result.Error.Code != 0 {
|
||||
return nil, fmt.Errorf("failed getting EAB credentials: HTTP %d: %s (code %d)",
|
||||
resp.StatusCode, result.Error.Type, result.Error.Code)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed getting EAB credentials: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
iss.logger.Info("generated EAB credentials", zap.String("key_id", result.EABKID))
|
||||
|
||||
return &acme.EAB{
|
||||
KeyID: result.EABKID,
|
||||
MACKey: result.EABHMACKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// initialize modifies the template for the underlying ACMEManager
|
||||
// values by setting the CA endpoint to the ZeroSSL directory and
|
||||
// setting the NewAccountFunc callback to one which allows us to
|
||||
// generate EAB credentials only if a new account is being made.
|
||||
// Since it modifies the stored template, its effect should only
|
||||
// be needed once, but it is fine to call it repeatedly.
|
||||
func (iss *ZeroSSLIssuer) initialize() {
|
||||
iss.mu.Lock()
|
||||
defer iss.mu.Unlock()
|
||||
if iss.template.CA == "" {
|
||||
iss.template.CA = zerosslACMEDirectory
|
||||
}
|
||||
if iss.template.NewAccountFunc == nil {
|
||||
iss.template.NewAccountFunc = iss.newAccountCallback
|
||||
}
|
||||
}
|
||||
|
||||
// PreCheck implements the certmagic.PreChecker interface.
|
||||
func (iss *ZeroSSLIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error {
|
||||
iss.initialize()
|
||||
return iss.ACMEIssuer.PreCheck(ctx, names, interactive)
|
||||
}
|
||||
|
||||
// Issue obtains a certificate for the given csr.
|
||||
func (iss *ZeroSSLIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
|
||||
iss.initialize()
|
||||
return iss.ACMEIssuer.Issue(ctx, csr)
|
||||
}
|
||||
|
||||
// IssuerKey returns the unique issuer key for the configured CA endpoint.
|
||||
func (iss *ZeroSSLIssuer) IssuerKey() string {
|
||||
iss.initialize()
|
||||
return iss.ACMEIssuer.IssuerKey()
|
||||
}
|
||||
|
||||
// Revoke revokes the given certificate.
|
||||
func (iss *ZeroSSLIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource, reason int) error {
|
||||
iss.initialize()
|
||||
return iss.ACMEIssuer.Revoke(ctx, cert, reason)
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile deserializes Caddyfile tokens into iss.
|
||||
//
|
||||
// ... zerossl <api_key> {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Any of the subdirectives for the ACME issuer can be used in the block.
|
||||
func (iss *ZeroSSLIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
if !d.AllArgs(&iss.APIKey) {
|
||||
return d.ArgErr()
|
||||
}
|
||||
|
||||
if iss.ACMEIssuer == nil {
|
||||
iss.ACMEIssuer = new(ACMEIssuer)
|
||||
}
|
||||
err := iss.ACMEIssuer.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
zerosslACMEDirectory = "https://acme.zerossl.com/v2/DV90"
|
||||
zerosslAPIBase = "https://api.zerossl.com/acme"
|
||||
)
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ certmagic.PreChecker = (*ZeroSSLIssuer)(nil)
|
||||
_ certmagic.Issuer = (*ZeroSSLIssuer)(nil)
|
||||
_ certmagic.Revoker = (*ZeroSSLIssuer)(nil)
|
||||
_ caddy.Provisioner = (*ZeroSSLIssuer)(nil)
|
||||
_ ConfigSetter = (*ZeroSSLIssuer)(nil)
|
||||
|
||||
// a type which properly embeds an ACMEIssuer should implement
|
||||
// this interface so it can be treated as an ACMEIssuer
|
||||
_ interface{ GetACMEIssuer() *ACMEIssuer } = (*ZeroSSLIssuer)(nil)
|
||||
)
|
||||
@@ -119,6 +119,13 @@ func (je *JSONEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
|
||||
// LogfmtEncoder encodes log entries as logfmt:
|
||||
// https://www.brandur.org/logfmt
|
||||
//
|
||||
// Note that logfmt does not encode nested structures
|
||||
// properly, so it is not a good fit for most logs.
|
||||
//
|
||||
// ⚠️ DEPRECATED. Do not use. It will eventually be removed
|
||||
// from the standard Caddy modules. For more information,
|
||||
// see https://github.com/caddyserver/caddy/issues/3575.
|
||||
type LogfmtEncoder struct {
|
||||
zapcore.Encoder `json:"-"`
|
||||
LogEncoderConfig
|
||||
@@ -133,7 +140,10 @@ func (LogfmtEncoder) CaddyModule() caddy.ModuleInfo {
|
||||
}
|
||||
|
||||
// Provision sets up the encoder.
|
||||
func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error {
|
||||
func (lfe *LogfmtEncoder) Provision(ctx caddy.Context) error {
|
||||
ctx.Logger(lfe).Warn("the logfmt encoder is DEPRECATED and will soon be removed from the standard modules",
|
||||
zap.String("recommendation", "switch to a log format that isn't broken"),
|
||||
zap.String("more_info", "https://github.com/caddyserver/caddy/issues/3575"))
|
||||
lfe.Encoder = zaplogfmt.NewEncoder(lfe.ZapcoreEncoderConfig())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/buffer"
|
||||
"go.uber.org/zap/zapcore"
|
||||
@@ -94,6 +96,80 @@ func (fe *FilterEncoder) Provision(ctx caddy.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// filter {
|
||||
// wrap <another encoder>
|
||||
// fields {
|
||||
// <field> <filter> {
|
||||
// <filter options>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
func (fe *FilterEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
for d.NextBlock(0) {
|
||||
switch d.Val() {
|
||||
case "wrap":
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
moduleName := d.Val()
|
||||
mod, err := caddy.GetModule("caddy.logging.encoders." + moduleName)
|
||||
if err != nil {
|
||||
return d.Errf("getting log encoder module named '%s': %v", moduleName, err)
|
||||
}
|
||||
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||
if !ok {
|
||||
return d.Errf("log encoder module '%s' is not a Caddyfile unmarshaler", mod)
|
||||
}
|
||||
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc, ok := unm.(zapcore.Encoder)
|
||||
if !ok {
|
||||
return d.Errf("module %s is not a zapcore.Encoder", mod)
|
||||
}
|
||||
fe.WrappedRaw = caddyconfig.JSONModuleObject(enc, "format", moduleName, nil)
|
||||
|
||||
case "fields":
|
||||
for d.NextBlock(1) {
|
||||
field := d.Val()
|
||||
if !d.NextArg() {
|
||||
return d.ArgErr()
|
||||
}
|
||||
filterName := d.Val()
|
||||
mod, err := caddy.GetModule("caddy.logging.encoders.filter." + filterName)
|
||||
if err != nil {
|
||||
return d.Errf("getting log filter module named '%s': %v", filterName, err)
|
||||
}
|
||||
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||
if !ok {
|
||||
return d.Errf("log encoder module '%s' is not a Caddyfile unmarshaler", mod)
|
||||
}
|
||||
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, ok := unm.(LogFieldFilter)
|
||||
if !ok {
|
||||
return d.Errf("module %s is not a LogFieldFilter", mod)
|
||||
}
|
||||
if fe.FieldsRaw == nil {
|
||||
fe.FieldsRaw = make(map[string]json.RawMessage)
|
||||
}
|
||||
fe.FieldsRaw[field] = caddyconfig.JSONModuleObject(f, "filter", filterName, nil)
|
||||
}
|
||||
|
||||
default:
|
||||
return d.Errf("unrecognized subdirective %s", d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddArray is part of the zapcore.ObjectEncoder interface.
|
||||
// Array elements do not get filtered.
|
||||
func (fe FilterEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error {
|
||||
@@ -330,4 +406,5 @@ func (mom logObjectMarshalerWrapper) MarshalLogObject(_ zapcore.ObjectEncoder) e
|
||||
var (
|
||||
_ zapcore.Encoder = (*FilterEncoder)(nil)
|
||||
_ zapcore.ObjectMarshaler = (*logObjectMarshalerWrapper)(nil)
|
||||
_ caddyfile.Unmarshaler = (*FilterEncoder)(nil)
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user