自建NAS照片管理服务启用TLS踩坑记录
目前我自用的 NAS 使用了OMV 提供存储管理的服务,同时用 OMV 的 omv-extral 插件所提供的 docker 支持,跑了Immich 照片管理服务。
前言
自从前年11月份收了一个二手的电脑之后,便开始了我的自建 NAS 之旅,当时赶着存储降价,拼夕夕入手了两个全新的4T硬盘,用 btrfs 组了一个 raid1阵列,运行的系统是 Openmediavault。在稳定运行了一段时间后,我在 NAS 上增加了照片管理服务。照片管理用的是 Immich,当时刚开始用的时候还比较简陋,经过一年多的高强度更新之后,Immich 已经非常完善,借助时间轴和深度学习,Immich 能够很方便的查找和管理照片,同时可以跟很方便的共享照片,最近的更新更是能让老婆的照片直接显示在自己的时间轴上(同时也能让自己的照片放到老婆的时间轴上,以后拍照是要考虑再三了😏)
经过长时间的调优,现在 NAS 存储+照片管理服务已经能够顺畅使用了,但还有一个不舒服的地方就是现在的照片管理通过 ddns-go 动态更新 DNS 域名解析的方式提供了外网的访问,但服务未经加密,直接通过 http 访问。而且最近我用火狐浏览器在不小心在访问时使用了https 的方式,访问失败最后转成 http 访问的时候又会自动跳转 https,这就导致我无法在浏览器上访问照片服务,虽然可以通过清理缓存等方式来重新使用 http 访问,但着实不优雅。于是想为照片服务启用 TLS,提供 https 服务。这篇文章是一个踩坑记录,不是教程,里面废话会比较多,主要是记录一下整个过程。
基本架构
为了安全性考虑,我的服务大都放在了路由器后面,如果有访问内网文件的需求,通过反向代理的方式进行临时访问,后续有机会水一篇 NAS 整体的设计。我的 Immich 的 https 访问则是通过反向代理服务器提供https访问服务,其中借助 certbot
自动获取 CA 证书。
域名申请和DNS域名解析代理
域名购买
域名购买比较简单,这里不详细介绍了。我的域名是在 namesilo 购买的,之所以选择 namesilo,是因为相比较而言价格较为便宜,功能比较完善,而且没有乱七八糟的广告,之前我有买过 GoDaddy 的域名,中间有各种广告和诱导收费,体验不是很好,不知道现在情况是否有改善。
域名解析代理
虽然 namesilo 的域名比较便宜,但许多自动化工具不支持 namesilo,好在 Cloudflare 支持代理域名解析,我们可以把 namesilo 上买的域名用 Cloudflare 域名服务器来代理解析。
首先在 Cloudflare dashboard 上添加站点,如果可以的话,也可以利用 Cloudflare 的 CDN 来加速网站访问,或者隐藏自己照片服务器的真实地址,不过 Cloudflare 免费套餐里只代理 http 和 https 常用端口的流量,如果需代理其他端口,需要使用额外付费的服务实现。
我这里没有使用代理,配置方式是进入站点的 dashboard,点击右侧 DNS 记录选项,将代理状态改为仅 DNS。
我使用子域名来提供照片服务的访问,其他子域名用作其他用途。
反向代理服务器
为了提供 https 服务,最简单的方式是在 immich上提供一个反向代理服务器,我这里用的是 caddy
。Caddy对小白友好,上手很快,不过就是有很多不经意的坑,还是要多读文档解决问题。
本身 Caddy 是自带CA证书自动获取功能的,但因为各种原因我这里无法使用。
流行的几种 Linux 发行版官方的源里就有caddy安装包,直接安装即可。如果安装 caddy 插件,如 DNS challenge 插件,则需要重新编译 caddy
,或者从 caddyserver 上下载二进制文件。
Caddy 配置反向代理非常简单。修改 Caddyfile 配置反向代理和 CA 证书:
https://your-domain:your-port {
reverse_proxy http://localhost:immich-port
tls /xxx/fullchain.pem /xxx/privkey.pem
}
/xxx/fullchain.pem
和 /xxx/privkey.pem
是签发的 full chain 和 private key。
需要注意的是,如果是直接从官方的源里安装的
caddy
其 service 是以 caddy 用户运行的,需设置权限使 fullchain.pem 和 privkey.pem 对 caddy 用户可读,不然启动 service 会报权限相关错误
证书自动签发
Caddy 踩坑
Caddy 自动签发CA通过三种方式进行:
HTTP challenge:需要开启
80
端口TLS-ALPN challenge:需要开启
443
端口DNS challenge:无需开启任何端口
鉴于运营商通常屏蔽80
和443
端口,因此理论上使用 DNS challenge 的方式签发CA最合适,但我这里测试失败,先记录一下,以后弄清怎么回事之后再作更新。
DNS challenge 是社区维护的功能的插件,需要下载插件后重编译 caddy,或者从 caddy 下载页面下载。具体下载方法或编译方法,可以参考社区论坛教程。
使用 DNS challenge 方式申请 CA 时,必须在自己的 DNS 服务器上添加包含特定令牌值的 TXT 记录,。一旦证书颁发机构确认 DNS 记录已经更新,即可完成验证,并颁发证书。Caddy 可以通过 DNS 服务商提供的 API Key 管理工具,自动添加 TXT。大多数 DNS 服务商都提供了 API Key 管理工具,namesilo 可以在 My Account 页面的 API Manager 选项里生成 API Key,注意不要勾选 Generate key for read-only access。Cloudflare 则需要添加站点后,进入站点的 Dashboard,点击右侧API选项里点击“获取您的 API令牌“。Caddyfile 的配置参考刚刚的社区论坛教程。
只能说理想很丰满,现实很骨感,我使用Caddy配置自动CA签发会报time out的错误,百思不得其解,因此最后退而求其次,选择使用certbot
来自动签发证书。
Certbot 证书自动申请
Certbot 是Let’s Encrypt 推荐的自动证书签发工具,其签发方式应该跟 Caddy 是类似的,官方教程里分为了defaul 方式和 wildcard 两种申请场景,其中后一种对应的是 DNS challenge。Openmediavault 基于 Debian 12,certbot 官方教程里,Debian 系的系统只提供了 snap 安装包,需要在系统里安装 snapd
,我觉得这个服务太过于臃肿,因此最终选择了使用 pip 的方式安装 certbot。我这里使用 wildcard 场景来自动签发域名。需要注意的是这种方式需要额外安装插件,不同的 DNS 服务商需要不同的插件,这里会发现 certbot
竟然没有 namesilo 的 DNS challenge 插件。因此我选择使用 Cloudflare 代理域名解析,让 Cloudflare 管理我的域名解析,之后再用 certbot-dns-cloudflare
进行证书申请。certbot
官方文档十分详细,我在申请的过程中也没碰到什么坑,这里就不赘述了。
certbot 申请的证书包括了 fullchain.pem
和 privkey.pem
两个文件,将这两个文件添加到 Caddyfile 里就能够启用 TLS 访问了。不过需要注意这里还有两个问题:
证书存储的路径对 caddy 用户组可能没有访问权限,参考反向代理服务器;
证书自动更新后会可能会被更改读写权限,caddy service 有可能读取文件失败。
这里只需要创建一个定时任务,让其在指定的时间更新 CA 并更改文件权限就行了。这里提供一个参考:
在/etc/crontab
里新增:
0 0,12 * * * root /opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && /bin/sh /var/lib/caddy/update_cert.sh
在 /var/lib/caddy
里创建 update_cert.sh
:
certbot renew -q
cp /etc/letsencrypt/live/your-domain/* /var/lib/caddy/
chown -R caddy /var/lib/caddy/
chgrp -R caddy /var/lib/caddy/
总结
其实对大多人而言,直接买现成的服务或许是最好的选择,能节省很多精力。但自建 NAS 的意义可能从来不是金钱来衡量的。自建 NAS 的动力一方面可能是出于通过“搭积木”来创造自己喜爱的东西的成就感,另一方面是出于对互联网服务提供商的不信任。想当年我把照片放在 Google 照片里,当时 Google 宣称自己的压缩技术可以几乎无损压缩照片,鼓励大家用压缩的格式存储自己的照片,多年以后却又声称这种压缩是有损的,建议用户使用无损上传。虽然这种压缩对我来说无关紧要,但这种欺骗,很难让我再去信任任何一家厂商的言论。把数据留在自己手里的安全感,亦是我耗费经历搭建 NAS 的初衷。