Help with webdav under a path

1. The problem I’m having:

Hi guys, I’m trying to setup webdav, like all my services, under a path (md5 generated, like a kind of psk). I would like that path the be the root of the dav, translated then to another folder in my filesystem.

There is something wrong with the request and I can’t wrap my head around it.

Thanks in advance for your help!

2. Error messages and/or full log output:

{"level":"error","ts":1726694330.375861,"logger":"http.handlers.webdav","msg":"internal handler error","error":"stat /data/webdav/34995edb340340fab3334bae34343db3: no such file or directory","request":{"remote_ip":"192.168.1.23","remote_port":"61554","client_ip":"192.168.1.23","proto":"HTTP/1.1","method":"PROPFIND","host":"webdav.noshit.net","uri":"/34995edb340340fab3334bae34343db3/","headers":{"Authorization":["REDACTED"],"Content-Length":["179"],"Connection":["keep-alive"],"Content-Type":["text/xml"],"Depth":["0"],"Accept":["*/*"],"User-Agent":["WebDAVFS/3.0.0 (03008000) Darwin/23.6.0 (arm64)"]},"tls":{"resumed":false,"version":771,"cipher_suite":49195,"proto":"","server_name":"webdav.noshit.net"}}}

3. Caddy version:

2.8.4

4. How I installed and ran Caddy:

a. System environment:

UNRAID, x64, Docker v24.0.9

b. Command:

docker run
  -d
  --name='CaddyV2'
  --net='cloud'
  --ip='172.18.0.3'
  --pids-limit 2048
  -e TZ="Europe/Berlin"
  -e HOST_OS="Unraid"
  -e HOST_HOSTNAME="MYNAS"
  -e HOST_CONTAINERNAME="CaddyV2"
  -l net.unraid.docker.managed=dockerman
  -l net.unraid.docker.icon='https://d1q6f0aelx0por.cloudfront.net/product-logos/library-caddy-logo.png'
  -p '2080:80/tcp'
  -p '2443:443/tcp'
  -p '2443:443/udp'
  -v '/mnt/user/storage/Configurations/caddyv2/data':'/data':'rw'
  -v '/mnt/user/appdata/caddyv2':'/config':'rw'
  -v '/mnt/user/storage/Configurations/caddyv2/Caddyfile':'/etc/caddy/Caddyfile':'rw' 'caddy:alpine'

d. My complete Caddy config:

*.noshit.net {
	tls {
		dns cloudflare SOMETOKEN
	}
	vars psk "34995edb340340fab3334bae34343db3"
	handle /{vars.psk}/* {
		@webdav host webdav.noshit.net
		handle @webdav {
			route {
				basicauth {
					user SOMEHASH
				}
				root /{vars.psk}/* /data/webdav
				webdav
			}
			error "Access Denied" 403
		}
	}
}

Howdy @cyruz. welcome to the Caddy community.

I think what you’re saying is that you’d like a client to request, for example, /34995edb340340fab3334bae34343db3/foo/bar.txt and be served a file at /data/webdav/foo/bar.txt?

Which is to say, in another way, you want to route the /34995edb340340fab3334bae34343db3 path and strip that prefix from the URI before checking the filesystem for a file to serve?

To do that you’d need a rewrite, for which we have a useful little directive for partial rewriting in the form of uri (Caddyfile directive) — Caddy Documentation. You can also probably clean up your config a lot to reduce the unnecessary handle, handle, route nesting. Just one matcher and one route should do the trick.

*.noshit.net {
	tls {
		dns cloudflare SOMETOKEN
	}
	vars psk "34995edb340340fab3334bae34343db3"
	@pskdav {
		host webdav.noshit.net
		path /{vars.psk}/*
	}
	route @pskdav {
		basicauth {
			user SOMEHASH
		}
		root /data/webdav
		uri strip_prefix /{vars.psk}
		webdav
	}
	error "Access Denied" 403
}
2 Likes

Thank you @Whitestrake that’s exactly what I wanted to achieve.

Regarding the config I have many others subdomains that I actually omitted from the configuration, so it’s something like:

*.noshit.net {
	tls {
		dns cloudflare SOMETOKEN
	}
	vars psk "34995edb340340fab3334bae34343db3"
	redir /{vars.psk} /{vars.psk}/
	
	handle /{vars.psk}/* {
		@vnc host vnc.noshit.net
		handle @vnc {
			reverse_proxy 172.18.0.4:3000
		}
		
		@fb host fb.noshit.net
		handle @fb {
			reverse_proxy 172.18.0.5:80
		}

		@vw host vw.noshit.net
		handle @vw {
			# Disable the Admin Panel.
			handle /admin/* {
				error "Access Denied" 403
			}
			reverse_proxy 172.18.0.6:80 {
				header_up X-Real-IP {remote_host}
			}
		}

		@webdav host webdav.noshit.net
		handle @webdav {
			route {
				basicauth {
					user SOMEHASH
				}
				root /data/webdav
				uri strip_prefix /{vars.psk}
				webdav
			}
			error "Access Denied" 403
		}
	}
	# Fallback for anything unhandled.
	handle /* {
		error "Access Denied" 403
	}
}

If you have any suggestion about cleaning this mess up, let me know :smiley:

Well, you can start by inverting your PSK logic to unwrap the handle there. By that, I mean instead of defaulting to error and routing the PSK path, default to routing the path and explicitly error non-PSK path requests. You can also use a CEL expression matcher to check the first path element to eliminate the need for that redirect. Altogether, this also reduces the repetition of {vars.psk} to a single instance, eliminating that line too.

After that you can use map to reduce the matchers for your generic reverse proxies, but you’ll need to keep handles for your uniquely configured hostnames.

	@notPSK `{path.0} != "34995edb340340fab3334bae34343db3"`
	error @notPSK "Access Denied" 403

	@vw host vw.noshit.net
	handle @vw {
		error /admin/* "Access Denied" 403
		reverse_proxy 172.18.0.6:80 {
			header_up X-Real-IP {client_ip}
		}
	}

	@webdav host webdav.noshit.net
	handle @webdav {
		root /data/webdav
		uri strip_prefix /{path.0}
		basic_auth {
			user SOMEHASH
		}
		webdav
	}

	map {host} {myproxy} {
		vnc.noshit.net	172.18.0.4:3000
		fb.noshit.net	172.18.0.5:80
	}
	reverse_proxy {myproxy}

No route is required for your webdav host because the standard directive order is sufficient for these directives (route is for manually reordering the execution of directives).

No 403 error is required in the webdav handle either as basic_auth will issue 401s automatically and you’ll have a separate 403 error response for missing the PSK path. The error there in your version would never execute anyway as the higher-precedence route offers a basic_auth and a webdav handler so all requests will receive some response before we reach the error handler.

2 Likes

Thanks again! Looks sleek, I will study it and apply the changes.

1 Like

They’re using webdav which is a plugin, which is why they use route.

But actually, using the order global option is better. Add order webdav before file_server in your global options to avoid needing to use route. See the README: GitHub - mholt/caddy-webdav: WebDAV handler module for Caddy

(Even better would be if the webdav plugin used the new RegisterDirectiveOrder() API so you wouldn’t even need the order global option, but that hasn’t been done yet – PRs are surely welcome on that repo to do that)

2 Likes

Right, I’d made the assumption they were doing this because I had a peek at the plugin and saw it was almost the first thing it says to do, manually order the plugin in global options before file_server.

Probably a bad assumption and well worth pointing out.

2 Likes

@Whitestrake in your conf, how to fit an exception to the psk rule? For example, if some webapps that I’m going to install do not support subpaths and can’t be fixed through rewrites, so I’m forced to use the subdomain root…

Probably something like this:

	@notPSK {
		expression {path.0} != "34995edb340340fab3334bae34343db3"
		not host app1.example.com app2.example.com app3.example.com
	}
	error @notPSK "Access Denied" 403

Multiple matchers get AND’ed together, but multiple hosts get OR’d together.

So the above basically says, “if it’s not the PSK path, and it isn’t any of these hosts, banish them to the shadow realm, Jimbo”.

2 Likes

Oh got it! I have to say that the support in this community is amazing. I noticed that from other threads as well. Thank you guys!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.