部署配方 · 密钥与身份
Authelia:给值得保护的服务装一道统一的门
机架上不是每个服务都需要第二道登录界面。但那些装着东西的——密码库、文档、带着主机名和端口信息的仪表盘——需要,而且它们不该各自长出一张用户表。Authelia 待在反向代理后面,对每个请求回答一个问题,并维护唯一一份「谁能看什么」的名单。
| 容器镜像 | authelia/authelia:4.38.19 |
|---|---|
| 宿主端口 | :9091/tcp |
| 数据目录 | /srv/homelab/authelia/config |
| 内存预留 | 120 MB |
| CPU 上限 | 0.25 vCPU (cpus: "0.25") |
| 更新节奏 | twice a year. Configuration keys are renamed between minor versions fairly often, so read the release notes and keep the old config in git before bumping |
| ARM 兼容 | arm64 native |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :9091 | 9091 | tcp | 仅局域网 | the portal itself and the forward-auth endpoint the proxy calls for every protected request |
部署步骤
先于一切生成三个密钥
会话、存储加密和身份校验 JWT 是三个独立的值。它们放在文件里而不是配置里,也绝不能从另一套安装里复用。
run sudo mkdir -p /srv/homelab/authelia/{config,secrets} for s in session storage jwt; do openssl rand -base64 48 | tr -d '\n' | sudo tee /srv/homelab/authelia/secrets/$s >/dev/null done sudo chmod 600 /srv/homelab/authelia/secrets/*用哈希后的密码创建用户数据库
每个密码都要过一遍容器自带的哈希命令。displayname 和 email 字段有意义,因为代理会把它们作为请求头传给后面的应用。
run docker run --rm authelia/authelia:4.38.19 authelia crypto hash generate argon2 --password 'CHANGE_ME' # users_database.yml: # users: # ada: # displayname: Ada # password: '$argon2id$v=19$m=65536,t=3,p=4$...' # email: [email protected] # groups: [family, admins]写好配置,策略默认拒绝
从全部拒绝开始,然后一次一个地加上你真正要保护的主机。顺序很重要:第一条匹配的规则生效,所以具体的规则要放在宽泛的上面。
run cat > /srv/homelab/authelia/config/configuration.yml <<'EOF' host: 0.0.0.0 port: 9091 theme: dark session: name: authelia_session domain: lan expiration: 1h remember_me: 30d storage: local: path: /config/db.sqlite3 access_control: default_policy: deny EOF接上代理,确认会发生跳转
用一个没有会话的浏览器测试。预期行为是跳转到门户,并带一个 redirect 参数,登录后回到你原本请求的页面。
run docker compose up -d authelia docker logs --tail 30 authelia curl -sI https://vault.lan | head -3 # 预期 302 到 https://auth.lan/?rd=https%3A%2F%2Fvault.lan%2F给每个账号登记双因素,并检查绕过路径
现在就登记,而不是等有人第一次要用密码库时。同时确认监控的绕过路径在无会话时仍然可用——一个被跳转到登录页的状态检查,会在服务其实已经挂了的时候报「正常」。
run curl -s -o /dev/null -w '%{http_code}\n' http://192.168.10.20:9110/api/health # 预期 200,且不带会话 cookie
代理和 Authelia 如何分工
对受保护主机的每个请求,代理都会向 Authelia 的 forward-auth 端点发一个子请求并复制它的答复。200 就是放行;302 指向门户,意味着浏览器去登录,回来后代理再问一次。
关键细节在于执行这件事的是 Caddy,不是应用本身。应用根本看不到未认证的请求,也就是说,一个自带弱登录的服务,依然被前面这道闸门保护着。
# Caddyfile
secure.lan {
forward_auth authelia:9091 {
uri /api/verify?rd=https://auth.lan/
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
reverse_proxy cloud:80
}默认说不的规则
访问控制段从上往下评估,第一条匹配的生效。安全的写法是底部放一条兜底的拒绝,上面是明确的放行,这样新增一个主机却没有加规则时,不会意外把它公开出去。
有两类对象需要区别对待:不装敏感内容的服务用单因素,密码库和文档用双因素,只有监控系统需要无会话访问的健康检查路径才用 bypass。
# /srv/homelab/authelia/config/configuration.yml
access_control:
default_policy: deny
rules:
- domain: vault.lan
policy: two_factor
- domain: cloud.lan
subject:
- ["group:family"]
policy: two_factor
- domain: status.lan
resources:
- "^/api/.*$"
policy: bypass
- domain: status.lan
policy: one_factor正确地生成密码哈希
Authelia 存的是 Argon2id 哈希,而生成它的唯一靠谱办法是用容器自带的命令,因为参数必须和正在运行的版本期望的一致。在别处生成再粘回来,就是人们得到「登录永远失败、却没有任何错误提示」的原因。
密钥同理:会话、存储加密和 JWT 密钥是三个独立的随机值,放在文件里;而轮换存储加密密钥需要一次迁移步骤,而不只是重启。
docker run --rm authelia/authelia:4.38.19 authelia crypto hash generate argon2 --password 'the-password'
# 把摘要粘进 users_database.yml,绝不粘密码本身家人实际体验到的样子
在 auth.lan 登录一次,可选记住三十天,之后门户就退到幕后。家庭里对这套的抵触来自两个地方:每次有人想看照片都要双因素,以及会话在正做事的时候过期。
两者都是配置问题。七天的会话加记住我 cookie 解决第一个;第二个正是「记住我」存在的意义。会话时长该按你对家里这些设备的信任程度来定,而不是按文档的建议值。
# 值得调的会话与限流设置
session:
expiration: 1h
inactivity: 5m
remember_me: 30d
regulation:
max_retries: 3
find_time: 2m
ban_time: 5mcompose 文件
整份文件放在 /srv/homelab/authelia/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
authelia:
image: authelia/authelia:4.38.19
container_name: authelia
restart: unless-stopped
ports:
- "9091:9091/tcp"
environment:
TZ: Asia/Shanghai
AUTHELIA_SESSION_SECRET_FILE: /secrets/session
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/storage
AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: /secrets/jwt
volumes:
- /srv/homelab/authelia/config:/config
- /srv/homelab/authelia/secrets:/secrets:ro
networks: [rack]
networks:
rack:
external: true加固清单
- 门户只在局域网内可达;代理会拒绝不是来自代理自身的 forward-auth 请求
- 密码以 Argon2id 哈希存储,由容器自带的哈希命令生成,明文绝不进配置文件
- 会话 cookie 用文件里的密钥签名,即使 cookie 在共享网络上被截获也无法篡改
- 每个受保护主机一组规则,默认拒绝:没有在访问控制文件里写明的路径就不可达
- 除一份很小的绕过清单外,一切都需要双因素;清单里每一条都要写明理由
备份方案
配置目录里有用户数据库(含密码哈希)、各种密钥和访问规则。它很小,而且在 git 里,唯一被排除的是密钥文件。丢了它意味着每个用户都要重新登记双因素——对一个四口之家来说,就是一个晚上。
上线后验证
- 未认证访问受保护主机会跳转到门户,登录后回到原 URL
- 密码库要求双因素,而状态页只要求密码
- 不在正确组里的用户得到 403 而不是登录循环,说明规则确实是按组匹配的,而不只是按域名
- 重启 Authelia 不会把任何人登出,说明会话密钥在重启之间是稳定的
踩过的坑
- 密钥在重启之间绝不能变。重新生成会话密钥会把所有人登出;重新生成存储加密密钥会让已有的用户数据库变得不可读,那就要恢复而不是修复了。
- 用别的 Argon2 实现、参数又不同生成的密码哈希,会验证失败且不给出有用的错误。永远用你正在运行的那个镜像版本自带的哈希命令。
- 绕过规则里写一个宽泛的路径正则,就是认证网关「其实什么都没保护」的由来。只匹配监控系统实际使用的那个精确 API 前缀,别的都不要。
- forward-auth URL 里的 redirect 参数是浏览器会去跟随的。Authelia 会校验它,但一个被手工改成指向意外地址的值,值得在第一周的日志里留意。
硬件与选型问答
- 四口之家值得上这个吗?
- 值得,但更多是因为它消掉的那堆重复登录,而不是它增加的安全性。四个服务各有一张账号表,就是四个要重置密码的地方;一个门户一份用户名单,就是一个。安全上的收益是真实的,但是次要的——真正的收益是它后面的服务可以完全不设登录。
- Authelia 还是 Authentik 还是 Keycloak?
- Authelia 最小,也最贴合 forward-auth 代理模式,代价是它不是完整的身份提供者——没有那些想要 OIDC 流程的应用能用的 OIDC。Authentik 是折中路线,更重但支持更多协议。Keycloak 是企业系统,它就属于企业。
- Authelia 挂了会怎样?
- 代理后面每个受保护的服务都会报错,因为验证子请求失败了。这是诚实的失败模式:失败即关闭。值得为监控留一条绕过路径,这样你是从状态页而不是从家人那里知道这次故障的。