首页
/ Copyparty 结合 Authentik 与 Traefik 的容器化 IdP 认证部署方案详解

Copyparty 结合 Authentik 与 Traefik 的容器化 IdP 认证部署方案详解

2026-09-05 20:03:51作者:温玫谨Lighthearted

本文以仓库中的 docs/examples/docker/idp-authentik-traefik 示例文件夹为主体,完整解析如何用 Docker Compose 将 copyparty 放到 Traefik 反向代理之后、并通过 goauthentik 作为身份提供方(Identity Provider)实现容器化的单点登录认证。读完你将掌握:整套 compose 文件中每个服务的职责与关键参数、cpp/copyparty.conf 中基于 ${u}/${g} 的动态卷权限体系,以及 copyparty 侧 IdP 头校验的源码级实现原理。需要特别注意:该示例作者在 README 中明确声明“无法保证其质量、安全与安全性,内容组合自网上找到的示例”,并标注“does not work yet”,因此本文将其定位为“架构参考与学习素材”,生产环境请自行完善后使用。

一、示例文件夹结构与总体架构

该示例文件夹(docs/examples/docker/idp-authentik-traefik/README.md)包含以下核心文件:

文件 作用
docker-compose.yml 六合一编排:copyparty、traefik、postgresql、redis、authentik_server、authentik_worker
cpp/copyparty.conf copyparty 的 IdP 模式配置文件,挂载进容器 /cfg
based-on/docker-compose-authentik.yml 参考来源:Authentik 官方 docker-compose 示例的存档
based-on/docker-compose-traefik.yml 参考来源:Authentik 官方 Traefik outpost 集成文档的示例存档

整体架构是:外部请求先到 Traefik(80 端口),Traefik 通过 Docker Provider 读取容器 label 发现路由;认证由 Authentik 完成,认证通过后由反向代理在请求头中注入用户名/组信息,copyparty 读取这些头完成授权。这与 docs/idp.md 中描述的通用 IdP 接入模型一致:copyparty 不做登录,只信任由反向代理注入的指定头。

示例 README 说明其基于 Authentik 官方的 docker-compose 示例与 Traefik 集成文档,并列出(不完全)的改动清单:

  • 支持以 root 在 Fedora 上用 podman 运行(:z 卷标志、label:disable SELinux 选项);
  • 其他改动未完全列出。

二、docker-compose.yml 逐服务解析

完整编排文件见 docs/examples/docker/idp-authentik-traefik/docker-compose.yml,以下按服务展开。

2.1 copyparty 服务

copyparty:
  image: copyparty/ac
  container_name: idp_copyparty
  restart: unless-stopped
  user: "1000:1000"  # should match the user/group of your fileshare volumes
  volumes:
    - ./cpp/:/cfg:z  # the copyparty config folder
    - /srv/pub:/w:z  # host path shared online
  ports:
    - 3923
  labels:
    - 'traefik.enable=true'
    - 'traefik.http.routers.fs.rule=Host(`fs.example.com`)'
    - 'traefik.http.routers.fs.entrypoints=http'
    #- 'traefik.http.routers.fs.middlewares=authelia@docker'  # TODO: ???
  stop_grace_period: 15s
  environment:
    LD_PRELOAD: /usr/lib/libmimalloc-secure.so.NOPE
    PYTHONUNBUFFERED: 1

关键点:

  • user: "1000:1000" 必须与宿主机共享目录属主的 uid/gid 一致,否则容器内无权限读写数据卷;
  • ./cpp/ 挂载为容器内 /cfg,即 copyparty 的配置目录;/srv/pub 映射为容器内 /w,这就是 cpp/copyparty.conf 中所有卷都指向 /w/... 的原因;
  • :z 卷后缀与 label:disable 是 SELinux/podman 场景下放行共享所需的写法;
  • stop_grace_period: 15s:注释说明这是给缩略图生成器在收到关闭信号后继续收尾用的;
  • LD_PRELOAD 指向 .NOPE 是禁用状态,把 NOPE 改为 2 可启用 mimalloc(注释称会提速但占用双倍内存);
  • 那行被注释掉的 traefik.http.routers.fs.middlewares=...# TODO: ??? 注释——这正是 README 所说“尚未跑通”的直接体现:认证中间件尚未挂到 fs 路由上,即请求还没有被强制经过 IdP 认证。

2.2 traefik 服务

traefik:
  image: traefik:v2.11
  container_name: traefik
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock  # WARNING: full root-access to host
  security_opt:
    - label:disable
  ports:
    - 80:80
  command:
    - '--api'
    - '--providers.docker=true'
    - '--providers.docker.exposedByDefault=false'
    - '--entrypoints.web.address=:80'

要点:使用 Docker Provider 监听容器 label 做服务发现,exposedByDefault=false 表示只有显式带 traefik.enable=true 的服务才会暴露(本文件中仅 copyparty 服务带该 label);挂载 docker.sock 会授予 Traefik 对宿主机的 root 级访问,compose 中的 WARNING 注释已明确提示此风险;label:disable 用于在 Fedora/SELinux 环境下禁用对 docker.sock 的 SELinux 拦截。

2.3 数据层:postgresql 与 redis

  • postgresql 使用 postgres:12-alpine,健康检查 pg_isready(start_period 20s / interval 30s / retries 5);数据目录为命名卷 database;环境里硬编码了示例账号 authentik/postgrass,并声明 env_file: .env——注意该 .env 文件并未包含在仓库中,实际部署需自行准备;
  • redis 使用 redis:alpine,启动参数 --save 60 1 --loglevel warning,健康检查 redis-cli ping | grep PONG,数据存于命名卷 redis

两者都是 Authentik 的运行时依赖(PostgreSQL 存认证数据,Redis 做缓存/会话)。

2.4 authentik_server 与 authentik_worker

两个服务都使用 ghcr.io/goauthentik/server:2024.2.1,分别以 command: server(HTTP 服务,对外 9000/9443 端口)与 command: worker(任务执行器)方式运行,环境变量 AUTHENTIK_REDIS__HOSTAUTHENTIK_POSTGRESQL__* 指向同网络内的服务名。worker 服务保留了几处值得注意的配置:

  • user: root/var/run/docker.sock 挂载是可选的,用于 Authentik 的 docker outpost 自动管理集成;注释同时提醒:若移除 user: root,需自行保证 ./media./certs./custom-templates 等挂载目录权限(默认 1000:1000);
  • 这些 ./media./certs./custom-templates 目录同样没有包含在仓库中,首次运行前需创建。

based-on/ 两个存档文件说明了本编排的原始出处:based-on/docker-compose-authentik.yml 是 Authentik 官方 compose 的存档(原版用 ${PG_PASS:?database password required} 强制从 .env 取密码,本示例改成了硬编码,安全性更差,仅作演示);based-on/docker-compose-traefik.yml 则展示了认证真正闭环时应该长什么样——它包含一个 authentik-proxy outpost 容器,并通过 Traefik label 定义 forwardauth 中间件:

traefik.http.routers.authentik.rule: Host(`app.company`) && PathPrefix(`/outpost.goauthentik.io/`)
traefik.http.middlewares.authentik.forwardauth.address: http://authentik-proxy:9000/outpost.goauthentik.io/auth/traefik
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,...

即:outpost 认证成功后,会把 X-authentik-usernameX-authentik-groups 等响应头回写给下游。对照当前 docker-compose.yml 中 copyparty 服务缺失的 middlewares 配置和那个 # TODO: ???,可以推断:要跑通该示例,需要引入类似 authentik-proxy 的 outpost 服务,并把 fs 路由挂上对应的 forwardauth 中间件,同时让 copyparty 读取 X-authentik-username/X-authentik-groups(或统一改名为 X-IdP-User/X-IdP-Group)。这正是作者标注“尚未跑通”的工作缺口。

三、cpp/copyparty.conf:IdP 模式下的动态卷与权限体系

容器内 copyparty 读取 docs/examples/docker/idp-authentik-traefik/cpp/copyparty.conf,这是本示例最有参考价值的部分。文件头注释解释了整体思路:不再使用 copyparty 内置账号认证,而是期望反向代理在 HTTP 头中提供用户名(以及可选组名),用于可选的组级访问控制。

3.1 [global] 段:声明 IdP 头

[global]
  e2dsa  # enable file indexing and filesystem scanning
  e2ts   # enable multimedia indexing
  ansi   # enable colors in log messages

  idp-h-usr: x-idp-user
  idp-h-grp: x-idp-group

idp-h-usr 指定承载用户名的头名(此处为 X-IdP-User),idp-h-grp 指定组名头(X-IdP-Group)。在源码中,这两个选项对应 copyparty/main.py 中的命令行参数 --idp-h-usr(可重复使用)与 --idp-h-key,且 --idp-h-usr 的帮助文本自带警告:“如果你启用这个,务必确保客户端无法自行指定该头;必须由反向代理将其洗掉并替换”。

补充安全参数(见 docs/idp.md,本示例的 conf 未全部启用):

  • xff-src:限定合法请求来源子网,例如 xff-src: 10.88.0.0/24xff-src: lan(所有私有 IP),防止外部伪造代理来源;
  • idp-h-key: <头名>:要求反向代理额外注入一个“秘密头”,缺省时其余 IdP 头一律不被信任——这是 copyparty 侧对 IdP 头信任链的主护栏。

3.2 静态根卷

[/]
  /w
  accs:
    rw: *        # 所有人可读
    rwmda: @su   # 组 "su" 获得读/写/移动/删除/管理权限

/ 卷直接分享容器数据卷 /w(宿主机 /srv/pub),所有登录用户可读,仅管理员组 su 可写。

3.3 基于 ${u} / ${g} 的动态卷

[/u/${u}]          # 每个用户在 /u/username 拥有自己的家目录
  /w/u/${u}
  accs:
    r: *                  # 任何人可读
    rwmda: ${u}, @su      # 该用户本人 + su 组可写

[/u/${u}/priv]     # 每个用户的私有区域
  /w/u/${u}/priv
  accs:
    rwmda: ${u}, @su      # 仅本人 + su 组,无 r:* 匿名读

[/lounge/${g}]     # 每个组一个共享卷
  /w/lounge/${g}
  accs:
    r: *
    rwmda: @${g}, @su

[/lounge/${g}/priv]  # 每个组也有私有区域
  /w/lounge/${g}/priv
  accs:
    rwmda: @${g}, @su

${u}${g} 是占位符,分别匹配请求头中的用户名与组名,URL 路径即按登录者身份动态解析出不同的文件系统路径。权限语法中:r 只读、rwmda = 读/写/移动/删除/管理,* 表示匿名/所有,@组名 表示组,${u}/${g} 表示身份本身。更复杂的条件选择器(如 ${u%+su} 仅 su 成员、${u%-su} 非 su 成员)在完整版示例 docs/examples/docker/idp/copyparty.conf 的尾部有演示,可作为本示例的进阶补充。

3.4 “战略性”兜底卷

[/u]
  /w/u
  accs:
    rwmda: @su
[/lounge]
  /w/lounge
  accs:
    rwmda: @su

这两个父级卷的作用是兜底安全:若 IdP 的用户/组数据库丢失,动态卷暂时无法“复活”时会继承父卷权限。父卷只授予 @su,意味着私有任何人不可读——这正是 docs/idp.md 中“IdP 卷默认在重启后被遗忘,复活前继承父卷权限”一节的推荐做法:把动态卷放在一个权限足够保守的父卷之下。

四、copyparty 侧 IdP 机制的源码印证

结合源码可以确认 IdP 头信任的完整链路:

  1. 参数解析copyparty/main.py 定义 --idp-h-usr(可重复)、--idp-h-key(秘密头,可选但推荐)、以及帮助文档中反复出现的 --idp-hm-usr 等配套头;--hdr-au-usr--idp-h-usr 的兼容别名(见 copyparty/main.py 中的参数迁移表)。
  2. 请求头校验copyparty/httpcli.py 实现了核心防线——当配置了 idp-h-key 而请求中缺失该秘密头时,日志会明确拒绝:“the idp-h-key header is not present in the request; will NOT trust the other headers saying that the client's username is …”,即直接忽略用户名/组头,请求退回未认证状态;copyparty/httpcli.py 附近的注释也点明“IdP 的主要保护手段是 --idp-h-key”。
  3. 身份持久化copyparty/authsrv.pyidp-store 级别处理身份缓存:级别 1(默认)把用户记入数据库但不真正“记住”;级别 2 记住用户名;级别 3 记住用户名及其组。默认不持久化的原因是:你从 IdP 服务器删除某用户后,期望 copyparty 也随之忘记此人;若开了持久化,则需手动在控制面板 view idp cache 中清理。相关状态在 copyparty/svchub.py 中与服务启动流程联动。

另外,若 IdP 部署后要连接 WebDAV 客户端(如 rclone),docs/idp.md 给出了已知解法:将目标域的策略设为单因素认证,然后在 rclone 配置里用 headers = Proxy-Authorization,basic <base64(用户名:密码)> 注入代理认证头。

五、使用前提、限制与加固建议

适用前提与限制(均来自该文件夹 README 与 compose 文件本身的注释,务必知悉):

  • 示例作者声明无法保证质量、安全与安全性,内容组合自网上示例;并明确标注 does not work yet——当前 compose 中 copyparty 的 fs 路由没有挂载认证中间件(见 2.1 节中的 TODO 注释),认证闭环尚未打通;
  • postgresqlauthentik_serverauthentik_worker 均引用 env_file: .env,但 .env./media./certs./custom-templates 均未包含在仓库中,部署前需自行补齐,且应把硬编码的 POSTGRES_PASSWORD 换回基于 .env${PG_PASS} 写法(可参照 based-on/docker-compose-authentik.yml${PG_PASS:?database password required} 强制写法);
  • Traefik 与 authentik_worker 都挂载了 /var/run/docker.sock,等于向容器暴露宿主 root 权限,属于示例的已知风险点,生产环境可考虑 docker socket-proxy 等收敛方案(姊妹示例 docs/examples/docker/idp-authelia-traefik/README.md 就采用了 socket-proxy,并把安全加固清单(如缓存服务移入私有网络、只暴露 Traefik 公开端口)列了出来,可供借鉴);
  • 该编排基于 Traefik v2(v2.11),中间件语法为 traefik.http.*,不要混用 Traefik v3 语法。

要跑通认证闭环,最小修改方向(基于 based-on/ 参考文件的推断):

  1. 增加 authentik-proxy outpost 容器(参考 based-on/docker-compose-traefik.yml),配置 AUTHENTIK_HOST 与 outpost token;
  2. 在 copyparty 服务的 label 中启用 fs 路由的 forwardauth 中间件,并把 outpost 回写的 X-authentik-username/X-authentik-groups 头与 copyparty 侧 idp-h-usr/idp-h-grp 的头名对齐(或统一为 X-IdP-User/X-IdP-Group);
  3. cpp/copyparty.conf[global] 中补上 xff-src(限定代理来源子网)与 idp-h-key(秘密头),这两项在 docs/idp.md 中被分别描述为“必需”与“推荐”;
  4. 按 3.4 节保留父级兜底卷,并为 su 组配置对应的 Authentik 组映射。

六、小结

docs/examples/docker/idp-authentik-traefik 提供了“copyparty + Traefik + Authentik”容器化 IdP 部署的完整骨架:compose 侧定义了六个服务的编排、数据卷与健康检查,cpp/copyparty.conf 侧用 ${u}/${g} 动态卷 + 组级 accs + 兜底父卷构建了可落地的权限模型,而 copyparty 源码(__main__.py 的参数、httpcli.py 的头校验、authsrv.pyidp-store)则保证了“头不可伪造、信任可关闭”的安全语义。由于该示例自述尚未跑通,建议将其视为结构与思路参考,配合 docs/idp.md 的机制说明与 based-on/ 中的官方 outpost 集成写法完成最后一公里,并对照姊妹示例 docs/examples/docker/idp-authelia-traefik/README.md 中已跑通的 Authelia 方案进行交叉验证。

登录后查看全文
热门项目推荐
相关项目推荐