Requirements:
- serve WebDAV over Nginx
- use custom authentication based on requested path
- basic authentication header (
Authorization: Basic <credentials>
) - dockerize the solution
I lost about two days to set this up despite using ChatGPT, DeepSeek, Grok and Gemini for help. So I suppose the contents of this post might be useful to some people.
Nginx config
The first line – loading dav_ext
module – is crucial.
It caused a lot of pain and none of the AI chatbots were helpful.
I finally got the hint how to solve it at the following link:
https://nginx-extras.getpagespeed.com/modules/dav-ext/.
load_module modules/ngx_http_dav_ext_module.so;
events {
worker_connections 1024;
}
http {
# limit maximum file upload size to 2MB
client_max_body_size 2M;
server {
listen 80;
# WebDAV location
location / {
auth_request /auth;
root /mnt;
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS;
create_full_put_path on;
dav_access user:rw group:rw all:rw;
auth_request_set $auth_uri $uri;
}
# internal auth subrequest
location = /auth {
internal;
proxy_pass http://127.0.0.1:8080/auth?path=$request_uri&method=$request_method;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Authorization $http_authorization;
}
# error handling for unauthorized access
error_page 401 = @unauthorized;
location @unauthorized {
return 401 "Unauthorized\n";
}
}
}
Auth in Golang
This is set up so that username
, when provided with proper password in basic auth scheme, has access to folder username
.
Of course, you can modify the code below for a different set up, but the relevant data is passed to the /auth
endpoint: path, method and credentials.
package main
import (
"crypto/subtle"
"encoding/base64"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
)
// path: token
var accessList = map[string]string{
"user1": "pass1",
"user2": "pass2",
"testuser": "testpass",
}
func main() {
mkDirs("/mnt")
http.HandleFunc("/auth", authHandler)
fmt.Println("* starting auth server")
http.ListenAndServe(":8080", nil)
}
func mkDirs(root string) {
for path, _ := range accessList {
err := os.MkdirAll(filepath.Join(root, path), os.ModePerm)
if err != nil {
panic(err)
}
if err := os.Chmod(filepath.Join(root, path), 0777); err != nil {
panic(err)
}
}
}
func authHandler(w http.ResponseWriter, r *http.Request) {
username := ""
password := ""
ok := false
authHeader := r.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Basic ") {
encoded := authHeader[len("Basic "):]
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err == nil {
parts := strings.SplitN(string(decoded), ":", 2)
if len(parts) == 2 {
username, password = parts[0], parts[1]
ok = true
}
}
}
path := r.URL.Query().Get("path")
method := r.URL.Query().Get("method")
fmt.Printf("* auth request (%d): "+username+":"+password+" -> "+method+" -> "+path+"\n", ok)
if !ok || !isValid(username, password, path) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
fmt.Println("* auth not ok")
return
}
fmt.Println("* auth ok")
w.WriteHeader(http.StatusOK)
}
func isValid(username, password, path string) bool {
expectedPassword, exists := accessList[username]
if !exists {
fmt.Printf("* `%s` does not exist\n", username)
return false
}
// cerify password
if subtle.ConstantTimeCompare([]byte(password), []byte(expectedPassword)) != 1 {
fmt.Println("* wrong password")
return false
}
// check if user has access to the requested path
if !strings.HasPrefix(path, fmt.Sprintf("/%s/", username)) && strings.Compare(path, fmt.Sprintf("/%s", username)) != 0 {
fmt.Printf("* invalid access path")
return false
}
// we're done
return true
}
Dockerfile
Use docker build -t nginxwebdavauth .
and then docker run -it --rm -v "$(pwd)/mnt:/mnt" -p 8080:80 nginxwebdavauth
.
# docker build -t nginxwebdavauth .
# docker run -it --rm -v "$(pwd)/mnt:/mnt" -p 8080:80 nginxwebdavauth
# STAGE #1: Build the Go auth binary
FROM golang:1.21 AS go-builder
WORKDIR /app
COPY auth.go .
RUN go mod init auth
RUN CGO_ENABLED=0 GOOS=linux go build -o auth auth.go
# STAGE #2: Set up Nginx and include the Go binary
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y nginx-full nginx-extras
WORKDIR /app
COPY --from=go-builder /app/auth /app/auth
COPY nginx.conf /etc/nginx/nginx.conf
CMD ["/bin/sh", "-c", "nginx -g 'daemon on;' & /app/auth"]