rclone: Add WebDAV volume share service

This allows for serving any Podman volume over WebDAV, with the ability
to serve a dedicated subdirectory per user. Authentication is provided
by LDAP, and users are expected to be in the `rclone_user` group.
This commit is contained in:
Alex Palaistras 2024-02-02 23:44:58 +00:00
parent bc592eca86
commit 96a1e177e3
7 changed files with 147 additions and 2 deletions

View File

@ -157,6 +157,14 @@ systemd:
[Service]
Environment=UPSTREAM_HOST=shiori UPSTREAM_PORT=8080
- name: nginx-proxy-http@webdav.localhost.service
enabled: true
dropins:
- name: rclone-upstream.conf
contents: |
[Service]
Environment=UPSTREAM_HOST=rclone-webdav UPSTREAM_PORT=8080
- name: letsencrypt-dns-register@localhost.service
enabled: true
dropins:
@ -165,7 +173,7 @@ systemd:
[Service]
ExecStartPre=/bin/podman volume create --ignore letsencrypt-certificates
ExecStart=
ExecStart=/bin/sh -c "V=$(podman volume mount letsencrypt-certificates) && cp -Rv /etc/ssl/private $V && chown -R 10000:10000 $V"
ExecStart=/bin/sh -c "V=$(podman volume mount letsencrypt-certificates) && cp -Rv /etc/ssl/private/. $V && chown -R 10000:10000 $V"
ExecStartPost=/bin/podman volume unmount letsencrypt-certificates
storage:

View File

@ -12,9 +12,14 @@ FROM docker.io/debian:bookworm-slim@sha256:7802002798b0e351323ed2357ae6dc5a8c4d0
RUN apt-get update -y && apt-get upgrade -y && apt-get install -y --no-install-recommends \
ca-certificates gettext gosu
RUN adduser --system --group --uid 10000 --no-create-home rclone
RUN apt-get update -y && apt-get install -y --no-install-recommends \
curl python3 python3-ldap3
RUN adduser --system --group --uid 10000 --no-create-home rclone && \
install -D --owner rclone --group rclone -d /var/lib/rclone-storage
COPY --from=builder /build /
COPY container/ldap-auth-proxy /usr/bin
USER rclone
ENTRYPOINT ["/usr/bin/rclone"]

View File

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""
An auth proxy for Rclone, backed by a generic LDAP server.
"""
import os
import sys
import json
from ldap3 import Server, Connection, AUTO_BIND_NO_TLS, SUBTREE
from ldap3.utils.conv import escape_filter_chars
ldap_host = os.getenv("AUTH_LDAP_HOST", "localhost")
ldap_port = os.getenv("AUTH_LDAP_PORT", 389)
ldap_bind_dn = os.getenv("AUTH_LDAP_BIND_DN", "")
ldap_password = os.getenv("AUTH_LDAP_PASSWORD", "")
ldap_base_dn = os.getenv("AUTH_LDAP_BASE_DN", "ou=people,dc=ldap,dc=local")
ldap_search_filter = os.getenv("AUTH_LDAP_SEARCH_FILTER", "(uid={username})")
serve_base_dir = os.getenv("SERVE_BASE_DIR", "/var/lib/rclone-storage")
serve_append_user_dir = os.getenv("SERVE_APPEND_USER_DIR", "false")
def main():
i = json.load(sys.stdin)
username = i["user"]
password = i["pass"]
if not username or not password:
print("Error: No username or password given", file=sys.stderr)
return
server = Server(ldap_host, port=int(ldap_port))
connection = Connection(
server, user=ldap_bind_dn, password=ldap_password, auto_bind=AUTO_BIND_NO_TLS
)
connection.search(
search_base=ldap_base_dn,
search_filter=ldap_search_filter.format(username=escape_filter_chars(username)),
search_scope=SUBTREE,
attributes=["uid"],
time_limit=15,
)
total_entries = len(connection.response)
if total_entries == 0:
print(f"Error: No results found for '{username}'", file=sys.stderr)
return
if total_entries > 1:
print(f"Error: More than one result found for '{username}'", file=sys.stderr)
return
response = connection.response[0]
if "uid" not in response["attributes"] or len(response["attributes"]["uid"]) == 0:
print(f"Error: Empty UID attribute returned for '{username}'", file=sys.stderr)
return
uid = response["attributes"]["uid"][0]
serve_root = (
serve_base_dir + "/" + uid
if serve_append_user_dir == "true"
else serve_base_dir
)
output = {
"type": "local",
"_root": serve_root,
"_obscure": "pass",
"user": uid,
"pass": password,
"host": "",
}
json.dump(output, sys.stdout)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,20 @@
[Unit]
Description=Rclone WebDAV Server
Wants=container-build@rclone.service lldap.service
After=container-build@rclone.service lldap.service
[Container]
AutoUpdate=local
ContainerName=%N
Environment=SERVE_APPEND_USER_DIR=true
EnvironmentFile=%E/coreos-home-server/rclone/rclone.env
Exec=serve webdav --addr :8080 --auth-proxy /usr/bin/ldap-auth-proxy
Image=localhost/rclone:latest
Network=internal
Volume=%N:/var/lib/rclone-storage:z
[Service]
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@ -2,6 +2,14 @@
RCLONE_LOG_LEVEL=INFO
RCLONE_LINKS=true
# Configuration for LDAP authentication.
AUTH_LDAP_HOST=lldap
AUTH_LDAP_PORT=3890
AUTH_LDAP_BASE_DN=ou=people,dc=ldap,dc=local
AUTH_LDAP_BIND_DN=uid=${LLDAP_ADMIN_USERNAME},ou=people,dc=ldap,dc=local
AUTH_LDAP_PASSWORD=${LLDAP_ADMIN_PASSWORD}
AUTH_LDAP_SEARCH_FILTER=(&(memberof=cn=rclone_user,ou=groups,dc=ldap,dc=local)(|(uid={username})(mail={username})))
# Configuration for default encrypted remote, configured to wrap the default unencrypted remote.
# Password and salt values must be processed via `rclone obscure` before setting.
RCLONE_CONFIG_CRYPT_TYPE=crypt

View File

@ -6,3 +6,5 @@ storage:
local: service/rclone/
- path: /etc/systemd/system
local: service/rclone/systemd/
- path: /etc/containers/systemd
local: service/rclone/quadlet/

View File

@ -0,0 +1,22 @@
[Unit]
Description=Rclone WebDAV Server for Volume %I
Wants=container-build@rclone.service
After=container-build@rclone.service
[Service]
Type=notify
NotifyAccess=all
SyslogIdentifier=%N
Restart=on-failure
Environment=PODMAN_SYSTEMD_UNIT=%n
ExecStart=/bin/podman run --replace --name %p-%i --net internal --sdnotify=conmon \
--env-file %E/coreos-home-server/rclone/rclone.env \
--volume %i:/var/lib/rclone-storage:z \
localhost/rclone:latest serve webdav \
--addr :8080 \
--auth-proxy /usr/bin/ldap-auth-proxy
ExecStop=/bin/podman stop --ignore --time 10 %p-%i
ExecStopPost=/bin/podman rm --ignore --force %p-%i
[Install]
WantedBy=multi-user.target