18.9.5 权限与凭据安全最佳实践

权限与凭据是 Jenkins 安全基线,实践应覆盖授权模型、凭据全生命周期与审计三条主线,并配合系统与网络加固,确保“最小权限 + 最小暴露 + 可审计”。

原理草图:权限与凭据流转

文章图片

1) 授权模型与配置示例#

推荐启用 MatrixRole-Based Strategy,禁止匿名访问与过度授权,并将权限隔离到 Folder/Job 级别。

安装与启用 Role Strategy 插件(示例)

# 在插件管理中安装: Role-based Authorization Strategy
# 也可通过脚本化方式(Jenkins Script Console)自动安装
# 进入: Manage Jenkins -> Script Console

def instance = Jenkins.getInstance()
def pm = instance.getPluginManager()
def uc = instance.getUpdateCenter()
def plugin = pm.getPlugin("role-strategy")
if (plugin == null) {
  uc.getPlugin("role-strategy").deploy()
  println("role-strategy plugin deploying...")
} else {
  println("role-strategy already installed")
}

配置角色与权限(Groovy 示例)

// Script Console: 创建全局/项目角色并绑定用户
import com.michelin.cio.hudson.plugins.rolestrategy.*
import hudson.security.*

def strategy = new RoleBasedAuthorizationStrategy()
Jenkins.instance.setAuthorizationStrategy(strategy)

def adminRole = new Role("admin", ".*")
adminRole.add(Permission.fromId("hudson.model.Hudson.Administer"))

def devRole = new Role("dev", "project-.*")
devRole.add(Permission.fromId("hudson.model.Item.Read"))
devRole.add(Permission.fromId("hudson.model.Item.Build"))
devRole.add(Permission.fromId("hudson.model.Item.Workspace"))

def readRole = new Role("read", ".*")
readRole.add(Permission.fromId("hudson.model.Item.Read"))

strategy.addRole(RoleBasedAuthorizationStrategy.GLOBAL, adminRole)
strategy.addRole(RoleBasedAuthorizationStrategy.PROJECT, devRole)
strategy.addRole(RoleBasedAuthorizationStrategy.PROJECT, readRole)

strategy.assignRole(RoleBasedAuthorizationStrategy.GLOBAL, adminRole, "admin")
strategy.assignRole(RoleBasedAuthorizationStrategy.PROJECT, devRole, "dev_user")
strategy.assignRole(RoleBasedAuthorizationStrategy.PROJECT, readRole, "auditor")

Jenkins.instance.save()
println("Roles configured.")

排错要点
- 若用户无法访问某 Job,检查 Job 名匹配正则Folder 继承
- Permission.fromId 不存在时报错,确认 Jenkins 核心版本与插件版本兼容。

2) 凭据安全:存储、绑定与掩码#

统一进入 Credentials Store,避免脚本/日志明文。按 System/Folder/Job 作用域分层。

添加 SSH Key 凭据(示例)

// Script Console: 添加全局 SSH 凭据
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.jenkins.plugins.sshcredentials.impl.*

def store = Jenkins.instance.getExtensionList(
  'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()

def key = new BasicSSHUserPrivateKey(
  CredentialsScope.GLOBAL,
  "git-ssh",
  "git",
  new BasicSSHUserPrivateKey.UsersPrivateKeySource(),
  null,
  "git ssh key"
)
store.addCredentials(Domain.global(), key)
println("SSH credential added.")

Pipeline 中使用凭据(含掩码)

pipeline {
  agent any
  stages {
    stage('Checkout') {
      steps {
        sshagent (credentials: ['git-ssh']) {
          sh 'git clone git@github.com:org/repo.git'
        }
      }
    }
    stage('Publish') {
      steps {
        withCredentials([usernamePassword(
          credentialsId: 'registry-cred',
          usernameVariable: 'REG_USER',
          passwordVariable: 'REG_PASS'
        )]) {
          sh '''
            echo "Logging in registry..."
            echo "$REG_PASS" | docker login -u "$REG_USER" --password-stdin registry.example.com
          '''
        }
      }
    }
  }
}

排错要点
- “Credentials not found” 多因作用域不匹配(Folder vs Global)。
- 日志中出现明文,检查是否使用 withCredentials/sshagent,并确保未 echo 密文。

3) 系统与网络加固示例#

禁止以 root 运行 & HTTPS 反代

# systemd 服务用户最小化
sudo sed -i 's/^User=.*/User=jenkins/' /etc/systemd/system/jenkins.service
sudo systemctl daemon-reload && sudo systemctl restart jenkins

# Nginx 反代 + HTTPS 安全头 (片段)
cat >/etc/nginx/conf.d/jenkins.conf <<'EOF'
server {
  listen 443 ssl;
  server_name jenkins.example.com;
  ssl_certificate /etc/ssl/certs/jenkins.crt;
  ssl_certificate_key /etc/ssl/private/jenkins.key;

  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  add_header Content-Security-Policy "default-src 'self'";

  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto https;
  }
}
EOF
nginx -t && systemctl reload nginx

4) 审计与告警示例#

开启安全日志(Jenkins 系统日志)

// Script Console: 添加安全相关日志记录器
import java.util.logging.*
def logger = Logger.getLogger("hudson.security")
logger.setLevel(Level.FINE)
println("Security logger set to FINE")

告警思路(Prometheus/日志平台)
- 关键事件:异常登录、权限提升、凭据频繁读取。
- 规则示例:同一用户 5 分钟内多次失败登录告警。

5) 典型故障与修复清单#

  • 无法登录/权限丢失:检查 config.xmlauthorizationStrategysecurityRealm 是否被覆盖。
  • 凭据消失:检查 credentials.xml 是否损坏,必要时从备份恢复并重启。
  • 节点绕过权限:限制 Agent 标签与执行权限,禁止不可信节点执行敏感任务。

修复示例:回滚安全配置

# 备份后替换 config.xml
cp /var/lib/jenkins/config.xml /var/lib/jenkins/config.xml.bak
cp /var/lib/jenkins/backup/config.xml /var/lib/jenkins/config.xml
chown jenkins:jenkins /var/lib/jenkins/config.xml
systemctl restart jenkins

6) 练习题(可操作)#

  1. 创建 dev 角色,仅允许构建 project-* 的 Job,验证 auditor 只读访问。
  2. 在 Folder 级别创建凭据并在 Pipeline 中使用,确认日志无明文。
  3. 将 Jenkins 置于 Nginx 反代 HTTPS 下,访问 https://jenkins.example.com 验证安全头。