
Dozzle Simple 认证实战users.yml 配置、角色权限与会话机制深度解析【免费下载链接】dozzleRealtime log viewer for containers. Supports Docker, Swarm and K8s.项目地址: https://gitcode.com/GitHub_Trending/do/dozzle本文基于 Dozzle 官方文档中的 simple 认证指南docs/de/guide/authentication/simple.md展开系统讲解 Dozzle 自带用户管理simple 认证的完整落地方式users.yml文件结构、bcrypt 密码哈希生成、会话 Cookie 有效期、按用户的容器过滤与角色roles授权并结合 internal/auth 下的源码与测试用例剖析 JWT 签名密钥派生、热重载与角色实时生效等底层机制帮助读者在自建容器日志平台上安全地开启多用户登录与细粒度权限控制。什么是 simple 认证Dozzle 的 simple 认证是其自带的用户管理方案所有用户集中存放在一份users.yml文件中由 Dozzle 自己读取和管理登录页也由 Dozzle 自身渲染。启用方式只有一个——把--auth-provider设置为simple对应环境变量DOZZLE_AUTH_PROVIDER。密码只是证明你是文件里某个用户的方式之一通过 GitHub 或 OIDC 登录是另一种方式两者读取的是同一份users.yml即该文件始终是允许登录的白名单。这一点在入口代码中有明确印证main.go 中simple是合法 provider 之一github、google是它的别名启动时会被直接改写为simple而 main.go 的oauthProviders注释写得很清楚——OAuth 只是多一种证明身份的方式users.yml始终是白名单。提示强烈建议使用内置的generate子命令生成users.yml而不是手写 bcrypt 哈希生成方法见下文。users.yml 的存放位置与加载优先级开启 simple 认证后Dozzle 会从/data/目录读取用户文件查找规则如下源码见 main.go优先查找/data/users.yml若不存在则查找/data/users.yaml两个都不存在时进程直接以 fatal 日志退出No users.yaml or users.yml file found.两个都存在时users.yml优先。启动日志会打印实际读取的文件名如Reading users.yml file对应 main.go 的Reading %s file日志便于确认当前生效的是哪一份。示例文件路径/data/users.yml/data/users.yaml文件内容形如users: # admin 是这里的用户名username admin: email: meemail.net name: Admin # 可用 docker run -it --rm amir20/dozzle generate admin --password password --email meemail.net --name Admin 生成 password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK filter: roles:各字段的实际语义可以从 internal/auth/users.go 的User结构体确认字段说明email可选。Dozzle 用它通过 Gravatar 服务生成用户头像users.go 中AvatarURL方法按邮箱哈希拼接 Gravatar 地址无头像时回退到 ui-avatars 的姓名头像。可用--disable-avatars全局关闭头像。name可选。显示名缺省时回退为用户名users.go。password必填之一。bcrypt 哈希长度必须为 60 字符。也可不填密码改用github链接或邮箱完成 OAuth 登录。filter可选。该用户可见的容器过滤器使用 Docker 过滤器语法。roles可选。该用户的角色集合缺省视为all。github源码中支持的可选字段users.go用于绑定 GitHub 登录名配合 OAuth 登录使用。加载阶段还会做一组快速失败的校验users.go 的decodeUsersFromFile任何一个用户不满足都会导致启动报错而不是登录时才暴露用户没有password、没有github也没有email时直接报错——这种条目永远无法登录视为配置错误密码哈希长度为 64 的按旧版 sha256 哈希处理并被拒绝sha256 密码自 v10.0.1 起不再支持报错信息会指引用dozzle generate重新生成 bcrypt 哈希密码哈希长度不是 60 时报invalid password hashbcrypt 哈希固定 60 字符filter解析失败时报错并带上具体用户和表达式container.ParseContainerFilter解析users.go两个用户共用同一个邮箱或同一个 GitHub 登录名时报错避免 OAuth 登录时共享账号解析到错误用户。另外用户数据库支持热重载users.go 的readFileIfChanged会在每次查找用户时stat文件只要 mtime 晚于上次读取时间就重新解析整份文件并打印Reloading user database。也就是说修改users.yml后不需要重启 Dozzle改动包括删除用户会在后续请求中实时生效。用 generate 子命令生成密码哈希users.yml中的密码必须是 bcrypt 哈希Dozzle 提供了内置的generate子命令来生成避免手写哈希。完整用法$ docker run -it --rm amir20/dozzle generate admin --password password --email meemail.net --name Admin命令输出即可直接作为users.yml内容的完整 YAML。该子命令的参数定义在 internal/support/cli/generate_command.go参数简写说明username位置参数用户名必填--password-p设置密码省略时会交互式提示输入终端下不回显也支持管道输入如echo secret \| dozzle generate ...--name-n显示名--email-e邮箱--user-filter-该用户的过滤器逗号分隔的过滤器列表--user-roles-该用户的角色逗号分隔的角色列表哈希算法细节在 users.go 的GenerateUsers中使用bcrypt.GenerateFromPassword(..., 11)即 bcrypt cost 11生成后直接编码为一份只含该用户的users.yml打印到 stdout——交互提示写在 stderr所以stdout可以放心重定向进文件。登录校验本身在 users.go 的CompareHashAndPassword只有 60 字符的哈希才进入bcrypt.CompareHashAndPassword比较64 字符的 sha256 会被拒绝并打错误日志其他长度一律拒绝。由于该函数运行在未认证的请求路径上它被刻意写成返回 false 而不是让进程崩溃防止猜测用户名被放大成拒绝服务。部署示例挂载 users.ymlusers.yml必须挂载进容器Dozzle 才能找到它。完整可复制的三种方式如下CLI 方式$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle --auth-provider simpledocker-compose 方式# docker-compose.yml services: dozzle: image: amir20/dozzle:latest volumes: - /var/run/docker.sock:/var/run/docker.sock - /path/to/dozzle/data:/data ports: - 8080:8080 environment: DOZZLE_AUTH_PROVIDER: simpleusers.ymlusers: admin: email: meemail.net name: Admin password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK如果使用 Docker Swarm 的 Secrets可以把users.yml作为 secret 直接投递到/data/users.ymlservices: dozzle: image: amir20/dozzle:latest environment: - DOZZLE_AUTH_PROVIDERsimple secrets: - source: users target: /data/users.yml volumes: - /var/run/docker.sock:/var/run/docker.sock - dozzle:/data secrets: users: file: users.yml volumes: dozzle:一个值得注意的登录页行为登录表单里的用户名/密码输入框并非无条件渲染。simple.go 的PasswordLoginEnabled会检查是否存在任何一个带密码的用户如果所有用户都只配置了 OAuth邮箱/GitHub 绑定登录页会直接隐藏密码表单避免给出一个永远不可能成功的答案。延长认证 Cookie 的生命周期默认情况下 Dozzle 使用 Session Cookie关闭浏览器即失效。要延长会话有效期把--auth-ttl环境变量DOZZLE_AUTH_TTL设为一个时长即可$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle --auth-provider simple --auth-ttl 48h# docker-compose.yml services: dozzle: image: amir20/dozzle:latest volumes: - /var/run/docker.sock:/var/run/docker.sock - /path/to/dozzle/data:/data ports: - 8080:8080 environment: DOZZLE_AUTH_PROVIDER: simple DOZZLE_AUTH_TTL: 48h注意只接受纯时长格式单位仅支持s秒、m分、h小时这一点在参数定义处有明确声明args.goAccepts duration values like 12h. Valid time units are s, m, h。解析逻辑在 main.go值为默认的session时 TTL 为 0会话 Cookie否则走time.ParseDuration解析失败直接 fatal。TTL 会被写进 JWT 的过期声明simple.go 的issueToken中SetExpiryInOAuth 回调路径复用同一个 TTL所以无论用密码还是 OAuth 登录Cookie 有效期一致。为单个用户设置容器过滤器filter过滤器用来限制某个用户能看到哪些容器配置在users.yml的filter字段使用 Docker 过滤器语法users: admin: email: name: Admin password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK filter: guest: email: name: Guest password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK filter: labelcom.example.app示例中admin没有过滤器因此能看到所有容器guest只能看到带com.example.app标签的容器。适合用它把只关心某个业务的一组容器的用户隔离开。两个补充事实来自源码用户过滤器在加载阶段就被解析成标签结构users.go 调container.ParseContainerFilter解析失败的文件整体拒载而不是让该用户下次请求时才看不到任何容器。测试用例 simple_test.go 验证了这一点filter: nope这种非法表达式会让ReadUsersFromFile直接返回错误。过滤器同样可以全局设置--filter环境变量DOZZLE_FILTER对所有用户生效用户自己的filter若已设置则覆盖全局过滤器。全局过滤器的语法与解析见 args.go每项必须形如keyvalue语法说明参考 filters 文档。过滤器不会冻结在令牌里中间件每次请求都从用户数据库重新解析当前用户见下文会话机制所以users.yml里改了 filter活跃会话下一次请求就用上新值。为单个用户设置角色roles角色roles定义用户能对容器执行哪些操作同样配置在users.ymlusers: admin: email: name: Admin password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK roles: guest: email: name: Guest password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK roles: shell示例中admin未指定角色即拥有全部容器操作权限roles为空时按all处理见 users.goguest只有shell角色只能对容器开 Shell。Dozzle 支持的角色如下角色同时接受的别名允许的操作shelldozzle_shell附加到容器并打开 Exec 会话。实例还必须额外开启--enable-shell。actionsdozzle_actions启动、停止、重启容器。实例还必须额外开启--enable-actions。downloaddozzle_download把容器日志下载为文件。notificationsdozzle_notifications创建和编辑通知规则与通知目标。clouddozzle_cloud关联、解除关联并配置 Dozzle Cloud。alldozzle_all以上所有角色。roles为空时的默认值。nonedozzle_none无任何角色。日志仍然可见受用户过滤器约束。会覆盖列表中的其他角色。角色字符串的解析规则由 internal/auth/roles.go 的ParseRole实现可确认以下细节角色之间用逗号或竖线分隔shell,actions或shell|actionsJSON 数组也可以[shell, actions]解析入口先检测是否为合法 JSON 再选择分支大小写不敏感strings.ToLower后匹配dozzle_前缀别名存在的目的是让身份提供方forward-proxy 模式里的组名原样透传未知角色名只打 debug 日志并跳过不会报错任意角色前加^表示排除排除在所有其他角色之后统一应用所以书写顺序无关紧要roles: all,^shell # 除了 shell 之外的所有角色none是唯一不可取反的角色^none会被忽略debug 日志none cannot be negated而列表中只要出现一个普通的none就立即返回空角色集抹掉其他所有角色。角色在实现上是一个位掩码roles.goShell、Actions、Download、Notifications、Cloud各占一位All是全部位的并集判断用roles.Has(role)。各 Web 端点对应执行点Shellinternal/web/terminal.go、internal/web/index.go容器操作internal/web/actions.go日志下载internal/web/download.go通知规则管理internal/web/notifications.goCloud 关联internal/web/cloud.go注意shell和actions角色还要叠加实例级开关前端能力开关是实例开启了该功能且用户拥有该角色的与运算internal/web/index.go 中config[enableShell] h.config.EnableShell user.Roles.Has(auth.Shell)即实例没开--enable-shell时有shell角色的用户同样开不了 Shell。两个官方文档中明确的告警值得在授权时重视notifications是实例级能力通知规则按表达式选择容器而不是按用户过滤器。拥有该角色的用户可以为自己被过滤器隐藏的容器创建规则并把那些日志行发送到自己控制的目标。只应授予你信任其接触实例上所有容器的人。cloud也是实例级能力关联 Cloud 时存储的是单个 API 密钥会把告警发送、日志流与工具执行整体指向一个 Cloud 账号。有cloud角色的用户可以把实例关联到自己的 Cloud 账号从而看到所有容器也可以解除已有连接。该角色管的是关联而不是读取——任何登录用户都能在自己过滤器范围内检索 Cloud 日志、查看 Cloud 告警而 Cloud 侧主动发起的工具调用比如在 Telegram/Discord 里提问没有对应的 Dozzle 用户走的是实例级过滤器。会话机制源码解析签名密钥、热重载与角色实时生效simple 认证的核心实现在 internal/auth/simple.go有三个值得理解的设计均有测试用例佐证internal/auth/simple_test.go1. 会话令牌是 JWT但令牌里只放你是谁。issueTokensimple.go只把username加签发时间和可选过期时间写进 claims注释说明用户的一切都按请求从users.yml读取写死进令牌的内容只会变成过期的副本。2. JWT 签名密钥来自持久化随机密钥 用户内容的联合摘要。NewSimpleAuthsimple.go先取SessionSecret见下再按用户名的稳定排序逐个写入密码哈希、角色、邮箱、GitHub 登录名做 SHA-256得到 HS256 签名密钥。这个设计让修改密码或角色能自动作废全部旧会话而重启则不会——按用户名排序是必要的Go 的 map 遍历顺序是随机的直接遍历会让多用户配置每次启动派生出不同密钥导致所有会话在重启后被静默作废。对应测试TestSimpleAuthSigningKeyIsStableAcrossRestarts重启 50 次旧令牌仍有效与TestSimpleAuthSigningKeyChangesWhenCredentialsChange改角色后旧令牌被拒。持久化密钥本身在 internal/auth/secret.go 的SessionSecret首次启动在users.yml同目录生成 32 字节随机数写入session_secret文件与users.yml一起落在同一个持久卷上多副本共享卷时天然共用同一把密钥写入用O_EXCL防止多副本首启竞争时互相覆盖。目录不可写时降级为临时密钥并打警告——会话将在重启后失效但服务仍可启动这是为了兼容只读挂载users.yml的真实部署。代码注释也解释了为什么密钥不能只从users.yml派生当存在无密码的纯 OAuth 账号时摘要输入全是可猜测的公开信息任何人都能自己派生密钥并伪造任意用户的会话。3. 角色和过滤器按请求实时解析不信任令牌里的旧值。AuthMiddlewaresimple.go验证令牌后用其中的 username 重新查一次用户数据库把当前的角色和过滤器注入请求上下文。这样users.yml里收窄角色或删除用户对活跃会话立即生效不需要等用户重新登录反之令牌里携带的旧角色掩码会被忽略。对应测试TestSimpleAuthAppliesRevokedRolesToExistingSessions把all改成all,^shell后旧会话立刻失去 shell、TestSimpleAuthRejectsTokenForUnknownUser用户被删除后其令牌按未认证处理。查询过程与热重载共用同一把互斥锁simple.go 的注释保证重载 users.yml和读取用户不会互相竞争。4. 无密码用户的登录页保护。若数据库里没有任何带密码的用户PasswordLoginEnabled返回 false登录页就不渲染密码表单simple.go而AnyPassword在读取失败时放行仍显示表单避免瞬时读文件错误把运维人员彻底锁在外面。小结simple 认证给 Dozzle 带来了一份自管的用户数据库加一套细粒度授权用--auth-provider simple开启users.yml优先于users.yaml缺失则启动失败日志会标明读取的文件用docker run -it --rm amir20/dozzle generate user --password ...生成 bcryptcost 11哈希加载阶段严格校验哈希长度、过滤器语法与账号唯一性--auth-ttl仅s/m/h把默认关浏览器即失效的会话 Cookie 延长为固定时长每个用户可独立设置filter覆盖全局--filter与roles位掩码权限支持逗号/竖线/JSON 数组、dozzle_别名、^排除none不可取反底层用持久化随机密钥 稳定排序用户摘要派生 JWT 签名密钥配合按请求的用户解析与 mtime 热重载实现了改配置即生效、重启不丢会话、改密码即作废旧会话的安全语义。如果你还需要接入 GitHub/OIDC 登录复用同一份users.yml、forward-proxy 透传或纯 OIDC 用户数据库可继续阅读 认证总览 与 OAuth 登录指南。【免费下载链接】dozzleRealtime log viewer for containers. Supports Docker, Swarm and K8s.项目地址: https://gitcode.com/GitHub_Trending/do/dozzle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考