Skip to content

09 多租户 RBAC 权限管理

基于 Role-Based Authorization Strategy 插件,实现 Jenkins 按项目文件夹隔离的多租户权限控制。 日期:2026-08-25

9.1 概述

在多团队共用一套 Jenkins 的场景下,需要保证:

  • 各团队只能看到和操作自己团队的文件夹及项目
  • 团队内部成员拥有完整的项目构建、配置、删除权限
  • 管理员拥有全局管控能力
  • 其他团队完全不可见(404),而非仅禁止操作

本方案通过 Role-Based Authorization Strategy (RBAS) 插件的 项目角色 (projectRoles) + 正则匹配 实现。

9.2 用户与团队定义

用户全名所属团队用途
adminJenkins 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/Administeradmin 用户 + jenkins-admins
readonly.*Overall/Readauthenticated

关键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-developerdev/.*Job/Build, Job/Cancel, Job/Read, Job/Workspace, Job/Configure, Job/Create, Job/Delete, Run/Delete, Run/Replaydev01 + dev-team
dev-folder^dev$Job/Configure, Job/Create, Job/Delete, Job/Build, Job/Readdev01 + dev-team

test 和 ops 团队的角色结构完全一致,仅替换 devtest / 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 变更并热加载 JCasC

9.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> 条目(USEREITHER 类型共存),导致 UI 显示异常。需手动清理:

bash
# 删除 config.xml 中的 EITHER 类型 SID 条目
kubectl exec -n jenkins jenkins-0 -c jenkins -- \
  sed -i '/<sid type="EITHER">/d' /var/jenkins_home/config.xml

9.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-check

9.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/Readhudson.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 jenkins

9.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 入口路径

方式路径
直接 URLhttp://<jenkins-host>:30880/role-strategy/
菜单导航Manage JenkinsSecurityManage and Assign Roles

进入后页面包含两个子页面:

  • Assign Roles(分配角色):/role-strategy/(主页面)
  • Manage Roles(管理角色):/role-strategy/manage-roles

9.11.2 Assign Roles — 分配角色

用于将用户/组分配到已有的角色上。页面分为三个区域:

Global Roles(全局角色)

显示 adminreadonly 两个全局角色。

操作步骤
给用户分配全局角色adminreadonly 行的 User/Group 列点击 Add User / Add Group,输入用户名
移除用户的全局角色点击用户名旁边的 删除按钮

Project Roles(项目角色)

显示 dev-developerdev-foldertest-developertest-folderops-developerops-folder 六个项目角色。

操作步骤
给用户分配项目角色在对应角色行(如 dev-developer)的 User/Group 列点击 Add User,输入 dev01
给组分配项目角色点击 Add Group,输入组名如 dev-team
移除分配点击用户/组旁边的 删除按钮

Agent Roles(代理角色)

当前为空,用于控制 Jenkins Agent 节点的访问权限。

操作完毕后点击底部的 Save 保存,或 Apply 立即生效。

9.11.3 Manage Roles — 管理角色定义

用于创建/编辑/删除角色定义本身(角色名、正则匹配模式、权限列表)。

全局角色区域

显示 adminreadonly 及其权限矩阵(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 文件夹的只读权限

  1. 进入 Manage Roles → Project Roles → 点击 Add Project Role
  2. 名称:dev-ops-viewer,Pattern:ops/.*
  3. 只勾选 Job → Read
  4. 保存后回到 Assign Roles → Project Roles
  5. dev-ops-viewer 行点击 Add User,输入 dev01
  6. 保存

场景 2:新增 qa 团队

  1. Manage Roles → Project Roles → Add Project Role × 2:
    • qa-developer,Pattern:qa/.*,勾选 Build/Cancel/Read/Workspace/Configure/Create/Delete
    • qa-folder,Pattern:^qa$,勾选 Configure/Create/Delete/Build/Read
  2. Assign Roles → Project Roles → 分别在两个角色行 Add User 输入 qa01
  3. 保存

场景 3:临时禁止 test01 操作 test 文件夹

  1. Assign Roles → Project Roles
  2. test-developertest-folder 行找到 test01,点击 移除
  3. 保存(test01 即刻无法看到 test/ 文件夹)

9.11.5 注意事项

要点说明
Save vs ApplySave 保存到磁盘;Apply 立即生效但不一定持久化
JCasC 冲突Web UI 的修改会写入 config.xml,但不会同步回 ConfigMap。如果 Jenkins 重启或 JCasC reload,ConfigMap 中的配置会覆盖 UI 修改
持久化建议UI 修改后,建议同步更新 JCasC ConfigMap(jenkins-jenkins-config-rbac),确保重启后配置不丢失
Filter 过滤页面顶部有 Filter by User/GroupFilter by Role 搜索框,用户多时可快速定位