19 Commits
Author SHA1 Message Date
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
18 changed files with 593 additions and 400 deletions
+4 -1
View File
@@ -11,13 +11,16 @@ jobs:
- image: circleci/golang:1.12
steps:
- checkout
- run: cd cmd/webdav && go build main.go
- run: go build main.go
release:
docker:
- image: circleci/golang:1.12
steps:
- checkout
- setup_remote_docker
- run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- run: curl -sL https://git.io/goreleaser | bash
- run: docker logout
workflows:
version: 2
build-workflow:
+32 -7
View File
@@ -1,6 +1,15 @@
env:
- GO111MODULE=on
before:
hooks:
- go mod download
build:
main: cmd/webdav/main.go
main: main.go
binary: webdav
ldflags:
- -s -w -X github.com/hacdias/webdav/cmd.version={{.Version}}
goos:
- darwin
- linux
@@ -13,6 +22,10 @@ build:
- 386
- arm
- arm64
goarm:
- 5
- 6
- 7
ignore:
- goos: openbsd
goarch: arm
@@ -21,9 +34,21 @@ 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
dockers:
-
goos: linux
goarch: amd64
goarm: ''
image_templates:
- "hacdias/webdav:latest"
- "hacdias/webdav:{{ .Tag }}"
- "hacdias/webdav:v{{ .Major }}.{{ .Minor }}"
- "hacdias/webdav:v{{ .Major }}"
+11
View File
@@ -0,0 +1,11 @@
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
+54
View File
@@ -0,0 +1,54 @@
# webdav
[![Build](https://img.shields.io/circleci/project/github/hacdias/webdav/master.svg?style=flat-square)](https://circleci.com/gh/hacdias/webdav)
[![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)
## 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
# Default user settings (will be merged)
scope: .
modify: true
rules: []
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
```
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).
## License
MIT © [Henrique Dias](https://hacdias.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)
}
}
+151
View File
@@ -0,0 +1,151 @@
package cmd
import (
"errors"
"log"
"os"
"regexp"
"strings"
"github.com/hacdias/webdav/webdav"
"github.com/spf13/pflag"
v "github.com/spf13/viper"
wd "golang.org/x/net/webdav"
)
func parseRules(raw []interface{}) []*webdav.Rule {
rules := []*webdav.Rule{}
for _, v := range raw {
if r, ok := v.(map[interface{}]interface{}); ok {
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["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 *webdav.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 strings.HasPrefix(password, "{env}") {
password, err = loadFromEnv(password)
checkErr(err)
}
user := &webdav.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 = parseRules(rules)
}
user.Handler = &wd.Handler{
FileSystem: wd.Dir(user.Scope),
LockSystem: wd.NewMemLS(),
}
c.Users[username] = user
}
}
}
func readConfig(flags *pflag.FlagSet) *webdav.Config {
cfg := &webdav.Config{
User: &webdav.User{
Scope: getOpt(flags, "scope"),
Modify: getOptB(flags, "modify"),
Rules: []*webdav.Rule{},
Handler: &wd.Handler{
FileSystem: wd.Dir(getOpt(flags, "scope")),
LockSystem: wd.NewMemLS(),
},
},
Auth: getOptB(flags, "auth"),
Users: map[string]*webdav.User{},
}
rawRules := v.Get("rules")
if rules, ok := rawRules.([]interface{}); ok {
cfg.User.Rules = parseRules(rules)
}
rawUsers := v.Get("users")
if users, ok := rawUsers.([]interface{}); ok {
parseUsers(users, cfg)
}
if len(cfg.Users) != 0 && !cfg.Auth {
log.Print("Users will be ignored due to auth=false")
}
return cfg
}
+98
View File
@@ -0,0 +1,98 @@
package cmd
import (
"fmt"
"log"
"net"
"net/http"
"strings"
"github.com/spf13/cobra"
v "github.com/spf13/viper"
)
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.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")
}
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)
// Builds the address and a listener.
laddr := getOpt(flags, "address") + ":" + getOpt(flags, "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 getOptB(flags, "tls") {
if err := http.ServeTLS(listener, cfg, getOpt(flags, "cert"), getOpt(flags, "key")); err != nil {
log.Fatal(err)
}
} else {
if err := http.Serve(listener, cfg); err != nil {
log.Fatal(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)
}
}
-300
View File
@@ -1,300 +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")
}
// load username from environment when prefix {env} is added
if strings.HasPrefix(username, "{env}") {
var envUsername = strings.TrimPrefix(username, "{env}")
if envUsername == "" {
log.Fatal("no environment variable specified for username")
}
username = os.Getenv(envUsername)
if username == "" {
log.Fatal("username must be set in environment")
}
}
password, ok := r["password"].(string)
if !ok {
log.Fatal("user needs a password")
}
// load password from environment when prefix {env} is added
if strings.HasPrefix(password, "{env}") {
var envPassword = strings.TrimPrefix(password, "{env}")
if envPassword == "" {
log.Fatal("no environment variable specified for password")
}
password = os.Getenv(envPassword)
if password == "" {
log.Fatal("password must be set in environment")
}
}
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 {
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)
}
}
}
+5 -3
View File
@@ -3,9 +3,11 @@ module github.com/hacdias/webdav
go 1.12
require (
github.com/kr/pretty v0.1.0 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.2
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.2
)
+42 -7
View File
@@ -1,20 +1,55 @@
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/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
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/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/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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
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/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 h1:6M3SDHlHHDCx2PcQw3S4KsR170vGqDhJDOmpVd4Hjak=
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+12
View File
@@ -0,0 +1,12 @@
package main
import (
"runtime"
"github.com/hacdias/webdav/cmd"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
cmd.Execute()
}
-35
View File
@@ -1,35 +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: "{env}ENV_USERNAME"
password: "{env}ENV_PASSWORD"
- 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).
+48
View File
@@ -0,0 +1,48 @@
package webdav
import (
"regexp"
"strings"
"golang.org/x/net/webdav"
)
// 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 {
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) 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
}
+16
View File
@@ -0,0 +1,16 @@
package webdav
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
}
+34 -47
View File
@@ -2,16 +2,14 @@ package webdav
import (
"context"
"log"
"net/http"
"regexp"
"strings"
"golang.org/x/net/webdav"
)
// Config is the configuration of a WebDAV instance.
type Config struct {
*User
Auth bool
Users map[string]*User
}
@@ -19,11 +17,38 @@ type Config struct {
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
if c.Auth {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
// Gets the correct user for this request.
username, password, ok := r.BasicAuth()
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) {
log.Println("Wrong Password for user", username)
http.Error(w, "Not authorized", 401)
return
}
u = user
} 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
}
}
}
@@ -68,44 +93,6 @@ func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
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 {