主题
09 多租户 RBAC 权限管理
基于 Role-Based Authorization Strategy 插件,实现 Jenkins 按项目文件夹隔离的多租户权限控制。 日期:2026-08-25
9.1 概述
在多团队共用一套 Jenkins 的场景下,需要保证:
- 各团队只能看到和操作自己团队的文件夹及项目
- 团队内部成员拥有完整的项目构建、配置、删除权限
- 管理员拥有全局管控能力
- 其他团队完全不可见(404),而非仅禁止操作
本方案通过 Role-Based Authorization Strategy (RBAS) 插件的 项目角色 (projectRoles) + 正则匹配 实现。
9.2 用户与团队定义
| 用户 | 全名 | 所属团队 | 用途 |
|---|---|---|---|
admin | Jenkins Admin | 全局管理员 | 全局管控 |
dev01 | 开发一号 | 开发团队 (dev-team) | 开发项目构建 |
test01 | 测试一号 | 测试团队 (test-team) | 测试项目构建 |
op01 | 运维一号 | 运维团队 (ops-team) | 运维项目构建 |
9.3 文件夹结构
Jenkins 根目录
├── dev/ ← dev01 专属
│ └── *.jobs ← dev-developer 角色匹配
├── test/ ← test01 专属
│ └── *.jobs ← test-developer 角色匹配
├── ops/ ← op01 专属
│ └── *.jobs ← ops-developer 角色匹配
└── (其他公共项目) ← 仅 admin 可见9.4 角色设计
9.4.1 全局角色 (Global Roles)
| 角色 | 匹配模式 | 权限 | 分配 |
|---|---|---|---|
admin | .* | Overall/Administer | admin 用户 + jenkins-admins 组 |
readonly | .* | Overall/Read | authenticated 组 |
关键:
readonly角色仅包含Overall/Read(允许登录 Jenkins UI),不包含Item.Read。 如果全局角色中包含Item.Read,所有已认证用户将看到全部项目,破坏隔离。
9.4.2 项目角色 (Project Roles)
每个团队分配两组角色:
| 角色 | 匹配模式 | 权限 | 说明 |
|---|---|---|---|
{team}-developer | {team}/.* | Build/Cancel/Read/Workspace/Configure/Create/Delete/Run.Delete/Run.Replay | 匹配文件夹内的项目 |
{team}-folder | ^{team}$ | Configure/Create/Delete/Build/Read | 匹配文件夹本身 |
完整配置(以 dev 团队为例):
| 角色 | Pattern | 权限 | 分配给用户 |
|---|---|---|---|
dev-developer | dev/.* | Job/Build, Job/Cancel, Job/Read, Job/Workspace, Job/Configure, Job/Create, Job/Delete, Run/Delete, Run/Replay | dev01 + dev-team 组 |
dev-folder | ^dev$ | Job/Configure, Job/Create, Job/Delete, Job/Build, Job/Read | dev01 + dev-team 组 |
test 和 ops 团队的角色结构完全一致,仅替换
dev→test/ops。
9.5 JCasC 配置 (ConfigMap)
RBAC 配置位于 ConfigMap jenkins-jenkins-config-rbac,文件键为 rbac.yaml。
9.5.1 完整 YAML
yaml
jenkins:
authorizationStrategy:
roleBased:
roles:
global:
- name: "admin"
description: "Jenkins 全局管理员"
permissions:
- "Overall/Administer"
entries:
- user: "admin"
- group: "jenkins-admins"
- name: "readonly"
description: "已认证用户可登录 Jenkins(不含项目可见性)"
permissions:
- "Overall/Read"
entries:
- group: "authenticated"
items:
# ===== dev 团队 =====
- name: "dev-developer"
description: "dev 团队完整权限"
pattern: "dev/.*"
permissions:
- "Job/Build"
- "Job/Cancel"
- "Job/Read"
- "Job/Workspace"
- "Job/Configure"
- "Job/Create"
- "Job/Delete"
- "Run/Delete"
- "Run/Replay"
entries:
- group: "dev-team"
- user: "dev01"
- name: "dev-folder"
description: "dev 文件夹管理权限"
pattern: "^dev$"
permissions:
- "Job/Configure"
- "Job/Create"
- "Job/Delete"
- "Job/Build"
- "Job/Read"
entries:
- group: "dev-team"
- user: "dev01"
# ===== test 团队 =====
- name: "test-developer"
description: "test 团队完整权限"
pattern: "test/.*"
permissions:
- "Job/Build"
- "Job/Cancel"
- "Job/Read"
- "Job/Workspace"
- "Job/Configure"
- "Job/Create"
- "Job/Delete"
- "Run/Delete"
- "Run/Replay"
entries:
- group: "test-team"
- user: "test01"
- name: "test-folder"
description: "test 文件夹管理权限"
pattern: "^test$"
permissions:
- "Job/Configure"
- "Job/Create"
- "Job/Delete"
- "Job/Build"
- "Job/Read"
entries:
- group: "test-team"
- user: "test01"
# ===== ops 团队 =====
- name: "ops-developer"
description: "ops 团队完整权限"
pattern: "ops/.*"
permissions:
- "Job/Build"
- "Job/Cancel"
- "Job/Read"
- "Job/Workspace"
- "Job/Configure"
- "Job/Create"
- "Job/Delete"
- "Run/Delete"
- "Run/Replay"
entries:
- group: "ops-team"
- user: "op01"
- name: "ops-folder"
description: "ops 文件夹管理权限"
pattern: "^ops$"
permissions:
- "Job/Configure"
- "Job/Create"
- "Job/Delete"
- "Job/Build"
- "Job/Read"
entries:
- group: "ops-team"
- user: "op01"9.5.2 更新方式
bash
# 通过 kubectl patch 更新 ConfigMap
kubectl -n jenkins patch cm jenkins-jenkins-config-rbac --type=merge -p '
{
"data": {
"rbac.yaml": "<完整 YAML 内容>"
}
}'
# k8s-sidecar 自动检测 ConfigMap 变更并热加载 JCasC9.6 运行时立即生效(Groovy Script Console)
JCasC 热加载可能因 CSRF crumb 会话问题失败,此时可通过 Groovy 脚本直接操作内存中的 RBAS 实例。
9.6.1 分配角色到用户
groovy
import jenkins.model.Jenkins
import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy
def jenkins = Jenkins.instance
def rbas = (RoleBasedAuthorizationStrategy) jenkins.getAuthorizationStrategy()
// 分配项目角色到用户
rbas.doAssignRole("projectRoles", "dev-developer", "dev01")
rbas.doAssignRole("projectRoles", "dev-folder", "dev01")
rbas.doAssignRole("projectRoles", "test-developer", "test01")
rbas.doAssignRole("projectRoles", "test-folder", "test01")
rbas.doAssignRole("projectRoles", "ops-developer", "op01")
rbas.doAssignRole("projectRoles", "ops-folder", "op01")
// 保存到磁盘
jenkins.save()
println "Done: all roles assigned"9.6.2 调用方式
bash
# 必须使用 cookie jar 保持 CSRF crumb 会话一致
kubectl exec -n jenkins jenkins-0 -c jenkins -- sh -c '
CRUMB_JSON=$(curl -s -c /tmp/jk.txt -u "admin:PASSWORD" http://localhost:8080/crumbIssuer/api/json)
CRUMB=$(echo "$CRUMB_JSON" | sed "s/.*\"crumb\":\"//" | sed "s/\".*//")
curl -s -b /tmp/jk.txt -u "admin:PASSWORD" \
-H "Jenkins-Crumb: $CRUMB" \
--data-urlencode "script@/tmp/script.groovy" \
http://localhost:8080/scriptText
'注意:
-c(写 cookie)和-b(读 cookie)必须在同一调用链中使用,否则 crumb 校验失败返回 403。
9.6.3 清理重复 SID 条目
通过 Groovy doAssignRole 分配角色时,可能会在 config.xml 中产生重复的 <sid> 条目(USER 和 EITHER 类型共存),导致 UI 显示异常。需手动清理:
bash
# 删除 config.xml 中的 EITHER 类型 SID 条目
kubectl exec -n jenkins jenkins-0 -c jenkins -- \
sed -i '/<sid type="EITHER">/d' /var/jenkins_home/config.xml9.7 隔离验证
9.7.1 验证矩阵
| 用户 | dev/ | test/ | ops/ |
|---|---|---|---|
dev01 | ✅ 200 (完全控制) | ❌ 404 (不可见) | ❌ 404 (不可见) |
test01 | ❌ 404 (不可见) | ✅ 200 (完全控制) | ❌ 404 (不可见) |
op01 | ❌ 404 (不可见) | ❌ 404 (不可见) | ✅ 200 (完全控制) |
admin | ✅ 200 | ✅ 200 | ✅ 200 |
9.7.2 各用户首页可见项目
--- dev01 ---
dev
--- test01 ---
test
--- op01 ---
ops
--- admin ---
dev
test
ops
java-demo
sonarqube-demo
toolchain-check9.7.3 API 验证命令
bash
# 验证某用户是否能看到某文件夹
curl -s -u 'dev01:xxx' -o /dev/null -w '%{http_code}' \
'http://<jenkins-host>:30880/job/dev/api/json'
# 期望: 200 (自己的) / 404 (别人的)
# 验证构建隔离
curl -s -u 'dev01:xxx' -X POST -o /dev/null -w '%{http_code}' \
'http://<jenkins-host>:30880/job/ops/job/some-job/build'
# 期望: 403 (无权操作其他团队项目)
# 列出用户可见的项目
curl -s -u 'dev01:xxx' 'http://<jenkins-host>:30880/api/json' | python3 -c "
import json, sys
data = json.load(sys.stdin)
for j in data.get('jobs', []):
print(j['name'])
"9.8 config.xml 中的权限结构
Jenkins 将 RBAS 配置存储在 /var/jenkins_home/config.xml 中,核心结构如下:
xml
<authorizationStrategy class="com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy">
<permissionTemplates/>
<roleMap type="slaveRoles"/>
<roleMap type="projectRoles">
<role name="dev-developer" pattern="dev/.*">
<permissions>
<permission>hudson.model.Item.Build</permission>
<permission>hudson.model.Item.Cancel</permission>
<permission>hudson.model.Item.Read</permission>
<!-- ... -->
</permissions>
<assignedSIDs>
<sid type="GROUP">dev-team</sid>
<sid type="USER">dev01</sid>
</assignedSIDs>
</role>
<!-- test/ops 角色同理 -->
</roleMap>
<roleMap type="globalRoles">
<role name="admin" pattern=".*">
<permissions>
<permission>hudson.model.Hudson.Administer</permission>
</permissions>
<assignedSIDs>
<sid type="USER">admin</sid>
<sid type="GROUP">jenkins-admins</sid>
</assignedSIDs>
</role>
<role name="readonly" pattern=".*">
<permissions>
<!-- 注意:这里只有 Hudson.Read,没有 Item.Read -->
<permission>hudson.model.Hudson.Read</permission>
</permissions>
<assignedSIDs>
<sid type="GROUP">authenticated</sid>
</assignedSIDs>
</role>
</roleMap>
</authorizationStrategy>9.9 常见陷阱与解决方案
陷阱 1:全局 readonly 角色包含 Item.Read
现象:所有用户都能看到所有文件夹和项目。
原因:JCasC 中 readonly 全局角色包含了 Job/Read(对应 hudson.model.Item.Read),使得 authenticated 组的所有成员都拥有全局项目读取权限。
解决:确保 readonly 角色只有 Overall/Read(hudson.model.Hudson.Read),不包含 Job/Read。
陷阱 2:all-viewer 项目角色泄露可见性
现象:所有用户都能看到 dev/、test/、ops/ 下的所有项目。
原因:存在类似 all-viewer 的项目角色,pattern 为 (dev|test|ops)/.*,分配给 authenticated 组。
解决:删除此类角色,各团队的可见性仅由其专属角色控制。
bash
# 删除 all-viewer 角色
sed -i '/<role name="all-viewer"/,/<\/role>/d' /var/jenkins_home/config.xml陷阱 3:Groovy 分配产生重复 SID
现象:UI 中 Manage Jenkins → Security → Manage and Assign Roles 页面显示异常。
原因:doAssignRole 可能同时写入 <sid type="USER"> 和 <sid type="EITHER">。
解决:清理 EITHER 类型条目。
bash
sed -i '/<sid type="EITHER">/d' /var/jenkins_home/config.xml陷阱 4:CSRF Crumb 会话不一致
现象:调用 Jenkins Script Console API 返回 403。
原因:获取 crumb 和执行脚本不在同一个 HTTP 会话中(cookie 不一致)。
解决:使用 -c(cookie jar 写入)和 -b(cookie jar 读取)保持会话一致。
陷阱 5:修改 config.xml 后未 reload
现象:config.xml 已修改但权限未生效。
原因:Jenkins 在内存中缓存权限策略,磁盘修改不会自动反映。
解决:
bash
# 方式一:Jenkins reload(推荐)
curl -u 'admin:xxx' -X POST 'http://localhost:8080/reload'
# 方式二:重启 Pod
kubectl delete pod jenkins-0 -n jenkins9.10 运维操作
9.10.1 新增团队成员
bash
# 1. 在 Jenkins 中创建用户(Manage Jenkins → Users 或通过 Groovy)
cat > /tmp/create-user.groovy << 'EOF'
import jenkins.model.Jenkins
def u = Jenkins.instance.securityRealm.createAccount("newdev01", "newdev01")
u.setFullName("新开发一号")
u.save()
println "User newdev01 created"
EOF
# 2. 在 JCasC ConfigMap 中添加用户到对应角色
# 在 dev-developer 和 dev-folder 的 entries 中添加:
# - user: "newdev01"
# 3. 或通过 Groovy 立即分配
rbas.doAssignRole("projectRoles", "dev-developer", "newdev01")
rbas.doAssignRole("projectRoles", "dev-folder", "newdev01")
jenkins.save()9.10.2 新增团队文件夹
bash
# 1. 在 JCasC ConfigMap 的 items 部分添加新角色定义
# 例如新增 qa 团队:
# - name: "qa-developer"
# pattern: "qa/.*"
# permissions: [...]
# entries:
# - user: "qa01"
# - name: "qa-folder"
# pattern: "^qa$"
# permissions: [...]
# entries:
# - user: "qa01"
# 2. 在 Jenkins UI 中创建 qa/ 文件夹
# 3. 触发 JCasC reload 或通过 Groovy 分配角色9.10.3 备份与恢复
bash
# 备份
kubectl cp jenkins/jenkins-0:/var/jenkins_home/config.xml ./config.xml.bak
# 恢复
kubectl cp ./config.xml.bak jenkins/jenkins-0:/var/jenkins_home/config.xml -c jenkins
kubectl exec -n jenkins jenkins-0 -c jenkins -- curl -s -u 'admin:xxx' -X POST 'http://localhost:8080/reload'9.11 通过 Web 界面修改多租户权限
Jenkins 内置的 Role-Based Authorization Strategy 插件提供了完整的图形化管理界面,无需编写脚本或编辑配置文件即可完成角色与用户的增删改查。
9.11.1 入口路径
| 方式 | 路径 |
|---|---|
| 直接 URL | http://<jenkins-host>:30880/role-strategy/ |
| 菜单导航 | Manage Jenkins → Security → Manage and Assign Roles |
进入后页面包含两个子页面:
- Assign Roles(分配角色):
/role-strategy/(主页面) - Manage Roles(管理角色):
/role-strategy/manage-roles
9.11.2 Assign Roles — 分配角色
用于将用户/组分配到已有的角色上。页面分为三个区域:
Global Roles(全局角色)
显示 admin 和 readonly 两个全局角色。
| 操作 | 步骤 |
|---|---|
| 给用户分配全局角色 | 在 admin 或 readonly 行的 User/Group 列点击 Add User / Add Group,输入用户名 |
| 移除用户的全局角色 | 点击用户名旁边的 ✕ 删除按钮 |
Project Roles(项目角色)
显示 dev-developer、dev-folder、test-developer、test-folder、ops-developer、ops-folder 六个项目角色。
| 操作 | 步骤 |
|---|---|
| 给用户分配项目角色 | 在对应角色行(如 dev-developer)的 User/Group 列点击 Add User,输入 dev01 |
| 给组分配项目角色 | 点击 Add Group,输入组名如 dev-team |
| 移除分配 | 点击用户/组旁边的 ✕ 删除按钮 |
Agent Roles(代理角色)
当前为空,用于控制 Jenkins Agent 节点的访问权限。
操作完毕后点击底部的 Save 保存,或 Apply 立即生效。
9.11.3 Manage Roles — 管理角色定义
用于创建/编辑/删除角色定义本身(角色名、正则匹配模式、权限列表)。
全局角色区域
显示 admin、readonly 及其权限矩阵(Overall / Credentials / Agent / Job / Run / View / SCM / Metrics)。
| 操作 | 步骤 |
|---|---|
| 修改角色权限 | 勾选/取消勾选权限复选框 |
| 新建全局角色 | 点击 Add Global Role |
| 删除角色 | 点击角色行右侧的 🗑 删除图标 |
项目角色区域
显示所有项目角色及其 Pattern(正则匹配模式)和权限矩阵。
| 操作 | 步骤 |
|---|---|
| 修改匹配模式 | 编辑 Pattern 输入框(如 dev/.* → dev-project/.*) |
| 修改权限 | 勾选/取消 Job / Run / View 等权限列 |
| 新建项目角色 | 点击 Add Project Role,填写名称和 Pattern |
| 删除角色 | 点击角色行右侧的删除图标 |
9.11.4 常见操作示例
场景 1:给 dev01 增加 ops 文件夹的只读权限
- 进入 Manage Roles → Project Roles → 点击 Add Project Role
- 名称:
dev-ops-viewer,Pattern:ops/.* - 只勾选 Job → Read
- 保存后回到 Assign Roles → Project Roles
- 在
dev-ops-viewer行点击 Add User,输入dev01 - 保存
场景 2:新增 qa 团队
- Manage Roles → Project Roles → Add Project Role × 2:
qa-developer,Pattern:qa/.*,勾选 Build/Cancel/Read/Workspace/Configure/Create/Deleteqa-folder,Pattern:^qa$,勾选 Configure/Create/Delete/Build/Read
- Assign Roles → Project Roles → 分别在两个角色行 Add User 输入
qa01 - 保存
场景 3:临时禁止 test01 操作 test 文件夹
- Assign Roles → Project Roles
- 在
test-developer和test-folder行找到test01,点击 ✕ 移除 - 保存(test01 即刻无法看到 test/ 文件夹)
9.11.5 注意事项
| 要点 | 说明 |
|---|---|
| Save vs Apply | Save 保存到磁盘;Apply 立即生效但不一定持久化 |
| JCasC 冲突 | Web UI 的修改会写入 config.xml,但不会同步回 ConfigMap。如果 Jenkins 重启或 JCasC reload,ConfigMap 中的配置会覆盖 UI 修改 |
| 持久化建议 | UI 修改后,建议同步更新 JCasC ConfigMap(jenkins-jenkins-config-rbac),确保重启后配置不丢失 |
| Filter 过滤 | 页面顶部有 Filter by User/Group 和 Filter by Role 搜索框,用户多时可快速定位 |