66 Commits
Author SHA1 Message Date
Henrique DiasandGitHub f0ca85e570 docs: add disclaimer 2022-09-26 11:58:28 +02:00
Ethan DavisandGitHub 436a3b05a1 fix: 'modify:false' is respected 2022-01-18 15:17:20 +01:00
Rui SunandGitHub 1a610b17ba feat: add armv7 support 2022-01-18 15:17:01 +01:00
Mohammed Al Sahaf 32613f76cc ci: update Go version to go1.17 2021-10-19 20:52:48 +03:00
Mohammed Al SahafandGitHub 8a8650d9b2 feat: introduce structured, field logging (#87)
* feat: introduce structured, field logging

The logging is currently minimal and only for initial message of listening and user login attempts

* chore: major version upgarde

there was a breaking API change in 8cd6d0a585 for which the git tag was v4.0.0 but the Go SIV was missed. This commit updates the SIV and retracts the defective version of v4.1.0.
2021-10-19 17:45:54 +00:00
5701cbb5b8 Create SECURITY.md (#81)
* Create SECURITY.md

simple instruction for security researchers

* Update SECURITY.md

Co-authored-by: Mohammed Al Sahaf <msaa1990@gmail.com>
2021-07-27 12:01:24 +03:00
9a4b378b32 feat: add source request log (#71)
Co-authored-by: shenyuning <shenyn@hua-cloud.com.cn>
Co-authored-by: sxueck <shenyuning@ctirobot.com>
2021-07-01 15:15:33 +02:00
JustynandGitHub 099479a894 feat: add a config option to bypass mime content type sniffing (#60) 2021-01-14 14:47:33 +00:00
mik2k2andGitHub 8cd6d0a585 feat: per path modify permissions (#57) 2021-01-14 14:43:52 +00:00
mik2k2andGitHub fca4e54839 feat: add support for unix sockets (#58) 2020-12-17 22:14:03 +00:00
Mohammed Al Sahaf da6dd253d5 chore: use fetch-depth to fetch complete history
Fixes the issue of publishing all commit since inception (see: goreleaser/goreleaser#990)
2020-10-10 03:28:00 +03:00
Mohammed Al Sahaf c1a919681b chore: publish releases immediately (not draft) 2020-10-10 03:09:23 +03:00
Mohammed Al Sahaf 5734366c54 Dockerfile: revert 05e995b
The commit 05e995b doesn't work well with goreleaser (see: goreleaser/goreleaser#694), which broke the Docker image build. Reverting the commit should restore sanity in the goreleaser flow.
2020-09-19 01:11:48 +03:00
Mohammed Al Sahaf aab63c0ccc chore: housekeeping 2020-09-19 01:11:23 +03:00
Mohammed Al SahafandGitHub f212531d75 chore: Switch to Github Actions (#53) 2020-09-18 21:27:22 +01:00
Henrique DiasandGitHub 2a39779cff Update config.yml 2020-09-18 07:06:16 +01:00
Mohammed Al SahafandGitHub 34f2336d91 chore: fix circleci release config (#52) 2020-09-18 07:02:51 +01:00
Henrique Dias 6c5420aa5a chore: install go 2020-08-26 08:44:14 +02:00
Henrique Dias 30c210d097 chore: use specific relese image 2020-08-26 08:09:44 +02:00
Henrique Dias f408f7aa6f chore: remove setup_remote_docker 2020-08-26 08:02:41 +02:00
Henrique Dias e9cd29578e use machine build on release 2020-08-26 07:58:11 +02:00
Henrique Dias e0020e8110 chore: update go version and golangci
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2020-08-25 08:24:31 +02:00
pataquetsandHenrique Dias 2078312ec8 Readme.md: Add Docker pulls badge. 2020-08-03 07:53:31 +01:00
pataquetsandHenrique Dias 05e995b11b Dockerfile: make image build from local source code instead of copying outside build. 2020-08-02 07:55:52 +01:00
Shun ZiandGitHub c4d4734095 fix: GET directory fails when non-root prefix configured (#40) 2020-06-27 08:45:33 +01:00
Mohammed Al SahafandGitHub cf267c1006 chore: build the binary statically to fix the docker image (#44) 2020-06-27 08:44:29 +01:00
Henrique DiasandGitHub 72d8e39927 fix(BREAKING): rename webdav/ --> lib/ (#36)
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-11-10 11:30:45 +00:00
Henrique DiasandGitHub 3ef86e8a7f chore: update dependencies (#35)
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-11-10 11:24:55 +00:00
Shun ZiandHenrique Dias 2971f7ed3d feat: add URL prefix(subpath) support (#31)
To adapt to situations such as working behind a reverse proxy dispatching query to different services by URL path prefix
2019-11-08 13:01:42 +00:00
Henrique Dias ab0334f036 chore: add mips(64)(le)
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-07-14 09:57:31 +01:00
Henrique Dias 931f125224 chore: go mod tidy
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-06-11 13:12:22 +01:00
Henrique Dias cd472b26be chore: bump to v2.0.0
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-06-11 13:09:51 +01:00
Steven VandeveldeandHenrique Dias 7358553e69 feat(BREAKING): extend cors functionality (#25) 2019-06-11 13:02:10 +01:00
Henrique DiasandGitHub 764a69cd33 fix: numeric passwords (#24)
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-06-09 13:51:53 +01:00
Henrique Dias d266f1150e fix: auth enabled by default
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-06-09 13:47:28 +01:00
Henrique Dias 76ebaffaef docs: add cors config
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-24 14:49:01 +01:00
Henrique Dias 60f2697615 fix: pass through linters
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-24 14:47:05 +01:00
oskarandHenrique Dias e5b3946388 feat: add support for custom CORS headers 2019-05-24 14:43:22 +01:00
Henrique Dias 8c66f0c585 feat: check basic auth user anyways
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 20:25:36 +01:00
Henrique Dias bd7e2d8158 feat: add arm v
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 20:15:13 +01:00
Henrique Dias 552c72987d fix: ldflags
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 20:11:43 +01:00
Henrique Dias e022bad4bd chore: no more parallell go mods
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 20:07:20 +01:00
Henrique Dias 82b3da92bd chore: one more try
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 20:06:13 +01:00
Henrique Dias 0b1da0c3c4 chore: hopefully fix goreleaser
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 20:01:28 +01:00
Henrique Dias 62ca86bf4e chore: go mod tidy
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 19:58:05 +01:00
Henrique Dias 5afeb0eb60 fix: remove go arm
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 19:50:33 +01:00
Henrique Dias 4bb814bed6 feat: update go.mod and go.sum
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 19:46:27 +01:00
Henrique DiasandGitHub c5f3907994 refactor: fix ci and put files where they belong (#15)
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 19:40:58 +01:00
Henrique Dias e81638120b feat: add gitignore
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 19:01:42 +01:00
Henrique Dias f172f1bca1 refactor: cleanup
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 19:00:58 +01:00
Henrique Dias 24fa8aa228 feat: use viper and more flags
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 19:00:16 +01:00
Henrique Dias 78ceb0e44c feat: warn users will be ignored
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 16:31:03 +01:00
Henrique Dias 5239649127 feat: allow no auth
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 16:29:36 +01:00
Henrique Dias 35fd913321 feat: allow no auth mode
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 16:01:25 +01:00
Henrique Dias dd4a57af2e feat: allow empty passwords
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 15:55:26 +01:00
Henrique Dias a037fe877c docs: update license file
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 13:48:03 +01:00
Henrique Dias 81f873d2de feat: docker images
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 13:46:39 +01:00
Henrique DiasandGitHub 505a10a63d chore: circle CI (#14)
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 12:59:33 +01:00
Henrique DiasandGitHub 61462c1da7 Merge pull request #13 from hacdias/fix/set-depth-header
fix: set depth header
2019-05-12 12:52:56 +01:00
Henrique Dias 21ec95a8db fix: set depth header
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 12:49:55 +01:00
Henrique Dias 8bf2c4eea3 feat: update modules
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-05-12 12:39:57 +01:00
Henrique DiasandGitHub 4199ffc190 Merge pull request #11 from acez/userdata-from-environment
Add support for loading username and password from environment
2019-05-12 12:34:14 +01:00
Henrique DiasandGitHub 7a67f76947 Merge pull request #12 from meeee/go-module
Make webdav a Go module
2019-03-31 12:14:08 +01:00
Michael Frister 088958913c Travis: Use Go 1.12 for Go module support
gometalinter breaks with Go modules, so disable them.
2019-03-31 12:55:24 +02:00
Michael Frister 3e7b39679b Add Go module files 2019-03-31 12:42:49 +02:00
Christian Wilhelm d95c1ec13b Add support for loading username and password from environment 2018-12-30 13:26:12 +01:00
24 changed files with 1530 additions and 467 deletions
+88
View File
@@ -0,0 +1,88 @@
# Used as inspiration: https://github.com/mvdan/github-actions-golang
name: Tests
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
test:
strategy:
# Default is true, cancels jobs for other platforms in the matrix if one fails
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
go: [ '1.16' ]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Checkout code
uses: actions/checkout@v2
- 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: ${{ runner.os }}-${{ matrix.go }}-go-ci-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go }}-go-ci
- name: Get dependencies
run: go get -v -t -d ./...
# To ensure the project compiles, and not just its tests are passing
- name: Build webdav
working-directory: .
env:
CGO_ENABLED: 0
run: go build -trimpath -ldflags="-w -s" -v
- name: Run tests
run: go test -v -race ./...
golangci-lint:
name: runner / golangci-lint
runs-on: ubuntu-latest
steps:
- name: Checkout code into the Go module directory
uses: actions/checkout@v2
- name: Run golangci-lint
uses: reviewdog/action-golangci-lint@v1
with:
github_token: ${{ secrets.github_token }}
goreleaser-check:
# goreleaser --rm-dist --skip-validate --skip-publish
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: check
env:
TAG: ${{ steps.vars.outputs.version_tag }}
+83
View File
@@ -0,0 +1,83 @@
name: Release
on:
push:
tags:
- 'v*.*.*'
jobs:
release:
name: Release
strategy:
matrix:
os: [ ubuntu-latest ]
go: [ '1.17' ]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
- 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=short_sha::$(git rev-parse --short HEAD)"
echo "::set-output name=go_cache::$(go env GOCACHE)"
# Parse semver
TAG=${GITHUB_REF/refs\/tags\//}
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
echo "::set-output name=tag_major::${TAG_MAJOR}"
echo "::set-output name=tag_minor::${TAG_MINOR}"
echo "::set-output name=tag_patch::${TAG_PATCH}"
echo "::set-output name=tag_special::${TAG_SPECIAL}"
- name: Cache the build cache
uses: actions/cache@v2
with:
path: ${{ steps.vars.outputs.go_cache }}
key: ${{ runner.os }}-go${{ matrix.go }}-release-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go${{ matrix.go }}-release
- name: Docker Login
if: success() && startsWith(github.ref, 'refs/tags/v')
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
# GoReleaser will take care of publishing those artifacts into the release
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+2 -3
View File
@@ -1,3 +1,2 @@
config.yaml
config.yml
config.json
dist/
webdav
+89 -7
View File
@@ -1,6 +1,18 @@
env:
- GO111MODULE=on
- CGO_ENABLED=0
before:
hooks:
- go mod download
build:
main: cmd/webdav/main.go
main: main.go
binary: webdav
flags:
- -trimpath
ldflags:
- -s -w -X github.com/hacdias/webdav/cmd.version={{.Version}}
goos:
- darwin
- linux
@@ -13,6 +25,14 @@ build:
- 386
- arm
- arm64
- mips
- mipsle
- mips64
- mips64le
goarm:
- 5
- 6
- 7
ignore:
- goos: openbsd
goarch: arm
@@ -21,9 +41,71 @@ build:
goarch: arm
goarm: 6
archive:
name_template: "{{.Os}}-{{.Arch}}-{{ .ProjectName }}"
format: tar.gz
format_overrides:
- goos: windows
format: zip
archives:
- name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
format: tar.gz
format_overrides:
- goos: windows
format: zip
release:
github:
owner: hacdias
name: webdav
draft: false
prerelease: auto
dockers:
- goos: linux
goarch: amd64
goarm: ""
use: buildx
image_templates:
- "hacdias/webdav:amd64-latest"
- "hacdias/webdav:amd64-{{ .Tag }}"
- "hacdias/webdav:amd64-v{{ .Major }}.{{ .Minor }}"
- "hacdias/webdav:amd64-v{{ .Major }}"
- goos: linux
goarch: arm
goarm: 7
use: buildx
build_flag_templates:
- "--platform=linux/arm/v7"
image_templates:
- "hacdias/webdav:armv7-latest"
- "hacdias/webdav:armv7-{{ .Tag }}"
- "hacdias/webdav:armv7-v{{ .Major }}.{{ .Minor }}"
- "hacdias/webdav:armv7-v{{ .Major }}"
- goos: linux
goarch: arm64
goarm: ""
use: buildx
build_flag_templates:
- "--platform=linux/arm64"
image_templates:
- "hacdias/webdav:arm64-latest"
- "hacdias/webdav:arm64-{{ .Tag }}"
- "hacdias/webdav:arm64-v{{ .Major }}.{{ .Minor }}"
- "hacdias/webdav:arm64-v{{ .Major }}"
docker_manifests:
- name_template: hacdias/webdav:latest
image_templates:
- hacdias/webdav:amd64-latest
- hacdias/webdav:armv7-latest
- hacdias/webdav:arm64-latest
- name_template: hacdias/webdav:{{ .Tag }}
image_templates:
- hacdias/webdav:amd64-{{ .Tag }}
- hacdias/webdav:armv7-{{ .Tag }}
- hacdias/webdav:arm64-{{ .Tag }}
- name_template: hacdias/webdav:v{{ .Major }}.{{ .Minor }}
image_templates:
- hacdias/webdav:amd64-v{{ .Major }}.{{ .Minor }}
- hacdias/webdav:armv7-v{{ .Major }}.{{ .Minor }}
- hacdias/webdav:arm64-v{{ .Major }}.{{ .Minor }}
- name_template: hacdias/webdav:v{{ .Major }}
image_templates:
- hacdias/webdav:amd64-v{{ .Major }}
- hacdias/webdav:armv7-v{{ .Major }}
- hacdias/webdav:arm64-v{{ .Major }}
-19
View File
@@ -1,19 +0,0 @@
language: go
go: 1.10.x
env:
- "PATH=/home/travis/gopath/bin:$PATH"
install:
- go get ./...
# Install gometalinter and certain linters
- go get github.com/alecthomas/gometalinter
- gometalinter --install
script:
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --tests ./...
- go test ./... -timeout 30s
after_success:
- test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash
+10
View File
@@ -0,0 +1,10 @@
FROM alpine:latest as certs
RUN apk --update add ca-certificates
FROM scratch
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 80
COPY webdav /webdav
ENTRYPOINT [ "/webdav" ]
View File
+95
View File
@@ -0,0 +1,95 @@
> ⚠️ Disclaimer: this repository is not actively maintained. If you are interested in maintaining it, please [contact me](https://github.com/hacdias/webdav/issues/144).
# webdav
![Build](https://github.com/hacdias/webdav/workflows/Tests/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/hacdias/webdav?style=flat-square)](https://goreportcard.com/report/hacdias/webdav)
[![Version](https://img.shields.io/github/release/hacdias/webdav.svg?style=flat-square)](https://github.com/hacdias/webdav/releases/latest)
[![Docker Pulls](https://img.shields.io/docker/pulls/hacdias/webdav)](https://hub.docker.com/r/hacdias/webdav)
## Install
Please refer to the [Releases page](https://github.com/hacdias/webdav/releases) for more information. There, you can either download the binaries or find the Docker commands to install WebDAV.
## Usage
```webdav``` command line interface is really easy to use so you can easily create a WebDAV server for your own user. By default, it runs on a random free port and supports JSON, YAML and TOML configuration. An example of a YAML configuration with the default configurations:
```yaml
# Server related settings
address: 0.0.0.0
port: 0
auth: true
tls: false
cert: cert.pem
key: key.pem
prefix: /
debug: false
# Default user settings (will be merged)
scope: .
modify: true
rules: []
# CORS configuration
cors:
enabled: true
credentials: true
allowed_headers:
- Depth
allowed_hosts:
- http://localhost:8080
allowed_methods:
- GET
exposed_headers:
- Content-Length
- Content-Range
users:
- username: admin
password: admin
scope: /a/different/path
- username: encrypted
password: "{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi"
- username: "{env}ENV_USERNAME"
password: "{env}ENV_PASSWORD"
- username: basic
password: basic
modify: false
rules:
- regex: false
allow: false
path: /some/file
- path: /public/access/
modify: true
```
There are more ways to customize how you run WebDAV through flags and environment variables. Please run `webdav --help` for more information on that.
### Systemd
An example of how to use this with `systemd` is on [webdav.service.example](/webdav.service.example).
### CORS
The `allowed_*` properties are optional, the default value for each of them will be `*`. `exposed_headers` is optional as well, but is not set if not defined. Setting `credentials` to `true` will allow you to:
1. Use `withCredentials = true` in javascript.
2. Use the `username:password@host` syntax.
### Reverse Proxy Service
When you use a reverse proxy implementation like `Nginx` or `Apache`, please note the following fields to avoid causing `502` errors
```text
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
```
## License
MIT © [Henrique Dias](https://hacdias.com)
+8
View File
@@ -0,0 +1,8 @@
# Security Policy
## Reporting a Vulnerability
Please report security issues to:
msaa1990 [at] gmail [dot com]
cc: hacdias [at] gmail [dot com]
+12
View File
@@ -0,0 +1,12 @@
package cmd
import (
"log"
)
// Execute executes the commands.
func Execute() {
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}
+224
View File
@@ -0,0 +1,224 @@
package cmd
import (
"errors"
"log"
"os"
"regexp"
"strconv"
"strings"
"github.com/hacdias/webdav/v4/lib"
"github.com/spf13/pflag"
v "github.com/spf13/viper"
"golang.org/x/net/webdav"
)
func parseRules(raw []interface{}, defaultModify bool) []*lib.Rule {
rules := []*lib.Rule{}
for _, v := range raw {
if r, ok := v.(map[interface{}]interface{}); ok {
rule := &lib.Rule{
Regex: false,
Allow: false,
Modify: defaultModify,
Path: "",
}
if regex, ok := r["regex"].(bool); ok {
rule.Regex = regex
}
if allow, ok := r["allow"].(bool); ok {
rule.Allow = allow
}
if modify, ok := r["modify"].(bool); ok {
rule.Modify = modify
if modify {
rule.Allow = true
}
}
path, ok := r["path"].(string)
if !ok {
continue
}
if rule.Regex {
rule.Regexp = regexp.MustCompile(path)
} else {
rule.Path = path
}
rules = append(rules, rule)
}
}
return rules
}
func loadFromEnv(v string) (string, error) {
v = strings.TrimPrefix(v, "{env}")
if v == "" {
return "", errors.New("no environment variable specified")
}
v = os.Getenv(v)
if v == "" {
return "", errors.New("the environment variable is empty")
}
return v, nil
}
func parseUsers(raw []interface{}, c *lib.Config) {
var err error
for _, v := range raw {
if u, ok := v.(map[interface{}]interface{}); ok {
username, ok := u["username"].(string)
if !ok {
log.Fatal("user needs an username")
}
if strings.HasPrefix(username, "{env}") {
username, err = loadFromEnv(username)
checkErr(err)
}
password, ok := u["password"].(string)
if !ok {
password = ""
if numPwd, ok := u["password"].(int); ok {
password = strconv.Itoa(numPwd)
}
}
if strings.HasPrefix(password, "{env}") {
password, err = loadFromEnv(password)
checkErr(err)
}
user := &lib.User{
Username: username,
Password: password,
Scope: c.User.Scope,
Modify: c.User.Modify,
Rules: c.User.Rules,
}
if scope, ok := u["scope"].(string); ok {
user.Scope = scope
}
if modify, ok := u["modify"].(bool); ok {
user.Modify = modify
}
if rules, ok := u["rules"].([]interface{}); ok {
user.Rules = append(c.User.Rules, parseRules(rules, user.Modify)...)
}
user.Handler = &webdav.Handler{
Prefix: c.User.Handler.Prefix,
FileSystem: lib.WebDavDir{
Dir: webdav.Dir(user.Scope),
NoSniff: c.NoSniff,
},
LockSystem: webdav.NewMemLS(),
}
c.Users[username] = user
}
}
}
func parseCors(cfg map[string]interface{}, c *lib.Config) {
cors := lib.CorsCfg{
Enabled: cfg["enabled"].(bool),
Credentials: cfg["credentials"].(bool),
}
cors.AllowedHeaders = corsProperty("allowed_headers", cfg)
cors.AllowedHosts = corsProperty("allowed_hosts", cfg)
cors.AllowedMethods = corsProperty("allowed_methods", cfg)
cors.ExposedHeaders = corsProperty("exposed_headers", cfg)
c.Cors = cors
}
func corsProperty(property string, cfg map[string]interface{}) []string {
var def []string
if property == "exposed_headers" {
def = []string{}
} else {
def = []string{"*"}
}
if allowed, ok := cfg[property].([]interface{}); ok {
items := make([]string, len(allowed))
for idx, a := range allowed {
items[idx] = a.(string)
}
if len(items) == 0 {
return def
}
return items
}
return def
}
func readConfig(flags *pflag.FlagSet) *lib.Config {
cfg := &lib.Config{
User: &lib.User{
Scope: getOpt(flags, "scope"),
Modify: getOptB(flags, "modify"),
Rules: []*lib.Rule{},
Handler: &webdav.Handler{
Prefix: getOpt(flags, "prefix"),
FileSystem: lib.WebDavDir{
Dir: webdav.Dir(getOpt(flags, "scope")),
NoSniff: getOptB(flags, "nosniff"),
},
LockSystem: webdav.NewMemLS(),
},
},
Debug: getOptB(flags, "debug"),
Auth: getOptB(flags, "auth"),
NoSniff: getOptB(flags, "nosniff"),
Cors: lib.CorsCfg{
Enabled: false,
Credentials: false,
},
Users: map[string]*lib.User{},
LogFormat: getOpt(flags, "log_format"),
}
rawRules := v.Get("rules")
if rules, ok := rawRules.([]interface{}); ok {
cfg.User.Rules = parseRules(rules, cfg.User.Modify)
}
rawUsers := v.Get("users")
if users, ok := rawUsers.([]interface{}); ok {
parseUsers(users, cfg)
}
rawCors := v.Get("cors")
if cors, ok := rawCors.(map[string]interface{}); ok {
parseCors(cors, cfg)
}
if len(cfg.Users) != 0 && !cfg.Auth {
log.Print("Users will be ignored due to auth=false")
}
return cfg
}
+126
View File
@@ -0,0 +1,126 @@
package cmd
import (
"log"
"net"
"net/http"
"strings"
"github.com/spf13/cobra"
v "github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var (
cfgFile string
)
func init() {
cobra.OnInitialize(initConfig)
flags := rootCmd.Flags()
flags.StringVarP(&cfgFile, "config", "c", "", "config file path")
flags.BoolP("tls", "t", false, "enable tls")
flags.Bool("auth", true, "enable auth")
flags.String("cert", "cert.pem", "TLS certificate")
flags.String("key", "key.pem", "TLS key")
flags.StringP("address", "a", "0.0.0.0", "address to listen to")
flags.StringP("port", "p", "0", "port to listen to")
flags.StringP("prefix", "P", "/", "URL path prefix")
flags.String("log_format", "console", "logging format")
}
var rootCmd = &cobra.Command{
Use: "webdav",
Short: "A simple to use WebDAV server",
Long: `If you don't set "config", it will look for a configuration file called
config.{json, toml, yaml, yml} in the following directories:
- ./
- /etc/webdav/
The precedence of the configuration values are as follows:
- flags
- environment variables
- configuration file
- defaults
The environment variables are prefixed by "WD_" followed by the option
name in caps. So to set "cert" via an env variable, you should
set WD_CERT.`,
Run: func(cmd *cobra.Command, args []string) {
flags := cmd.Flags()
cfg := readConfig(flags)
// Build address and listener
laddr := getOpt(flags, "address")
var lnet string
if strings.HasPrefix(laddr, "unix:") {
laddr = laddr[5:]
lnet = "unix"
} else {
laddr = laddr + ":" + getOpt(flags, "port")
lnet = "tcp"
}
listener, err := net.Listen(lnet, laddr)
if err != nil {
log.Fatal(err)
}
loggerConfig := zap.NewProductionConfig()
loggerConfig.DisableCaller = true
if cfg.Debug {
loggerConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
}
loggerConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
loggerConfig.Encoding = cfg.LogFormat
logger, err := loggerConfig.Build()
if err != nil {
// if we fail to configure proper logging, then the user has deliberately
// misconfigured the logger. Abort.
panic(err)
}
zap.ReplaceGlobals(logger)
defer func() {
_ = zap.L().Sync()
}()
// Tell the user the port in which is listening.
zap.L().Info("Listening", zap.String("address", listener.Addr().String()))
// Starts the server.
if getOptB(flags, "tls") {
if err := http.ServeTLS(listener, cfg, getOpt(flags, "cert"), getOpt(flags, "key")); err != nil {
zap.L().Fatal("shutting server", zap.Error(err))
}
} else {
if err := http.Serve(listener, cfg); err != nil {
zap.L().Fatal("shutting server", zap.Error(err))
}
}
},
}
func initConfig() {
if cfgFile == "" {
v.AddConfigPath(".")
v.AddConfigPath("/etc/webdav/")
v.SetConfigName("config")
} else {
v.SetConfigFile(cfgFile)
}
v.SetEnvPrefix("WD")
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(v.ConfigParseError); ok {
panic(err)
}
cfgFile = "No config file used"
} else {
cfgFile = "Using config file: " + v.ConfigFileUsed()
}
}
+19
View File
@@ -0,0 +1,19 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var version = "(untracked)"
func init() {
rootCmd.AddCommand(&cobra.Command{
Use: "version",
Short: "Print the version number",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("WebDAV version " + version)
},
})
}
+55
View File
@@ -0,0 +1,55 @@
package cmd
import (
"log"
"github.com/spf13/pflag"
v "github.com/spf13/viper"
)
// getOption returns a parameter as a string.
//
// NOTE: we could simply bind the flags to viper and use IsSet.
// Although there is a bug on Viper that always returns true on IsSet
// if a flag is binded. Our alternative way is to manually check
// the flag and then the value from env/config/gotten by viper.
// https://github.com/spf13/viper/pull/331
func getOpt(flags *pflag.FlagSet, key string) string {
value, _ := flags.GetString(key)
// If set on Flags, use it.
if flags.Changed(key) {
return value
}
// If set through viper (env, config), return it.
if v.IsSet(key) {
return v.GetString(key)
}
// Otherwise use default value on flags.
return value
}
func getOptB(flags *pflag.FlagSet, key string) bool {
value, _ := flags.GetBool(key)
// If set on Flags, use it.
if flags.Changed(key) {
return value
}
// If set through viper (env, config), return it.
if v.IsSet(key) {
return v.GetBool(key)
}
// Otherwise use default value on flags.
return value
}
func checkErr(err error) {
if err != nil {
log.Fatal(err)
}
}
-276
View File
@@ -1,276 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/hacdias/webdav"
"golang.org/x/crypto/bcrypt"
wd "golang.org/x/net/webdav"
yaml "gopkg.in/yaml.v2"
)
var (
config string
defaultConfigs = []string{
"config.json",
"config.yaml",
"config.yml",
"/etc/webdav/config.json",
"/etc/webdav/config.yaml",
"/etc/webdav/config.yml",
}
)
func init() {
flag.StringVar(&config, "config", "", "Configuration file")
}
func parseRules(raw []map[string]interface{}) []*webdav.Rule {
rules := []*webdav.Rule{}
for _, r := range raw {
rule := &webdav.Rule{
Regex: false,
Allow: false,
Path: "",
}
if regex, ok := r["regex"].(bool); ok {
rule.Regex = regex
}
if allow, ok := r["allow"].(bool); ok {
rule.Allow = allow
}
path, ok := r["rule"].(string)
if !ok {
continue
}
if rule.Regex {
rule.Regexp = regexp.MustCompile(path)
} else {
rule.Path = path
}
rules = append(rules, rule)
}
return rules
}
func parseUsers(raw []map[string]interface{}, c *cfg) {
for _, r := range raw {
username, ok := r["username"].(string)
if !ok {
log.Fatal("user needs an username")
}
password, ok := r["password"].(string)
if !ok {
log.Fatal("user needs a password")
}
c.auth[username] = password
user := &webdav.User{
Scope: c.webdav.User.Scope,
Modify: c.webdav.User.Modify,
Rules: c.webdav.User.Rules,
}
if scope, ok := r["scope"].(string); ok {
user.Scope = scope
}
if modify, ok := r["modify"].(bool); ok {
user.Modify = modify
}
if rules, ok := r["rules"].([]map[string]interface{}); ok {
user.Rules = parseRules(rules)
}
user.Handler = &wd.Handler{
FileSystem: wd.Dir(user.Scope),
LockSystem: wd.NewMemLS(),
}
c.webdav.Users[username] = user
}
}
func getConfig() []byte {
if config == "" {
for _, v := range defaultConfigs {
_, err := os.Stat(v)
if err == nil {
config = v
break
}
}
}
if config == "" {
log.Fatal("no config file specified; couldn't find any config.{yaml,json}")
}
file, err := ioutil.ReadFile(config)
if err != nil {
log.Fatal(err)
}
return file
}
type cfg struct {
webdav *webdav.Config
address string
port string
tls bool
cert string
key string
auth map[string]string
}
func parseConfig() *cfg {
file := getConfig()
data := struct {
Address string `json:"address" yaml:"address"`
Port string `json:"port" yaml:"port"`
TLS bool `json:"tls" yaml:"tls"`
Cert string `json:"cert" yaml:"cert"`
Key string `json:"key" yaml:"key"`
Scope string `json:"scope" yaml:"scope"`
Modify bool `json:"modify" yaml:"modify"`
Rules []map[string]interface{} `json:"rules" yaml:"rules"`
Users []map[string]interface{} `json:"users" yaml:"users"`
}{
Address: "0.0.0.0",
Port: "0",
TLS: false,
Cert: "cert.pem",
Key: "key.pem",
Scope: "./",
Modify: true,
}
var err error
if filepath.Ext(config) == ".json" {
err = json.Unmarshal(file, &data)
} else {
err = yaml.Unmarshal(file, &data)
}
if err != nil {
log.Fatal(err)
}
config := &cfg{
address: data.Address,
port: data.Port,
tls: data.TLS,
cert: data.Cert,
key: data.Key,
auth: map[string]string{},
webdav: &webdav.Config{
User: &webdav.User{
Scope: data.Scope,
Modify: data.Modify,
Rules: []*webdav.Rule{},
Handler: &wd.Handler{
FileSystem: wd.Dir(data.Scope),
LockSystem: wd.NewMemLS(),
},
},
Users: map[string]*webdav.User{},
},
}
if len(data.Users) == 0 {
log.Fatal("no user defined")
}
if len(data.Rules) != 0 {
config.webdav.User.Rules = parseRules(data.Rules)
}
parseUsers(data.Users, config)
return config
}
func basicAuth(c *cfg) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
username, password, authOK := r.BasicAuth()
if authOK == false {
http.Error(w, "Not authorized", 401)
return
}
p, ok := c.auth[username]
if !ok {
http.Error(w, "Not authorized", 401)
return
}
if !checkPassword(p, password) {
log.Println("Wrong Password for user", username)
http.Error(w, "Not authorized", 401)
return
}
c.webdav.ServeHTTP(w, r)
})
}
func checkPassword(saved, input string) bool {
if strings.HasPrefix(saved, "{bcrypt}") {
savedPassword := strings.TrimPrefix(saved, "{bcrypt}")
return bcrypt.CompareHashAndPassword([]byte(savedPassword), []byte(input)) == nil
}
return saved == input
}
func main() {
flag.Parse()
cfg := parseConfig()
handler := basicAuth(cfg)
// Builds the address and a listener.
laddr := cfg.address + ":" + cfg.port
listener, err := net.Listen("tcp", laddr)
if err != nil {
log.Fatal(err)
}
// Tell the user the port in which is listening.
fmt.Println("Listening on", listener.Addr().String())
// Starts the server.
if cfg.tls {
if err := http.ServeTLS(listener, handler, cfg.cert, cfg.key); err != nil {
log.Fatal(err)
}
} else {
if err := http.Serve(listener, handler); err != nil {
log.Fatal(err)
}
}
}
+23
View File
@@ -0,0 +1,23 @@
module github.com/hacdias/webdav/v4
go 1.16
require (
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.1.3
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.1
go.uber.org/zap v1.19.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
)
retract v4.1.0
+355
View File
@@ -0,0 +1,355 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
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/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
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=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
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/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
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/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
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/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/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/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
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/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
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/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-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-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
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/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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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 h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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/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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20181026203630-95b1ffbd15a5/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-20190215142949-d0b11bdaac8a/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
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-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+83
View File
@@ -0,0 +1,83 @@
package lib
import (
"context"
"mime"
"os"
"path"
"golang.org/x/net/webdav"
)
// NoSniffFileInfo wraps any generic FileInfo interface and bypasses mime type sniffing.
type NoSniffFileInfo struct {
os.FileInfo
}
func (w NoSniffFileInfo) ContentType(ctx context.Context) (contentType string, err error) {
if mimeType := mime.TypeByExtension(path.Ext(w.FileInfo.Name())); mimeType != "" {
// We can figure out the mime from the extension.
return mimeType, nil
} else {
// We can't figure out the mime type without sniffing, call it an octet stream.
return "application/octet-stream", nil
}
}
type WebDavDir struct {
webdav.Dir
NoSniff bool
}
func (d WebDavDir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
// Skip wrapping if NoSniff is off
if !d.NoSniff {
return d.Dir.Stat(ctx, name)
}
info, err := d.Dir.Stat(ctx, name)
if err != nil {
return nil, err
}
return NoSniffFileInfo{info}, nil
}
func (d WebDavDir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
// Skip wrapping if NoSniff is off
if !d.NoSniff {
return d.Dir.OpenFile(ctx, name, flag, perm)
}
file, err := d.Dir.OpenFile(ctx, name, flag, perm)
if err != nil {
return nil, err
}
return WebDavFile{File: file}, nil
}
type WebDavFile struct {
webdav.File
}
func (f WebDavFile) Stat() (os.FileInfo, error) {
info, err := f.File.Stat()
if err != nil {
return nil, err
}
return NoSniffFileInfo{info}, nil
}
func (f WebDavFile) Readdir(count int) (fis []os.FileInfo, err error) {
fis, err = f.File.Readdir(count)
if err != nil {
return nil, err
}
for i := range fis {
fis[i] = NoSniffFileInfo{fis[i]}
}
return fis, nil
}
Executable
+50
View File
@@ -0,0 +1,50 @@
package lib
import (
"regexp"
"strings"
"golang.org/x/net/webdav"
)
// Rule is a dissalow/allow rule.
type Rule struct {
Regex bool
Allow bool
Modify bool
Path string
Regexp *regexp.Regexp
}
// User contains the settings of each user.
type User struct {
Username string
Password string
Scope string
Modify bool
Rules []*Rule
Handler *webdav.Handler
}
// Allowed checks if the user has permission to access a directory/file
func (u User) Allowed(url string, noModification bool) bool {
var rule *Rule
i := len(u.Rules) - 1
for i >= 0 {
rule = u.Rules[i]
isAllowed := rule.Allow && (noModification || rule.Modify)
if rule.Regex {
if rule.Regexp.MatchString(url) {
return isAllowed
}
} else if strings.HasPrefix(url, rule.Path) {
return isAllowed
}
i--
}
return noModification || u.Modify
}
Executable
+25
View File
@@ -0,0 +1,25 @@
package lib
import (
"strings"
"golang.org/x/crypto/bcrypt"
)
func checkPassword(saved, input string) bool {
if strings.HasPrefix(saved, "{bcrypt}") {
savedPassword := strings.TrimPrefix(saved, "{bcrypt}")
return bcrypt.CompareHashAndPassword([]byte(savedPassword), []byte(input)) == nil
}
return saved == input
}
func isAllowedHost(allowedHosts []string, origin string) bool {
for _, host := range allowedHosts {
if host == origin {
return true
}
}
return false
}
+174
View File
@@ -0,0 +1,174 @@
package lib
import (
"context"
"net/http"
"strings"
"go.uber.org/zap"
)
// CorsCfg is the CORS config.
type CorsCfg struct {
Enabled bool
Credentials bool
AllowedHeaders []string
AllowedHosts []string
AllowedMethods []string
ExposedHeaders []string
}
// Config is the configuration of a WebDAV instance.
type Config struct {
*User
Auth bool
Debug bool
NoSniff bool
Cors CorsCfg
Users map[string]*User
LogFormat string
}
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
u := c.User
requestOrigin := r.Header.Get("Origin")
// Add CORS headers before any operation so even on a 401 unauthorized status, CORS will work.
if c.Cors.Enabled && requestOrigin != "" {
headers := w.Header()
allowedHeaders := strings.Join(c.Cors.AllowedHeaders, ", ")
allowedMethods := strings.Join(c.Cors.AllowedMethods, ", ")
exposedHeaders := strings.Join(c.Cors.ExposedHeaders, ", ")
allowAllHosts := len(c.Cors.AllowedHosts) == 1 && c.Cors.AllowedHosts[0] == "*"
allowedHost := isAllowedHost(c.Cors.AllowedHosts, requestOrigin)
if allowAllHosts {
headers.Set("Access-Control-Allow-Origin", "*")
} else if allowedHost {
headers.Set("Access-Control-Allow-Origin", requestOrigin)
}
if allowAllHosts || allowedHost {
headers.Set("Access-Control-Allow-Headers", allowedHeaders)
headers.Set("Access-Control-Allow-Methods", allowedMethods)
if c.Cors.Credentials {
headers.Set("Access-Control-Allow-Credentials", "true")
}
if len(c.Cors.ExposedHeaders) > 0 {
headers.Set("Access-Control-Expose-Headers", exposedHeaders)
}
}
}
if r.Method == "OPTIONS" && c.Cors.Enabled && requestOrigin != "" {
return
}
// Authentication
if c.Auth {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
// Gets the correct user for this request.
username, password, ok := r.BasicAuth()
zap.L().Info("login attempt", zap.String("username", username), zap.String("remote_address", r.RemoteAddr))
if !ok {
http.Error(w, "Not authorized", 401)
return
}
user, ok := c.Users[username]
if !ok {
http.Error(w, "Not authorized", 401)
return
}
if !checkPassword(user.Password, password) {
zap.L().Info("invalid password", zap.String("username", username), zap.String("remote_address", r.RemoteAddr))
http.Error(w, "Not authorized", 401)
return
}
u = user
zap.L().Info("user authorized", zap.String("username", username))
} else {
// Even if Auth is disabled, we might want to get
// the user from the Basic Auth header. Useful for Caddy
// plugin implementation.
username, _, ok := r.BasicAuth()
if ok {
if user, ok := c.Users[username]; ok {
u = user
}
}
}
// Checks for user permissions relatively to this PATH.
noModification := r.Method == "GET" || r.Method == "HEAD" ||
r.Method == "OPTIONS" || r.Method == "PROPFIND"
allowed := u.Allowed(r.URL.Path, noModification)
zap.L().Debug("allowed & method & path", zap.Bool("allowed", allowed), zap.String("method", r.Method), zap.String("path", r.URL.Path))
if !allowed {
w.WriteHeader(http.StatusForbidden)
return
}
if r.Method == "HEAD" {
w = newResponseWriterNoBody(w)
}
// Excerpt from RFC4918, section 9.4:
//
// GET, when applied to a collection, may return the contents of an
// "index.html" resource, a human-readable view of the contents of
// the collection, or something else altogether.
//
// Get, when applied to collection, will return the same as PROPFIND method.
if r.Method == "GET" && strings.HasPrefix(r.URL.Path, u.Handler.Prefix) {
info, err := u.Handler.FileSystem.Stat(context.TODO(), strings.TrimPrefix(r.URL.Path, u.Handler.Prefix))
if err == nil && info.IsDir() {
r.Method = "PROPFIND"
if r.Header.Get("Depth") == "" {
r.Header.Add("Depth", "1")
}
}
}
// Runs the WebDAV.
//u.Handler.LockSystem = webdav.NewMemLS()
u.Handler.ServeHTTP(w, r)
}
// responseWriterNoBody is a wrapper used to suprress the body of the response
// to a request. Mainly used for HEAD requests.
type responseWriterNoBody struct {
http.ResponseWriter
}
// newResponseWriterNoBody creates a new responseWriterNoBody.
func newResponseWriterNoBody(w http.ResponseWriter) *responseWriterNoBody {
return &responseWriterNoBody{w}
}
// Header executes the Header method from the http.ResponseWriter.
func (w responseWriterNoBody) Header() http.Header {
return w.ResponseWriter.Header()
}
// Write suprresses the body.
func (w responseWriterNoBody) Write(data []byte) (int, error) {
return 0, nil
}
// WriteHeader writes the header to the http.ResponseWriter.
func (w responseWriterNoBody) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
}
+9
View File
@@ -0,0 +1,9 @@
package main
import (
"github.com/hacdias/webdav/v4/cmd"
)
func main() {
cmd.Execute()
}
-33
View File
@@ -1,33 +0,0 @@
# webdav
[![Build](https://img.shields.io/travis/hacdias/webdav.svg?style=flat-square)](https://travis-ci.org/hacdias/webdav)
[![Go Report Card](https://goreportcard.com/badge/github.com/hacdias/webdav?style=flat-square)](https://goreportcard.com/report/hacdias/webdav)
```webdav``` is a simple tool that creates a WebDAV server for you. By default, it runs on a random free port and supports JSON and YAML configuration. Here is a simple YAML configuration example:
```yaml
scope: /path/to/files
address: 0.0.0.0
port: 8080
tls: false
cert: cert.pem
key: key.pem
users:
- username: admin
password: admin
- username: encrypted
password: "{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi"
- username: basic
password: basic
modify: false
rules:
- regex: false
- allow: false
- path: /some/file
```
You can specify the path to the configuration file using the `--config` flag. By default, it will search for a `config.{yaml,json}` file on your current working directory.
An example of how to use this with `systemd` is on [webdav.service.example](/webdav.service.example).
Download it [here](https://github.com/hacdias/webdav/releases).
-129
View File
@@ -1,129 +0,0 @@
package webdav
import (
"context"
"net/http"
"regexp"
"strings"
"golang.org/x/net/webdav"
)
// Config is the configuration of a WebDAV instance.
type Config struct {
*User
Users map[string]*User
}
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
u := c.User
// Gets the correct user for this request.
username, _, ok := r.BasicAuth()
if ok {
if user, ok := c.Users[username]; ok {
u = user
}
}
// Checks for user permissions relatively to this PATH.
if !u.Allowed(r.URL.Path) {
w.WriteHeader(http.StatusForbidden)
return
}
if r.Method == "HEAD" {
w = newResponseWriterNoBody(w)
}
// If this request modified the files and the user doesn't have permission
// to do so, return forbidden.
if (r.Method == "PUT" || r.Method == "POST" || r.Method == "MKCOL" ||
r.Method == "DELETE" || r.Method == "COPY" || r.Method == "MOVE") &&
!u.Modify {
w.WriteHeader(http.StatusForbidden)
return
}
// Excerpt from RFC4918, section 9.4:
//
// GET, when applied to a collection, may return the contents of an
// "index.html" resource, a human-readable view of the contents of
// the collection, or something else altogether.
//
// Get, when applied to collection, will return the same as PROPFIND method.
if r.Method == "GET" {
info, err := u.Handler.FileSystem.Stat(context.TODO(), r.URL.Path)
if err == nil && info.IsDir() {
r.Method = "PROPFIND"
}
}
// Runs the WebDAV.
u.Handler.ServeHTTP(w, r)
}
// Rule is a dissalow/allow rule.
type Rule struct {
Regex bool
Allow bool
Path string
Regexp *regexp.Regexp
}
// User contains the settings of each user.
type User struct {
Scope string
Modify bool
Rules []*Rule
Handler *webdav.Handler
}
// Allowed checks if the user has permission to access a directory/file
func (u User) Allowed(url string) bool {
var rule *Rule
i := len(u.Rules) - 1
for i >= 0 {
rule = u.Rules[i]
if rule.Regex {
if rule.Regexp.MatchString(url) {
return rule.Allow
}
} else if strings.HasPrefix(url, rule.Path) {
return rule.Allow
}
i--
}
return true
}
// responseWriterNoBody is a wrapper used to suprress the body of the response
// to a request. Mainly used for HEAD requests.
type responseWriterNoBody struct {
http.ResponseWriter
}
// newResponseWriterNoBody creates a new responseWriterNoBody.
func newResponseWriterNoBody(w http.ResponseWriter) *responseWriterNoBody {
return &responseWriterNoBody{w}
}
// Header executes the Header method from the http.ResponseWriter.
func (w responseWriterNoBody) Header() http.Header {
return w.ResponseWriter.Header()
}
// Write suprresses the body.
func (w responseWriterNoBody) Write(data []byte) (int, error) {
return 0, nil
}
// WriteHeader writes the header to the http.ResponseWriter.
func (w responseWriterNoBody) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
}