mirror of
https://github.com/hacdias/webdav.git
synced 2024-04-21 12:32:06 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a037fe877c | ||
|
|
81f873d2de | ||
|
|
505a10a63d | ||
|
|
61462c1da7 | ||
|
|
21ec95a8db | ||
|
|
8bf2c4eea3 | ||
|
|
4199ffc190 | ||
|
|
7a67f76947 | ||
|
|
088958913c | ||
|
|
3e7b39679b | ||
|
|
d95c1ec13b | ||
|
|
3d06904d1f | ||
|
|
4dc7bae6ea | ||
|
|
6b139d375c | ||
|
|
80e3ffd57b | ||
|
|
893d3b429a | ||
|
|
cdaf929a4b | ||
|
|
d216a2edef | ||
|
|
eeae21cd29 | ||
|
|
b645ac51eb | ||
|
|
62aea27486 | ||
|
|
541defc796 | ||
|
|
d2f2b1ccde | ||
|
|
6c10c35efe | ||
|
|
8271975046 | ||
|
|
ac0e137b1d | ||
|
|
65fa394f98 |
@@ -0,0 +1,45 @@
|
||||
version: 2
|
||||
jobs:
|
||||
lint:
|
||||
docker:
|
||||
- image: golangci/golangci-lint:v1.16
|
||||
steps:
|
||||
- checkout
|
||||
- run: golangci-lint run -v
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
steps:
|
||||
- checkout
|
||||
- run: cd cmd/webdav && 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:
|
||||
jobs:
|
||||
- lint:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- build:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- release:
|
||||
context: deploy
|
||||
requires:
|
||||
- build
|
||||
- lint
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
||||
branches:
|
||||
ignore: /.*/
|
||||
@@ -1,3 +0,0 @@
|
||||
config.yaml
|
||||
config.yml
|
||||
config.json
|
||||
+18
-6
@@ -21,9 +21,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}}-{{ .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 }}"
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
language: go
|
||||
|
||||
go: 1.8.3
|
||||
|
||||
env:
|
||||
- "PATH=/home/travis/gopath/bin:$PATH"
|
||||
|
||||
install:
|
||||
- go get ./...
|
||||
# Install gometalinter and certain linters
|
||||
- go get github.com/alecthomas/gometalinter
|
||||
- go get github.com/client9/misspell/cmd/misspell
|
||||
- go get github.com/gordonklaus/ineffassign
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- go get github.com/tsenart/deadcode
|
||||
|
||||
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
|
||||
+11
@@ -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" ]
|
||||
+85
-20
@@ -11,15 +11,24 @@ import (
|
||||
"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"}
|
||||
defaultConfigs = []string{
|
||||
"config.json",
|
||||
"config.yaml",
|
||||
"config.yml",
|
||||
"/etc/webdav/config.json",
|
||||
"/etc/webdav/config.yaml",
|
||||
"/etc/webdav/config.yml",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -68,11 +77,35 @@ func parseUsers(raw []map[string]interface{}, c *cfg) {
|
||||
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{
|
||||
@@ -126,24 +159,36 @@ func getConfig() []byte {
|
||||
}
|
||||
|
||||
type cfg struct {
|
||||
webdav *webdav.Config
|
||||
port string
|
||||
auth map[string]string
|
||||
webdav *webdav.Config
|
||||
address string
|
||||
port string
|
||||
tls bool
|
||||
cert string
|
||||
key string
|
||||
auth map[string]string
|
||||
}
|
||||
|
||||
func parseConfig() *cfg {
|
||||
file := getConfig()
|
||||
|
||||
data := struct {
|
||||
Port string `json:"port" yaml:"port"`
|
||||
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 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"`
|
||||
}{
|
||||
Port: "0",
|
||||
Scope: "./",
|
||||
Modify: true,
|
||||
Address: "0.0.0.0",
|
||||
Port: "0",
|
||||
TLS: false,
|
||||
Cert: "cert.pem",
|
||||
Key: "key.pem",
|
||||
Scope: "./",
|
||||
Modify: true,
|
||||
}
|
||||
|
||||
var err error
|
||||
@@ -158,10 +203,13 @@ func parseConfig() *cfg {
|
||||
}
|
||||
|
||||
config := &cfg{
|
||||
port: data.Port,
|
||||
auth: map[string]string{},
|
||||
address: data.Address,
|
||||
port: data.Port,
|
||||
tls: data.TLS,
|
||||
cert: data.Cert,
|
||||
key: data.Key,
|
||||
auth: map[string]string{},
|
||||
webdav: &webdav.Config{
|
||||
BaseURL: "",
|
||||
User: &webdav.User{
|
||||
Scope: data.Scope,
|
||||
Modify: data.Modify,
|
||||
@@ -192,7 +240,7 @@ func basicAuth(c *cfg) http.Handler {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
|
||||
username, password, authOK := r.BasicAuth()
|
||||
if authOK == false {
|
||||
if !authOK {
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return
|
||||
}
|
||||
@@ -203,7 +251,8 @@ func basicAuth(c *cfg) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
if password != p {
|
||||
if !checkPassword(p, password) {
|
||||
log.Println("Wrong Password for user", username)
|
||||
http.Error(w, "Not authorized", 401)
|
||||
return
|
||||
}
|
||||
@@ -212,13 +261,22 @@ func basicAuth(c *cfg) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
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.port
|
||||
laddr := cfg.address + ":" + cfg.port
|
||||
listener, err := net.Listen("tcp", laddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -228,7 +286,14 @@ func main() {
|
||||
fmt.Println("Listening on", listener.Addr().String())
|
||||
|
||||
// Starts the server.
|
||||
if err := http.Serve(listener, handler); err != nil {
|
||||
log.Fatal(err)
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
module github.com/hacdias/webdav
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
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=
|
||||
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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=
|
||||
@@ -7,10 +7,18 @@
|
||||
|
||||
```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
|
||||
@@ -22,4 +30,6 @@ users:
|
||||
|
||||
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.
|
||||
|
||||
Download it [here](https://github.com/hacdias/webdav/releases).
|
||||
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).
|
||||
|
||||
@@ -12,8 +12,7 @@ import (
|
||||
// Config is the configuration of a WebDAV instance.
|
||||
type Config struct {
|
||||
*User
|
||||
BaseURL string
|
||||
Users map[string]*User
|
||||
Users map[string]*User
|
||||
}
|
||||
|
||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
||||
@@ -28,9 +27,6 @@ func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the BaseURL from the url path.
|
||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, c.BaseURL)
|
||||
|
||||
// Checks for user permissions relatively to this PATH.
|
||||
if !u.Allowed(r.URL.Path) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
@@ -61,6 +57,10 @@ func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := u.Handler.FileSystem.Stat(context.TODO(), r.URL.Path)
|
||||
if err == nil && info.IsDir() {
|
||||
r.Method = "PROPFIND"
|
||||
|
||||
if r.Header.Get("Depth") == "" {
|
||||
r.Header.Add("Depth", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=WebDAV server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/bin/webdav --config /opt/webdav.config
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user