Skip to content

OpenID Connect ​

使用 --auth-provider oidc 时,你的身份提供方就是用户数据库。这种模式下 Dozzle 从不读取 users.yml:用户是谁、拥有哪些角色、能看到哪些容器,全部来自 OpenID Connect 令牌。在 Keycloak、Authentik、Zitadel 或 Pocket ID 里添加一个用户,他就能登录;把他的角色移除,他就登不进来。

这和在 simple 验证方式下让 users.yml 里的用户用 OIDC 登录是两回事。那种情况下提供方只负责证明你是谁,你能得到什么仍由 users.yml 决定。想手动列出每一个用户就选 simple,想让提供方掌管用户列表就选 oidc。

最小配置 ​

在你的身份提供方那里把 Dozzle 注册为机密客户端,并把重定向 URI 设为:

https://your-dozzle-host/api/auth/callback

如果 Dozzle 部署在某个基础路径下,这里也要带上它,例如 https://example.com/dozzle/api/auth/callback。然后把 Dozzle 指向 issuer:

sh
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle --auth-provider oidc --auth-oidc-issuer https://keycloak.example.com/realms/main --auth-oidc-client-id dozzle --auth-oidc-client-secret secret
yaml
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: oidc
      DOZZLE_AUTH_OIDC_ISSUER: https://keycloak.example.com/realms/main
      DOZZLE_AUTH_OIDC_CLIENT_ID: dozzle
      DOZZLE_AUTH_OIDC_CLIENT_SECRET: secret

这就是全部配置。对常见的令牌布局来说不需要设置任何 claim 路径,DOZZLE_AUTH_OIDC_NAME 也只是改变登录按钮上的文字。client secret 同样接受一个 _FILE 形式的对应变量,参见用 Docker secrets 保存 client secret。

issuer URL 就是提供 /.well-known/openid-configuration 的那个地址。Dozzle 会拉取这份文档来找到授权端点、令牌端点和 userinfo 端点;如果文档里声明的 issuer 和你配置的不一致,Dozzle 会拒绝发起登录流程。

角色 ​

用户的角色从令牌中读取。Dozzle 会按顺序尝试下面这些 claim,并取第一个存在的:

  1. dozzle_roles
  2. resource_access.<client-id>.roles
  3. roles

client id 已经配置过了,所以当 DOZZLE_AUTH_OIDC_CLIENT_ID=dozzle 时,第二条路径就是 resource_access.dozzle.roles,这正是 Keycloak 存放客户端角色的地方。对这种布局不需要再设置任何东西。

如果你的角色放在别的地方,DOZZLE_AUTH_OIDC_ROLES_CLAIM 会用你给出的那一条点分路径取代整个搜索过程:

yaml
DOZZLE_AUTH_OIDC_ROLES_CLAIM: realm_access.roles

每个 claim 都会先在 ID 令牌里查找,再到 userinfo 响应里查找,所以你的提供方把它放在两者中的哪一个并不重要。接受三种形式:字符串数组、用逗号或空格分隔的单个字符串,以及以角色名为键的对象,最后这种是 Zitadel 编码项目角色的方式。

角色名和 users.yml 里的一样:shell、actions、download、notifications、cloud 和 all,并且可以用 ^ 做排除,所以 all,^shell 授予除 shell 访问之外的全部权限。带 dozzle_ 前缀的名称同样被接受,这在提供方用同一个角色 claim 服务多个应用时很有用。每个角色解锁什么,参见角色。

WARNING

groups 是有意不在这个列表里的。在 Authentik 或 Google 那里,每个用户都至少属于一个组,搜索它会把"拒绝登录"变成"登录成功并能读取所有容器"。如果你手头只有用户组,请在提供方那里把它们映射成一个 dozzle_roles claim,参见下面的示例。

请求额外的 scope ​

Dozzle 向提供方请求 openid、profile 和 email 这三个 scope。大多数提供方通过 mapper 把角色 claim 挂到客户端上,所以这样就够了。但有些提供方,比如 Authelia,只有在请求了 claim 所属的 scope 时才会给出这个 claim。DOZZLE_AUTH_OIDC_SCOPES 会把 scope 加进请求里,多个之间用逗号分隔:

yaml
DOZZLE_AUTH_OIDC_SCOPES: dozzle_roles

三个默认 scope 始终会被请求,无法去掉。提供方必须允许该客户端请求这个额外的 scope,否则会以 invalid_scope 拒绝登录。

登录被拒绝时 ​

当这些 claim 一个都不存在,或者第一个存在的 claim 为空时,登录会被拒绝。日志会列出尝试过的路径:

WRN OIDC login rejected: no roles claim found in the ID token or userinfo, or it was empty sub=... tried="dozzle_roles, resource_access.dozzle.roles, roles"

claim 存在但里面没有任何 Dozzle 能识别为角色的内容,则是另一种情况。这样的用户会以无权限的身份登录,和 users.yml 里的 roles: none 一样:他可以读取过滤器允许的那些容器的日志,仅此而已。Keycloak 的 realm 角色就是这种表现,因为那里的每个用户都带有 offline_access 和 uma_authorization,所以下面的示例改用客户端角色。

过滤器 ​

容器过滤器的工作方式相同,从 dozzle_filters、resource_access.<client-id>.filters 和 filters 中第一个存在的读取,或者从 DOZZLE_AUTH_OIDC_FILTERS_CLAIM 指定的那一条路径读取。每个值就是一个过滤器,语法和 users.yml 一样,例如 label=com.example.app 或 name=web:

json
"resource_access": {
  "dozzle": {
    "roles": ["shell", "actions"],
    "filters": ["label=com.example.app"]
  }
}

没有过滤器 claim 的用户可以看到这个 Dozzle 实例能看到的所有容器。无法解析的过滤器会让登录失败,而不是被丢弃,因此提供方那边的一个笔误不会悄悄扩大某人的可见范围。

身份 ​

sub claim 是用户的稳定标识。它是 /data 下配置目录的键,所以即使用户名或邮箱在提供方那里发生了变化,设置也会跟着这个人走。菜单里显示的名字取 name,没有则依次回退到 preferred_username、email、sub。email 和 picture 用于生成头像,如果提供方发来了 picture URL,就使用它。Dozzle 会自己去获取这张图片,而且只从公网地址获取,所以托管在私有网络上的图片会回退到 Gravatar。

和 simple 验证方式不同,这里的邮箱不需要经过验证。它只用于显示,从不拿来和任何东西匹配,所以不授予 email scope 的 issuer 也能正常工作。

会话 ​

登录之后,Dozzle 会签发自己的会话 cookie,其中带着从令牌里读到的角色和过滤器。它们在每个请求上都会生效,但不会被重新拉取:在提供方那里更改角色,要到用户下次登录才会生效。如果这个间隔对你很重要,把 --auth-ttl 设成 8h 之类的值,让会话过期后从新的令牌重新建立。

登出 ​

登出会清除 Dozzle 的会话,并同时把用户从提供方那里登出。Dozzle 从 discovery 文档中读取提供方的 end_session_endpoint,并把签发该会话时的 ID token 交回给它,因此提供方不会要求确认,浏览器会回到 Dozzle 的登录页。Dozzle 这边不需要做任何设置。

在提供方那里把下面的地址登记为 post logout redirect URI,如果 Dozzle 运行在某个基础路径下,要带上该路径:

https://your-dozzle-host/login

ID token 保存在 /data 下,直到用户登出。如果容器在 /data 没有挂载卷的情况下被重新创建,之前的会话仍然可以登出,但提供方会要求确认。

--auth-logout-url 只用于登出地址不在 discovery 文档里的提供方。设置成其他 URL 时,浏览器会改为被送到那里。

和 simple 有什么不同 ​

两种验证方式共用 --auth-oidc-* 这组标志,所以区别体现在行为上:

  • 从不读取 users.yml。如果 /data 下存在这个文件,Dozzle 会在日志里说明它被忽略了。
  • 没有密码表单,也没有 /api/token 端点。身份提供方是唯一的入口,所以一个填错的回调 URL 或一个过期的 client secret 会把所有人挡在外面,直到问题被修复。
  • --auth-github-* 会在启动时报错。GitHub 不是 OpenID Connect issuer,也不发布任何可以用来读取角色的 claim。
  • 启动时会在日志里打印 issuer 以及将从哪些 claim 路径读取角色。

提供方示例 ​

Keycloak ​

在你的 realm 里创建一个名为 dozzle 的客户端,开启 client authentication,并添加上面的重定向 URI,再在 Valid post logout redirect URIs 下添加登出一节中的登录 URL。然后在客户端的 Roles 标签页下创建你想分发的客户端角色:shell、actions、download、notifications、cloud 或 all。在 Role mapping 下把它们分配给用户或用户组。

Keycloak 以 resource_access.<client-id>.roles 的形式输出客户端角色,Dozzle 本来就会搜索这条路径。检查客户端的 Client scopes,打开专属 scope,确认 client roles 这个 mapper 会把该 claim 加进 ID 令牌或 userinfo;Dozzle 会读这两者,但不读 access token。

至于过滤器,添加一个名为 dozzle_filters 的用户属性,并在专属 scope 上添加一个 User Attribute mapper,令牌 claim 名称保持一致,并开启 Multivalued。每个值就是一个过滤器,例如 label=com.example.app。

Authentik ​

在 Customization → Property Mappings 下添加一个 scope mapping,根据用户所属的组返回角色,然后把它挂到 Dozzle 的 provider 上:

python
roles = []
if request.user.ak_groups.filter(name="dozzle-admins").exists():
    roles.append("all")
elif request.user.ak_groups.filter(name="dozzle-users").exists():
    roles.append("download")
return {"dozzle_roles": roles}

两个组都不属于的用户会得到一个空列表,并被拒绝登录。

Authelia ​

Authelia 只有在客户端请求了承载自定义 claim 的 scope 时,才会把这个 claim 放进令牌。把角色定义成一个用户属性,在 claims policy 里把它映射为 dozzle_roles claim,把这个 claim 放进一个 scope,再允许客户端请求这个 scope:

yaml
definitions:
  user_attributes:
    dozzle_roles:
      expression: '"dozzle-admins" in groups ? ["all"] : "dozzle-users" in groups ? ["download"] : []'
identity_providers:
  oidc:
    claims_policies:
      dozzle:
        id_token: ["dozzle_roles"]
        custom_claims:
          dozzle_roles: {}
    scopes:
      dozzle_roles:
        claims: ["dozzle_roles"]
    clients:
      - client_id: dozzle
        claims_policy: dozzle
        scopes: ["openid", "profile", "email", "dozzle_roles"]

然后让 Dozzle 去请求它:

yaml
DOZZLE_AUTH_OIDC_SCOPES: dozzle_roles

两个组都不属于的用户会得到一个空列表,并被拒绝登录。

Zitadel ​

给用户授予以 Dozzle 角色命名的项目角色,并在应用上启用 Assert Roles on Authentication。Zitadel 的角色 claim 是一个以角色名为键的对象,Dozzle 能接受这种形式,所以设置:

yaml
DOZZLE_AUTH_OIDC_ROLES_CLAIM: urn:zitadel:iam:org:project:roles

Google ​

Google 的令牌不带任何角色 claim,所以 oidc 无法配合它使用。请改用带 Google 登录的 simple 验证方式,由 users.yml 决定谁能进来。

基于 MIT 许可证发布。开源项目,由 Docker OSS 赞助。