7.1 安全概述
Jenkins安全威胁
常见安全风险:
访问控制风险:
- 未授权访问Jenkins实例
- 权限提升攻击
- 横向移动攻击
- 敏感信息泄露
代码执行风险:
- 恶意脚本注入
- 构建脚本篡改
- 插件漏洞利用
- 远程代码执行
数据安全风险:
- 凭据泄露
- 源码泄露
- 构建产物泄露
- 配置信息泄露
网络安全风险:
- 中间人攻击
- 网络嗅探
- DDoS攻击
- 内网渗透
安全威胁模型:
外部威胁:
├── 网络攻击者
│ ├── 暴力破解
│ ├── 漏洞利用
│ └── 社会工程学
├── 恶意软件
│ ├── 木马植入
│ ├── 勒索软件
│ └── 挖矿程序
└── 供应链攻击
├── 恶意插件
├── 依赖投毒
└── 镜像污染
内部威胁:
├── 恶意内部人员
│ ├── 权限滥用
│ ├── 数据窃取
│ └── 破坏活动
├── 无意泄露
│ ├── 配置错误
│ ├── 误操作
│ └── 弱密码
└── 特权账户
├── 共享账户
├── 默认密码
└── 过期权限
安全框架
纵深防御策略:
网络层安全:
- 防火墙配置
- VPN访问
- 网络隔离
- 流量监控
应用层安全:
- 身份认证
- 访问控制
- 输入验证
- 输出编码
数据层安全:
- 数据加密
- 备份保护
- 访问审计
- 数据脱敏
运维层安全:
- 安全监控
- 日志审计
- 漏洞管理
- 应急响应
安全合规要求:
行业标准:
- ISO 27001: 信息安全管理体系
- NIST Cybersecurity Framework
- OWASP Top 10
- CIS Controls
法规要求:
- GDPR: 通用数据保护条例
- SOX: 萨班斯-奥克斯利法案
- HIPAA: 健康保险便携性和责任法案
- PCI DSS: 支付卡行业数据安全标准
企业政策:
- 密码策略
- 访问控制策略
- 数据分类策略
- 事件响应策略
7.2 身份认证
内置用户管理
用户数据库配置:
启用步骤:
1. 进入 "Manage Jenkins" → "Configure Global Security"
2. 选择 "Security Realm" → "Jenkins' own user database"
3. 勾选 "Allow users to sign up"
4. 配置密码策略
5. 保存配置
用户注册流程:
1. 访问 /signup 页面
2. 填写用户信息
3. 设置密码
4. 邮箱验证(可选)
5. 管理员审批(可选)
密码策略配置:
// 通过Groovy脚本配置密码策略
import jenkins.model.Jenkins
import hudson.security.HudsonPrivateSecurityRealm
import hudson.security.SecurityRealm
def jenkins = Jenkins.instance
def securityRealm = jenkins.getSecurityRealm()
if (securityRealm instanceof HudsonPrivateSecurityRealm) {
// 设置密码策略
securityRealm.setPasswordPolicy([
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireDigit: true,
requireSymbol: true,
maxAge: 90, // 密码有效期(天)
historySize: 5 // 密码历史记录
])
jenkins.save()
println "密码策略配置完成"
} else {
println "当前未使用Jenkins内置用户数据库"
}
用户管理脚本:
// 批量创建用户
import jenkins.model.Jenkins
import hudson.security.HudsonPrivateSecurityRealm
import hudson.model.User
def jenkins = Jenkins.instance
def securityRealm = jenkins.getSecurityRealm()
if (securityRealm instanceof HudsonPrivateSecurityRealm) {
// 用户列表
def users = [
[username: 'developer1', password: 'Dev@123456', fullName: '开发者1', email: 'dev1@company.com'],
[username: 'developer2', password: 'Dev@123456', fullName: '开发者2', email: 'dev2@company.com'],
[username: 'tester1', password: 'Test@123456', fullName: '测试员1', email: 'test1@company.com'],
[username: 'ops1', password: 'Ops@123456', fullName: '运维员1', email: 'ops1@company.com']
]
users.each { userInfo ->
try {
// 创建用户
def user = securityRealm.createAccount(userInfo.username, userInfo.password)
// 设置用户属性
user.setFullName(userInfo.fullName)
user.addProperty(new hudson.tasks.Mailer.UserProperty(userInfo.email))
user.save()
println "用户 ${userInfo.username} 创建成功"
} catch (Exception e) {
println "用户 ${userInfo.username} 创建失败: ${e.message}"
}
}
jenkins.save()
} else {
println "当前未使用Jenkins内置用户数据库"
}
LDAP集成
LDAP配置:
LDAP服务器配置:
Server: ldap://ldap.company.com:389
Root DN: dc=company,dc=com
User search base: ou=users,dc=company,dc=com
User search filter: (uid={0})
Group search base: ou=groups,dc=company,dc=com
Group search filter: (member={0})
Group membership filter: (memberOf={0})
管理员DN: cn=admin,dc=company,dc=com
管理员密码: [管理员密码]
高级配置:
✓ Enable cache
Cache size: 100
Cache TTL: 300 seconds
✓ Enable StartTLS
LDAP配置脚本:
// LDAP配置脚本
import jenkins.model.Jenkins
import hudson.security.LDAPSecurityRealm
import hudson.security.LDAPSecurityRealm.LDAPConfiguration
def jenkins = Jenkins.instance
// LDAP配置
def ldapConfigurations = [
new LDAPConfiguration(
"ldap://ldap.company.com:389", // server
"dc=company,dc=com", // rootDN
false, // inhibitInferRootDN
"cn=admin,dc=company,dc=com", // managerDN
"admin_password", // managerPasswordSecret
"ou=users,dc=company,dc=com", // userSearchBase
"(uid={0})", // userSearch
"ou=groups,dc=company,dc=com", // groupSearchBase
"(member={0})", // groupSearchFilter
new LDAPSecurityRealm.LDAPGroupMembershipStrategy(), // groupMembershipStrategy
"displayName", // displayNameAttributeName
"mail", // mailAddressAttributeName
true, // disableMailAddressResolver
true, // cache
null, // environmentProperties
null, // extraEnvVars
LDAPSecurityRealm.DescriptorImpl.DEFAULT_CACHE_SIZE, // cacheSize
LDAPSecurityRealm.DescriptorImpl.DEFAULT_CACHE_TTL // cacheTTL
)
]
// 创建LDAP安全域
def ldapSecurityRealm = new LDAPSecurityRealm(
ldapConfigurations,
false, // disableMailAddressResolver
null, // groupIdStrategy
null // userIdStrategy
)
// 应用配置
jenkins.setSecurityRealm(ldapSecurityRealm)
jenkins.save()
println "LDAP配置完成"
LDAP测试脚本:
// LDAP连接测试
import javax.naming.Context
import javax.naming.directory.DirContext
import javax.naming.directory.InitialDirContext
import javax.naming.directory.SearchControls
import javax.naming.directory.SearchResult
def testLDAPConnection() {
def env = new Hashtable<String, String>()
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory")
env.put(Context.PROVIDER_URL, "ldap://ldap.company.com:389")
env.put(Context.SECURITY_AUTHENTICATION, "simple")
env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=company,dc=com")
env.put(Context.SECURITY_CREDENTIALS, "admin_password")
try {
DirContext ctx = new InitialDirContext(env)
// 搜索用户
def searchControls = new SearchControls()
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE)
def results = ctx.search("ou=users,dc=company,dc=com", "(uid=testuser)", searchControls)
if (results.hasMore()) {
SearchResult result = results.next()
println "找到用户: ${result.getName()}"
def attributes = result.getAttributes()
attributes.getAll().each { attr ->
println " ${attr.getID()}: ${attr.get()}"
}
} else {
println "未找到测试用户"
}
ctx.close()
println "LDAP连接测试成功"
} catch (Exception e) {
println "LDAP连接测试失败: ${e.message}"
}
}
testLDAPConnection()
单点登录(SSO)
SAML配置:
<!-- SAML配置示例 -->
<saml2:EntityDescriptor xmlns:saml2="urn:oasis:names:tc:SAML:2.0:metadata"
entityID="http://jenkins.company.com/securityRealm/finishLogin">
<saml2:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<!-- 签名证书 -->
<saml2:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>
<!-- 证书内容 -->
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</saml2:KeyDescriptor>
<!-- 加密证书 -->
<saml2:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>
<!-- 证书内容 -->
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</saml2:KeyDescriptor>
<!-- 断言消费服务 -->
<saml2:AssertionConsumerService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://jenkins.company.com/securityRealm/finishLogin"
index="0" isDefault="true"/>
</saml2:SPSSODescriptor>
</saml2:EntityDescriptor>
OAuth配置:
// GitHub OAuth配置
import jenkins.model.Jenkins
import org.jenkinsci.plugins.GithubSecurityRealm
def jenkins = Jenkins.instance
// GitHub OAuth配置
def githubSecurityRealm = new GithubSecurityRealm(
"https://github.com", // githubWebUri
"https://api.github.com", // githubApiUri
"your_client_id", // clientID
"your_client_secret", // clientSecret
"read:org,user:email" // oauthScopes
)
// 应用配置
jenkins.setSecurityRealm(githubSecurityRealm)
jenkins.save()
println "GitHub OAuth配置完成"
多因素认证(MFA):
// TOTP (Time-based One-Time Password) 配置
import jenkins.model.Jenkins
import hudson.plugins.otp.OtpSecurityRealm
def jenkins = Jenkins.instance
// 启用TOTP
def otpRealm = new OtpSecurityRealm(
jenkins.getSecurityRealm(), // 包装现有的安全域
true, // 强制启用TOTP
30, // TOTP有效期(秒)
6 // TOTP位数
)
jenkins.setSecurityRealm(otpRealm)
jenkins.save()
println "多因素认证配置完成"
7.3 访问控制
授权策略
矩阵授权策略:
// 配置矩阵授权策略
import jenkins.model.Jenkins
import hudson.security.ProjectMatrixAuthorizationStrategy
import hudson.security.Permission
def jenkins = Jenkins.instance
// 创建矩阵授权策略
def strategy = new ProjectMatrixAuthorizationStrategy()
// 管理员权限
strategy.add(Jenkins.ADMINISTER, "admin")
strategy.add(Jenkins.READ, "admin")
// 开发者权限
strategy.add(Jenkins.READ, "developers")
strategy.add(hudson.model.Item.BUILD, "developers")
strategy.add(hudson.model.Item.CANCEL, "developers")
strategy.add(hudson.model.Item.READ, "developers")
strategy.add(hudson.model.Item.WORKSPACE, "developers")
strategy.add(hudson.model.Run.UPDATE, "developers")
// 测试人员权限
strategy.add(Jenkins.READ, "testers")
strategy.add(hudson.model.Item.BUILD, "testers")
strategy.add(hudson.model.Item.READ, "testers")
strategy.add(hudson.model.View.READ, "testers")
// 只读用户权限
strategy.add(Jenkins.READ, "readonly")
strategy.add(hudson.model.Item.READ, "readonly")
strategy.add(hudson.model.View.READ, "readonly")
// 应用授权策略
jenkins.setAuthorizationStrategy(strategy)
jenkins.save()
println "矩阵授权策略配置完成"
基于角色的授权策略:
// 配置基于角色的授权策略
import jenkins.model.Jenkins
import com.michelin.cio.hudson.plugins.rolestrategy.RoleBasedAuthorizationStrategy
import com.michelin.cio.hudson.plugins.rolestrategy.Role
def jenkins = Jenkins.instance
// 创建角色授权策略
def roleStrategy = new RoleBasedAuthorizationStrategy()
// 全局角色
def globalRoles = [
new Role("admin", "Jenkins管理员", [
Jenkins.ADMINISTER,
Jenkins.READ
] as Set),
new Role("developer", "开发人员", [
Jenkins.READ,
hudson.model.Item.BUILD,
hudson.model.Item.CANCEL,
hudson.model.Item.READ,
hudson.model.Item.WORKSPACE
] as Set),
new Role("viewer", "查看者", [
Jenkins.READ,
hudson.model.Item.READ,
hudson.model.View.READ
] as Set)
]
// 项目角色
def projectRoles = [
new Role("project-admin", "项目管理员", ".*", [
hudson.model.Item.BUILD,
hudson.model.Item.CANCEL,
hudson.model.Item.CONFIGURE,
hudson.model.Item.CREATE,
hudson.model.Item.DELETE,
hudson.model.Item.READ,
hudson.model.Item.WORKSPACE
] as Set),
new Role("project-developer", "项目开发者", "myproject-.*", [
hudson.model.Item.BUILD,
hudson.model.Item.CANCEL,
hudson.model.Item.READ,
hudson.model.Item.WORKSPACE
] as Set)
]
// 添加角色
globalRoles.each { role ->
roleStrategy.addRole(RoleBasedAuthorizationStrategy.GLOBAL, role)
}
projectRoles.each { role ->
roleStrategy.addRole(RoleBasedAuthorizationStrategy.PROJECT, role)
}
// 分配角色给用户/组
roleStrategy.assignRole(RoleBasedAuthorizationStrategy.GLOBAL, "admin", "admin")
roleStrategy.assignRole(RoleBasedAuthorizationStrategy.GLOBAL, "developer", "developers")
roleStrategy.assignRole(RoleBasedAuthorizationStrategy.GLOBAL, "viewer", "readonly")
roleStrategy.assignRole(RoleBasedAuthorizationStrategy.PROJECT, "project-admin", "project-leads")
roleStrategy.assignRole(RoleBasedAuthorizationStrategy.PROJECT, "project-developer", "myproject-team")
// 应用授权策略
jenkins.setAuthorizationStrategy(roleStrategy)
jenkins.save()
println "基于角色的授权策略配置完成"
项目级权限
项目安全配置:
// 为特定项目配置权限
import jenkins.model.Jenkins
import hudson.model.FreeStyleProject
import hudson.security.ProjectMatrixAuthorizationStrategy
import hudson.security.AuthorizationMatrixProperty
def jenkins = Jenkins.instance
def projectName = "sensitive-project"
// 获取项目
def project = jenkins.getItem(projectName)
if (project == null) {
println "项目 ${projectName} 不存在"
return
}
// 创建项目级权限矩阵
def authProperty = new AuthorizationMatrixProperty()
// 项目管理员权限
authProperty.add(hudson.model.Item.CONFIGURE, "project-admin")
authProperty.add(hudson.model.Item.BUILD, "project-admin")
authProperty.add(hudson.model.Item.CANCEL, "project-admin")
authProperty.add(hudson.model.Item.READ, "project-admin")
authProperty.add(hudson.model.Item.WORKSPACE, "project-admin")
authProperty.add(hudson.model.Item.DELETE, "project-admin")
authProperty.add(hudson.model.Run.DELETE, "project-admin")
authProperty.add(hudson.model.Run.UPDATE, "project-admin")
// 开发者权限
authProperty.add(hudson.model.Item.BUILD, "project-developers")
authProperty.add(hudson.model.Item.CANCEL, "project-developers")
authProperty.add(hudson.model.Item.READ, "project-developers")
authProperty.add(hudson.model.Item.WORKSPACE, "project-developers")
// 测试人员权限
authProperty.add(hudson.model.Item.BUILD, "project-testers")
authProperty.add(hudson.model.Item.READ, "project-testers")
// 只读权限
authProperty.add(hudson.model.Item.READ, "project-viewers")
// 应用权限到项目
project.addProperty(authProperty)
project.save()
println "项目 ${projectName} 权限配置完成"
文件夹级权限:
// 配置文件夹权限
import jenkins.model.Jenkins
import com.cloudbees.hudson.plugins.folder.Folder
import hudson.security.AuthorizationMatrixProperty
def jenkins = Jenkins.instance
def folderName = "production"
// 获取文件夹
def folder = jenkins.getItem(folderName)
if (folder == null || !(folder instanceof Folder)) {
println "文件夹 ${folderName} 不存在"
return
}
// 创建文件夹级权限矩阵
def authProperty = new AuthorizationMatrixProperty()
// 生产环境管理员权限
authProperty.add(hudson.model.Item.CONFIGURE, "prod-admin")
authProperty.add(hudson.model.Item.BUILD, "prod-admin")
authProperty.add(hudson.model.Item.CANCEL, "prod-admin")
authProperty.add(hudson.model.Item.CREATE, "prod-admin")
authProperty.add(hudson.model.Item.DELETE, "prod-admin")
authProperty.add(hudson.model.Item.READ, "prod-admin")
authProperty.add(hudson.model.Item.WORKSPACE, "prod-admin")
// 运维人员权限
authProperty.add(hudson.model.Item.BUILD, "ops-team")
authProperty.add(hudson.model.Item.CANCEL, "ops-team")
authProperty.add(hudson.model.Item.READ, "ops-team")
// 开发者只读权限
authProperty.add(hudson.model.Item.READ, "developers")
// 应用权限到文件夹
folder.addProperty(authProperty)
folder.save()
println "文件夹 ${folderName} 权限配置完成"
视图权限
视图访问控制:
// 配置视图权限
import jenkins.model.Jenkins
import hudson.model.ListView
import hudson.security.AuthorizationMatrixProperty
def jenkins = Jenkins.instance
def viewName = "Development"
// 获取视图
def view = jenkins.getView(viewName)
if (view == null) {
println "视图 ${viewName} 不存在"
return
}
// 创建视图级权限矩阵
def authProperty = new AuthorizationMatrixProperty()
// 开发团队权限
authProperty.add(hudson.model.View.CONFIGURE, "dev-leads")
authProperty.add(hudson.model.View.CREATE, "dev-leads")
authProperty.add(hudson.model.View.DELETE, "dev-leads")
authProperty.add(hudson.model.View.READ, "dev-leads")
// 开发者权限
authProperty.add(hudson.model.View.READ, "developers")
// 测试人员权限
authProperty.add(hudson.model.View.READ, "testers")
// 应用权限到视图
view.addProperty(authProperty)
view.save()
println "视图 ${viewName} 权限配置完成"
7.4 凭据管理
凭据类型
用户名密码凭据:
// 创建用户名密码凭据
import jenkins.model.Jenkins
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
import com.cloudbees.plugins.credentials.CredentialsScope
def jenkins = Jenkins.instance
def domain = com.cloudbees.plugins.credentials.domains.Domain.global()
def store = jenkins.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
// 创建凭据
def credentials = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL,
"database-credentials", // ID
"数据库连接凭据", // 描述
"dbuser", // 用户名
"dbpassword" // 密码
)
// 添加凭据
store.addCredentials(domain, credentials)
println "用户名密码凭据创建完成"
SSH密钥凭据:
// 创建SSH密钥凭据
import jenkins.model.Jenkins
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey
import com.cloudbees.plugins.credentials.CredentialsScope
def jenkins = Jenkins.instance
def domain = com.cloudbees.plugins.credentials.domains.Domain.global()
def store = jenkins.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
// 读取私钥文件
def privateKeyFile = new File('/path/to/private/key')
def privateKey = privateKeyFile.text
// 创建SSH密钥凭据
def sshCredentials = new BasicSSHUserPrivateKey(
CredentialsScope.GLOBAL,
"ssh-key-credentials", // ID
"SSH密钥凭据", // 描述
"git", // 用户名
new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(privateKey), // 私钥
"passphrase" // 密钥密码(可选)
)
// 添加凭据
store.addCredentials(domain, sshCredentials)
println "SSH密钥凭据创建完成"
API Token凭据:
// 创建API Token凭据
import jenkins.model.Jenkins
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl
import com.cloudbees.plugins.credentials.CredentialsScope
def jenkins = Jenkins.instance
def domain = com.cloudbees.plugins.credentials.domains.Domain.global()
def store = jenkins.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
// 创建API Token凭据
def tokenCredentials = new StringCredentialsImpl(
CredentialsScope.GLOBAL,
"github-api-token", // ID
"GitHub API Token", // 描述
"ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // Token值
)
// 添加凭据
store.addCredentials(domain, tokenCredentials)
println "API Token凭据创建完成"
证书凭据:
// 创建证书凭据
import jenkins.model.Jenkins
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl
import com.cloudbees.plugins.credentials.CredentialsScope
def jenkins = Jenkins.instance
def domain = com.cloudbees.plugins.credentials.domains.Domain.global()
def store = jenkins.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
// 读取证书文件
def keystoreFile = new File('/path/to/keystore.p12')
def keystoreBytes = keystoreFile.bytes
// 创建证书凭据
def certCredentials = new CertificateCredentialsImpl(
CredentialsScope.GLOBAL,
"ssl-certificate", // ID
"SSL证书", // 描述
"keystore_password", // 密钥库密码
new CertificateCredentialsImpl.UploadedKeyStoreSource(keystoreBytes) // 证书数据
)
// 添加凭据
store.addCredentials(domain, certCredentials)
println "证书凭据创建完成"
凭据域
创建凭据域:
// 创建特定的凭据域
import jenkins.model.Jenkins
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
import com.cloudbees.plugins.credentials.domains.Domain
import com.cloudbees.plugins.credentials.domains.DomainSpecification
import com.cloudbees.plugins.credentials.domains.HostnameSpecification
import com.cloudbees.plugins.credentials.domains.SchemeSpecification
import com.cloudbees.plugins.credentials.domains.PathSpecification
def jenkins = Jenkins.instance
def store = jenkins.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
// 创建域规范
def specifications = [
new HostnameSpecification("github.com", null), // GitHub域
new SchemeSpecification("https"), // HTTPS协议
new PathSpecification("/api/*", null, false) // API路径
]
// 创建凭据域
def domain = new Domain(
"github-domain", // 域名
"GitHub相关凭据域", // 描述
specifications // 域规范
)
// 添加域
store.addDomain(domain)
println "凭据域创建完成"
域级凭据管理:
// 在特定域中管理凭据
import jenkins.model.Jenkins
import com.cloudbees.plugins.credentials.SystemCredentialsProvider
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl
import com.cloudbees.plugins.credentials.CredentialsScope
import com.cloudbees.plugins.credentials.domains.Domain
def jenkins = Jenkins.instance
def store = jenkins.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore()
// 查找特定域
def domain = store.getDomains().find { it.getName() == "github-domain" }
if (domain == null) {
println "域 'github-domain' 不存在"
return
}
// 在特定域中创建凭据
def credentials = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL,
"github-credentials",
"GitHub用户凭据",
"github_username",
"github_password"
)
// 添加到特定域
store.addCredentials(domain, credentials)
println "凭据已添加到GitHub域"
凭据使用
Pipeline中使用凭据:
// Pipeline中使用各种类型的凭据
pipeline {
agent any
environment {
// 使用用户名密码凭据
DB_CREDENTIALS = credentials('database-credentials')
// 使用API Token
GITHUB_TOKEN = credentials('github-api-token')
}
stages {
stage('使用用户名密码') {
steps {
script {
// 分别访问用户名和密码
echo "数据库用户: ${DB_CREDENTIALS_USR}"
// 注意:不要在日志中输出密码
sh 'echo "连接数据库..."'
// 在脚本中使用
sh """
mysql -h database.company.com \
-u ${DB_CREDENTIALS_USR} \
-p${DB_CREDENTIALS_PSW} \
-e "SELECT 1;"
"""
}
}
}
stage('使用SSH密钥') {
steps {
// 使用SSH密钥进行Git操作
sshagent(['ssh-key-credentials']) {
sh 'git clone git@github.com:company/private-repo.git'
sh 'cd private-repo && git pull'
}
}
}
stage('使用API Token') {
steps {
script {
// 使用API Token调用GitHub API
def response = sh(
script: """
curl -H "Authorization: token ${GITHUB_TOKEN}" \
https://api.github.com/user
""",
returnStdout: true
).trim()
echo "GitHub用户信息: ${response}"
}
}
}
stage('使用证书') {
steps {
// 使用证书进行HTTPS请求
withCredentials([certificate(credentialsId: 'ssl-certificate',
keystoreVariable: 'KEYSTORE',
passwordVariable: 'KEYSTORE_PASSWORD')]) {
sh """
curl --cert ${KEYSTORE}:${KEYSTORE_PASSWORD} \
https://secure-api.company.com/data
"""
}
}
}
stage('使用文件凭据') {
steps {
// 使用文件类型凭据
withCredentials([file(credentialsId: 'config-file', variable: 'CONFIG_FILE')]) {
sh 'cp ${CONFIG_FILE} ./app-config.json'
sh 'cat ./app-config.json'
}
}
}
}
post {
always {
// 清理敏感文件
sh 'rm -f ./app-config.json'
}
}
}
自由风格项目中使用凭据:
// 在构建步骤中使用凭据
import jenkins.model.Jenkins
import hudson.model.FreeStyleProject
import hudson.tasks.Shell
import org.jenkinsci.plugins.credentialsbinding.impl.SecretBuildWrapper
import org.jenkinsci.plugins.credentialsbinding.impl.UsernamePasswordMultiBinding
def jenkins = Jenkins.instance
def project = jenkins.getItem("my-project")
// 添加凭据绑定
def bindings = [
new UsernamePasswordMultiBinding(
"DB_USER", // 用户名环境变量
"DB_PASS", // 密码环境变量
"database-credentials" // 凭据ID
)
]
def wrapper = new SecretBuildWrapper(bindings)
project.getBuildWrappersList().add(wrapper)
// 添加使用凭据的构建步骤
def buildStep = new Shell("""
echo "连接数据库..."
mysql -h database.company.com -u \$DB_USER -p\$DB_PASS -e "SELECT 1;"
""")
project.getBuildersList().add(buildStep)
project.save()
println "项目凭据配置完成"
7.5 网络安全
HTTPS配置
SSL证书配置:
#!/bin/bash
# 生成自签名证书(仅用于测试)
generate_self_signed_cert() {
echo "生成自签名SSL证书..."
# 创建证书目录
mkdir -p /etc/jenkins/ssl
cd /etc/jenkins/ssl
# 生成私钥
openssl genrsa -out jenkins.key 2048
# 生成证书签名请求
openssl req -new -key jenkins.key -out jenkins.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/OU=IT/CN=jenkins.company.com"
# 生成自签名证书
openssl x509 -req -days 365 -in jenkins.csr -signkey jenkins.key -out jenkins.crt
# 生成PKCS12格式证书(Jenkins使用)
openssl pkcs12 -export -in jenkins.crt -inkey jenkins.key -out jenkins.p12 -name jenkins -password pass:changeit
# 设置权限
chown jenkins:jenkins /etc/jenkins/ssl/*
chmod 600 /etc/jenkins/ssl/*
echo "SSL证书生成完成"
}
# 配置Jenkins HTTPS
configure_jenkins_https() {
echo "配置Jenkins HTTPS..."
# 停止Jenkins
systemctl stop jenkins
# 修改Jenkins配置
cat > /etc/default/jenkins << EOF
# Jenkins配置
JENKINS_PORT=-1
JENKINS_HTTPS_PORT=8443
JENKINS_HTTPS_KEYSTORE=/etc/jenkins/ssl/jenkins.p12
JENKINS_HTTPS_KEYSTORE_PASSWORD=changeit
JENKINS_HTTPS_LISTEN_ADDRESS=0.0.0.0
# JVM参数
JAVA_ARGS="-Djava.awt.headless=true -Xmx2g -Xms1g"
JENKINS_ARGS="--webroot=/var/cache/jenkins/war --httpPort=\$JENKINS_PORT --httpsPort=\$JENKINS_HTTPS_PORT --httpsKeyStore=\$JENKINS_HTTPS_KEYSTORE --httpsKeyStorePassword=\$JENKINS_HTTPS_KEYSTORE_PASSWORD --httpsListenAddress=\$JENKINS_HTTPS_LISTEN_ADDRESS"
EOF
# 启动Jenkins
systemctl start jenkins
echo "Jenkins HTTPS配置完成"
echo "访问地址: https://jenkins.company.com:8443"
}
# 配置反向代理(Nginx)
configure_nginx_proxy() {
echo "配置Nginx反向代理..."
cat > /etc/nginx/sites-available/jenkins << EOF
server {
listen 80;
server_name jenkins.company.com;
# 重定向到HTTPS
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl http2;
server_name jenkins.company.com;
# SSL配置
ssl_certificate /etc/ssl/certs/jenkins.company.com.crt;
ssl_certificate_key /etc/ssl/private/jenkins.company.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
# 代理配置
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header X-Forwarded-Port \$server_port;
# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲设置
proxy_buffering off;
proxy_request_buffering off;
}
# 静态资源缓存
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)\$ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host \$host;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
EOF
# 启用站点
ln -sf /etc/nginx/sites-available/jenkins /etc/nginx/sites-enabled/
# 测试配置
nginx -t
# 重载Nginx
systemctl reload nginx
echo "Nginx反向代理配置完成"
}
# 执行配置
generate_self_signed_cert
configure_jenkins_https
configure_nginx_proxy
防火墙配置
iptables规则:
#!/bin/bash
# Jenkins防火墙配置脚本
configure_jenkins_firewall() {
echo "配置Jenkins防火墙规则..."
# 清空现有规则
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
# 设置默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# 允许本地回环
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# 允许已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 允许SSH(管理访问)
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
# 允许HTTP/HTTPS(仅来自特定网段)
iptables -A INPUT -p tcp --dport 80 -s 10.0.0.0/8 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -s 10.0.0.0/8 -m state --state NEW -j ACCEPT
# 允许Jenkins端口(仅来自内网)
iptables -A INPUT -p tcp --dport 8080 -s 192.168.0.0/16 -m state --state NEW -j ACCEPT
iptables -A INPUT -p tcp --dport 8443 -s 192.168.0.0/16 -m state --state NEW -j ACCEPT
# 允许Jenkins代理连接(JNLP端口)
iptables -A INPUT -p tcp --dport 50000 -s 192.168.0.0/16 -m state --state NEW -j ACCEPT
# 限制连接频率(防止暴力破解)
iptables -A INPUT -p tcp --dport 22 -m recent --name ssh --set
iptables -A INPUT -p tcp --dport 22 -m recent --name ssh --rcheck --seconds 60 --hitcount 4 -j DROP
iptables -A INPUT -p tcp --dport 80 -m recent --name http --set
iptables -A INPUT -p tcp --dport 80 -m recent --name http --rcheck --seconds 60 --hitcount 20 -j DROP
iptables -A INPUT -p tcp --dport 443 -m recent --name https --set
iptables -A INPUT -p tcp --dport 443 -m recent --name https --rcheck --seconds 60 --hitcount 20 -j DROP
# 记录被丢弃的包
iptables -A INPUT -j LOG --log-prefix "IPTABLES-DROPPED: " --log-level 4
iptables -A INPUT -j DROP
# 保存规则
iptables-save > /etc/iptables/rules.v4
echo "防火墙规则配置完成"
}
# UFW配置(Ubuntu)
configure_ufw() {
echo "配置UFW防火墙..."
# 重置UFW
ufw --force reset
# 设置默认策略
ufw default deny incoming
ufw default allow outgoing
# 允许SSH
ufw allow ssh
# 允许HTTP/HTTPS(限制来源)
ufw allow from 10.0.0.0/8 to any port 80
ufw allow from 10.0.0.0/8 to any port 443
# 允许Jenkins端口
ufw allow from 192.168.0.0/16 to any port 8080
ufw allow from 192.168.0.0/16 to any port 8443
ufw allow from 192.168.0.0/16 to any port 50000
# 启用UFW
ufw --force enable
# 显示状态
ufw status verbose
echo "UFW防火墙配置完成"
}
# 选择防火墙类型
if command -v ufw >/dev/null 2>&1; then
configure_ufw
else
configure_jenkins_firewall
fi
网络隔离
Docker网络隔离:
# docker-compose.yml - Jenkins网络隔离
version: '3.8'
services:
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins-master
restart: unless-stopped
networks:
- jenkins-internal
- jenkins-external
ports:
- "127.0.0.1:8080:8080" # 仅绑定到本地
- "127.0.0.1:50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
- JAVA_OPTS=-Djenkins.install.runSetupWizard=false
- JENKINS_OPTS=--httpListenAddress=0.0.0.0
security_opt:
- no-new-privileges:true
user: "1000:1000"
nginx:
image: nginx:alpine
container_name: jenkins-proxy
restart: unless-stopped
networks:
- jenkins-external
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/ssl:ro
depends_on:
- jenkins
security_opt:
- no-new-privileges:true
jenkins-agent:
image: jenkins/inbound-agent:latest
container_name: jenkins-agent-1
restart: unless-stopped
networks:
- jenkins-internal
environment:
- JENKINS_URL=http://jenkins:8080
- JENKINS_SECRET=${AGENT_SECRET}
- JENKINS_AGENT_NAME=agent-1
- JENKINS_AGENT_WORKDIR=/home/jenkins/agent
volumes:
- jenkins_agent_workdir:/home/jenkins/agent
depends_on:
- jenkins
security_opt:
- no-new-privileges:true
user: "1000:1000"
networks:
jenkins-internal:
driver: bridge
internal: true # 内部网络,无法访问外网
ipam:
config:
- subnet: 172.20.0.0/16
jenkins-external:
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/16
volumes:
jenkins_home:
driver: local
jenkins_agent_workdir:
driver: local
Kubernetes网络策略:
# jenkins-network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jenkins-network-policy
namespace: jenkins
spec:
podSelector:
matchLabels:
app: jenkins
policyTypes:
- Ingress
- Egress
ingress:
# 允许来自Nginx Ingress的流量
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
# 允许来自Jenkins代理的连接
- from:
- podSelector:
matchLabels:
app: jenkins-agent
ports:
- protocol: TCP
port: 50000
# 允许来自监控系统的连接
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 8080
egress:
# 允许访问Kubernetes API
- to: []
ports:
- protocol: TCP
port: 443
# 允许访问Git仓库
- to: []
ports:
- protocol: TCP
port: 22
- protocol: TCP
port: 443
# 允许访问Docker Registry
- to: []
ports:
- protocol: TCP
port: 443
# 允许DNS解析
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jenkins-agent-network-policy
namespace: jenkins
spec:
podSelector:
matchLabels:
app: jenkins-agent
policyTypes:
- Ingress
- Egress
ingress:
# 拒绝所有入站流量
[]
egress:
# 允许连接到Jenkins主节点
- to:
- podSelector:
matchLabels:
app: jenkins
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 50000
# 允许访问外部资源
- to: []
ports:
- protocol: TCP
port: 22
- protocol: TCP
port: 443
- protocol: TCP
port: 80
# 允许DNS解析
- to: []
ports:
- protocol: UDP
port: 53
7.6 安全监控
审计日志
启用审计日志:
// 配置Jenkins审计日志
import jenkins.model.Jenkins
import hudson.logging.LogRecorder
import hudson.logging.LogRecorder.Target
import java.util.logging.Level
def jenkins = Jenkins.instance
// 创建审计日志记录器
def logRecorder = new LogRecorder("Security Audit")
// 添加日志目标
def targets = [
new Target("hudson.security.SecurityRealm", Level.INFO),
new Target("hudson.security.AuthorizationStrategy", Level.INFO),
new Target("jenkins.security.ApiTokenFilter", Level.INFO),
new Target("hudson.security.csrf.CrumbFilter", Level.INFO),
new Target("org.jenkinsci.plugins.scriptsecurity", Level.INFO),
new Target("hudson.model.User", Level.INFO)
]
targets.each { target ->
logRecorder.targets.add(target)
}
// 添加到Jenkins
jenkins.getLog().getRecorders().put("Security Audit", logRecorder)
jenkins.save()
println "安全审计日志配置完成"
审计事件监听器:
// 自定义审计事件监听器
import hudson.Extension
import hudson.model.listeners.RunListener
import hudson.model.AbstractBuild
import hudson.model.TaskListener
import hudson.model.User
import jenkins.model.Jenkins
import java.util.logging.Logger
import java.util.logging.Level
@Extension
class SecurityAuditListener extends RunListener<AbstractBuild> {
private static final Logger LOGGER = Logger.getLogger(SecurityAuditListener.class.getName())
@Override
void onStarted(AbstractBuild build, TaskListener listener) {
def user = User.current()
def project = build.getProject()
def auditEvent = [
timestamp: new Date(),
event: "BUILD_STARTED",
user: user?.getId() ?: "SYSTEM",
project: project.getName(),
buildNumber: build.getNumber(),
node: build.getBuiltOn()?.getNodeName() ?: "master",
cause: build.getCauses().collect { it.getShortDescription() }.join(", ")
]
LOGGER.info("AUDIT: ${auditEvent}")
// 发送到外部审计系统
sendToAuditSystem(auditEvent)
}
@Override
void onCompleted(AbstractBuild build, TaskListener listener) {
def user = User.current()
def project = build.getProject()
def auditEvent = [
timestamp: new Date(),
event: "BUILD_COMPLETED",
user: user?.getId() ?: "SYSTEM",
project: project.getName(),
buildNumber: build.getNumber(),
result: build.getResult().toString(),
duration: build.getDuration(),
node: build.getBuiltOn()?.getNodeName() ?: "master"
]
LOGGER.info("AUDIT: ${auditEvent}")
// 发送到外部审计系统
sendToAuditSystem(auditEvent)
}
private void sendToAuditSystem(Map auditEvent) {
try {
// 发送到Elasticsearch
def json = new groovy.json.JsonBuilder(auditEvent).toString()
def url = new URL("http://elasticsearch:9200/jenkins-audit/_doc")
def connection = url.openConnection()
connection.setRequestMethod("POST")
connection.setRequestProperty("Content-Type", "application/json")
connection.setDoOutput(true)
connection.getOutputStream().write(json.getBytes("UTF-8"))
def responseCode = connection.getResponseCode()
if (responseCode == 201) {
LOGGER.fine("审计事件已发送到Elasticsearch")
} else {
LOGGER.warning("发送审计事件失败: ${responseCode}")
}
} catch (Exception e) {
LOGGER.log(Level.WARNING, "发送审计事件异常", e)
}
}
}
日志分析脚本:
#!/bin/bash
# Jenkins安全日志分析脚本
analyze_security_logs() {
local log_file="/var/log/jenkins/jenkins.log"
local output_dir="/var/log/jenkins/security-analysis"
mkdir -p "$output_dir"
echo "分析Jenkins安全日志..."
# 分析登录失败
echo "=== 登录失败分析 ===" > "$output_dir/login-failures.txt"
grep -i "authentication failed\|login failed\|invalid credentials" "$log_file" | \
awk '{print $1, $2, $3}' | sort | uniq -c | sort -nr >> "$output_dir/login-failures.txt"
# 分析权限拒绝
echo "=== 权限拒绝分析 ===" > "$output_dir/access-denied.txt"
grep -i "access denied\|permission denied\|unauthorized" "$log_file" | \
awk '{print $1, $2, $3}' | sort | uniq -c | sort -nr >> "$output_dir/access-denied.txt"
# 分析异常IP
echo "=== 异常IP分析 ===" > "$output_dir/suspicious-ips.txt"
grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" "$log_file" | \
sort | uniq -c | sort -nr | head -20 >> "$output_dir/suspicious-ips.txt"
# 分析脚本执行
echo "=== 脚本执行分析 ===" > "$output_dir/script-execution.txt"
grep -i "script\|groovy\|execute" "$log_file" | \
grep -v "INFO" | head -50 >> "$output_dir/script-execution.txt"
# 生成安全报告
generate_security_report "$output_dir"
}
generate_security_report() {
local output_dir="$1"
local report_file="$output_dir/security-report-$(date +%Y%m%d).html"
cat > "$report_file" << EOF
<!DOCTYPE html>
<html>
<head>
<title>Jenkins安全分析报告</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; }
.warning { background-color: #fff3cd; border-color: #ffeaa7; }
.danger { background-color: #f8d7da; border-color: #f5c6cb; }
pre { background-color: #f8f9fa; padding: 10px; overflow-x: auto; }
</style>
</head>
<body>
<h1>Jenkins安全分析报告</h1>
<p>生成时间: $(date)</p>
<div class="section warning">
<h2>登录失败统计</h2>
<pre>$(cat "$output_dir/login-failures.txt")</pre>
</div>
<div class="section danger">
<h2>权限拒绝事件</h2>
<pre>$(cat "$output_dir/access-denied.txt")</pre>
</div>
<div class="section warning">
<h2>可疑IP地址</h2>
<pre>$(cat "$output_dir/suspicious-ips.txt")</pre>
</div>
<div class="section">
<h2>脚本执行记录</h2>
<pre>$(cat "$output_dir/script-execution.txt")</pre>
</div>
</body>
</html>
EOF
echo "安全报告已生成: $report_file"
}
# 执行分析
analyze_security_logs
威胁检测
异常行为检测:
// 异常行为检测脚本
import jenkins.model.Jenkins
import hudson.model.User
import hudson.model.AbstractBuild
import hudson.model.Run
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
class ThreatDetectionService {
private static final Map<String, UserActivity> userActivities = new ConcurrentHashMap<>()
private static final Map<String, AtomicInteger> ipAttempts = new ConcurrentHashMap<>()
static class UserActivity {
String userId
List<LocalDateTime> loginTimes = []
List<String> ipAddresses = []
List<String> projects = []
int failedLogins = 0
LocalDateTime lastActivity
UserActivity(String userId) {
this.userId = userId
this.lastActivity = LocalDateTime.now()
}
}
// 检测暴力破解攻击
static boolean detectBruteForce(String userId, String ipAddress) {
def activity = userActivities.computeIfAbsent(userId, { new UserActivity(it) })
// 检查失败登录次数
if (activity.failedLogins > 5) {
def lastFailTime = activity.lastActivity
def timeDiff = ChronoUnit.MINUTES.between(lastFailTime, LocalDateTime.now())
if (timeDiff < 30) {
sendAlert("暴力破解检测", "用户 ${userId} 在30分钟内失败登录超过5次")
return true
}
}
// 检查IP地址尝试次数
def attempts = ipAttempts.computeIfAbsent(ipAddress, { new AtomicInteger(0) })
if (attempts.incrementAndGet() > 10) {
sendAlert("IP暴力破解检测", "IP地址 ${ipAddress} 尝试登录超过10次")
return true
}
return false
}
// 检测异常登录时间
static boolean detectAbnormalLoginTime(String userId) {
def activity = userActivities.get(userId)
if (activity == null) return false
def now = LocalDateTime.now()
def hour = now.getHour()
// 检测非工作时间登录(晚上10点到早上6点)
if (hour >= 22 || hour <= 6) {
sendAlert("异常登录时间", "用户 ${userId} 在非工作时间 ${now} 登录")
return true
}
return false
}
// 检测异常地理位置
static boolean detectAbnormalLocation(String userId, String ipAddress) {
def activity = userActivities.get(userId)
if (activity == null) return false
// 检查IP地址变化
if (!activity.ipAddresses.contains(ipAddress)) {
activity.ipAddresses.add(ipAddress)
// 如果用户从新的IP地址登录
if (activity.ipAddresses.size() > 1) {
def location = getLocationByIP(ipAddress)
sendAlert("异常登录位置", "用户 ${userId} 从新位置 ${location} (${ipAddress}) 登录")
return true
}
}
return false
}
// 检测权限提升
static boolean detectPrivilegeEscalation(String userId, String action) {
def dangerousActions = [
"ADMINISTER",
"MANAGE_PLUGINS",
"CONFIGURE_UPDATECENTER",
"RUN_SCRIPTS",
"MANAGE_CREDENTIALS"
]
if (dangerousActions.contains(action)) {
def user = User.getById(userId, false)
if (user != null) {
// 检查用户是否应该有这些权限
def hasPermission = Jenkins.instance.hasPermission(user, Jenkins.ADMINISTER)
if (!hasPermission) {
sendAlert("权限提升检测", "用户 ${userId} 尝试执行高权限操作: ${action}")
return true
}
}
}
return false
}
// 检测异常构建行为
static boolean detectAbnormalBuildBehavior(String userId, String projectName) {
def activity = userActivities.computeIfAbsent(userId, { new UserActivity(it) })
// 检查项目访问模式
if (!activity.projects.contains(projectName)) {
activity.projects.add(projectName)
// 如果用户访问了太多不同的项目
if (activity.projects.size() > 20) {
sendAlert("异常项目访问", "用户 ${userId} 访问了过多项目 (${activity.projects.size()})")
return true
}
}
return false
}
// 发送安全警报
static void sendAlert(String type, String message) {
def alertData = [
timestamp: LocalDateTime.now(),
type: type,
message: message,
severity: "HIGH"
]
// 记录到日志
def logger = java.util.logging.Logger.getLogger("SECURITY_ALERT")
logger.warning("SECURITY_ALERT: ${alertData}")
// 发送到监控系统
sendToMonitoringSystem(alertData)
// 发送邮件通知
sendEmailAlert(alertData)
}
static void sendToMonitoringSystem(Map alertData) {
// 发送到Prometheus/Grafana
try {
def json = new groovy.json.JsonBuilder(alertData).toString()
// 实现发送逻辑
} catch (Exception e) {
println "发送监控数据失败: ${e.message}"
}
}
static void sendEmailAlert(Map alertData) {
// 发送邮件警报
try {
def mailSender = Jenkins.instance.getDescriptor("hudson.tasks.Mailer")
// 实现邮件发送逻辑
} catch (Exception e) {
println "发送邮件警报失败: ${e.message}"
}
}
static String getLocationByIP(String ipAddress) {
// 简单的IP地理位置查询
try {
def url = new URL("http://ip-api.com/json/${ipAddress}")
def response = url.text
def json = new groovy.json.JsonSlurper().parseText(response)
return "${json.city}, ${json.country}"
} catch (Exception e) {
return "未知位置"
}
}
}
// 使用示例
def userId = "testuser"
def ipAddress = "192.168.1.100"
// 检测各种威胁
ThreatDetectionService.detectBruteForce(userId, ipAddress)
ThreatDetectionService.detectAbnormalLoginTime(userId)
ThreatDetectionService.detectAbnormalLocation(userId, ipAddress)
ThreatDetectionService.detectPrivilegeEscalation(userId, "ADMINISTER")
ThreatDetectionService.detectAbnormalBuildBehavior(userId, "sensitive-project")
安全监控仪表板
Grafana仪表板配置:
{
"dashboard": {
"id": null,
"title": "Jenkins安全监控",
"tags": ["jenkins", "security"],
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "登录失败次数",
"type": "stat",
"targets": [
{
"expr": "increase(jenkins_login_failures_total[1h])",
"legendFormat": "登录失败"
}
],
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 5},
{"color": "red", "value": 10}
]
}
}
}
},
{
"id": 2,
"title": "权限拒绝事件",
"type": "timeseries",
"targets": [
{
"expr": "rate(jenkins_access_denied_total[5m])",
"legendFormat": "权限拒绝/分钟"
}
]
},
{
"id": 3,
"title": "活跃用户",
"type": "table",
"targets": [
{
"expr": "jenkins_active_users",
"format": "table"
}
]
},
{
"id": 4,
"title": "安全警报",
"type": "logs",
"targets": [
{
"expr": "{job=\"jenkins\"} |= \"SECURITY_ALERT\""
}
]
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"refresh": "30s"
}
}
7.7 安全最佳实践
安全配置检查清单
基础安全配置:
□ 启用全局安全配置
□ 配置强密码策略
□ 启用CSRF保护
□ 配置适当的授权策略
□ 禁用不必要的功能
□ 定期更新Jenkins和插件
□ 配置安全的网络访问
□ 启用HTTPS
□ 配置防火墙规则
□ 设置会话超时
身份认证:
□ 使用企业级认证系统(LDAP/AD)
□ 启用多因素认证
□ 配置单点登录(SSO)
□ 定期审查用户账户
□ 禁用匿名访问
□ 设置账户锁定策略
访问控制:
□ 实施最小权限原则
□ 使用基于角色的访问控制
□ 配置项目级权限
□ 定期审查权限分配
□ 使用文件夹组织项目
□ 限制管理员权限
凭据管理:
□ 使用Jenkins凭据存储
□ 启用凭据加密
□ 定期轮换凭据
□ 使用凭据域隔离
□ 避免在脚本中硬编码凭据
□ 监控凭据使用情况
网络安全:
□ 配置HTTPS/TLS
□ 使用强加密算法
□ 配置网络隔离
□ 实施网络访问控制
□ 使用VPN或专线
□ 监控网络流量
监控和审计:
□ 启用审计日志
□ 配置安全监控
□ 设置安全警报
□ 定期安全评估
□ 实施威胁检测
□ 建立事件响应流程
安全运维流程
日常安全检查:
#!/bin/bash
# Jenkins日常安全检查脚本
daily_security_check() {
echo "开始Jenkins日常安全检查..."
# 检查Jenkins版本
check_jenkins_version
# 检查插件更新
check_plugin_updates
# 检查用户账户
check_user_accounts
# 检查权限配置
check_permissions
# 检查凭据安全
check_credentials
# 检查网络配置
check_network_config
# 检查日志异常
check_log_anomalies
# 生成安全报告
generate_daily_report
}
check_jenkins_version() {
echo "检查Jenkins版本..."
local current_version=$(curl -s http://localhost:8080/api/json | jq -r '.version')
local latest_version=$(curl -s https://api.github.com/repos/jenkinsci/jenkins/releases/latest | jq -r '.tag_name')
if [ "$current_version" != "$latest_version" ]; then
echo "警告: Jenkins版本过旧 (当前: $current_version, 最新: $latest_version)"
else
echo "Jenkins版本正常"
fi
}
check_plugin_updates() {
echo "检查插件更新..."
# 获取有更新的插件列表
local updates=$(curl -s "http://localhost:8080/pluginManager/api/json?depth=1" | \
jq -r '.plugins[] | select(.hasUpdate == true) | .shortName')
if [ -n "$updates" ]; then
echo "发现插件更新:"
echo "$updates"
else
echo "所有插件都是最新版本"
fi
}
check_user_accounts() {
echo "检查用户账户..."
# 检查长期未登录的用户
local inactive_users=$(curl -s "http://localhost:8080/asynchPeople/api/json" | \
jq -r '.users[] | select(.lastChange < (now - 2592000)) | .user.fullName')
if [ -n "$inactive_users" ]; then
echo "发现长期未登录用户:"
echo "$inactive_users"
fi
# 检查管理员账户
local admin_count=$(curl -s "http://localhost:8080/whoAmI/api/json" | \
jq -r '.authorities[] | select(. == "hudson.model.Hudson.Administer")' | wc -l)
if [ "$admin_count" -gt 3 ]; then
echo "警告: 管理员账户过多 ($admin_count)"
fi
}
check_permissions() {
echo "检查权限配置..."
# 检查匿名访问
local anonymous_read=$(curl -s "http://localhost:8080/api/json" | \
jq -r '.useSecurity')
if [ "$anonymous_read" = "false" ]; then
echo "警告: 可能启用了匿名访问"
fi
}
check_credentials() {
echo "检查凭据安全..."
# 检查凭据数量
local cred_count=$(curl -s "http://localhost:8080/credentials/api/json" | \
jq -r '.credentials | length')
echo "当前凭据数量: $cred_count"
# 检查过期凭据(需要自定义逻辑)
# ...
}
check_network_config() {
echo "检查网络配置..."
# 检查HTTPS配置
if curl -s -k https://localhost:8443 >/dev/null 2>&1; then
echo "HTTPS配置正常"
else
echo "警告: HTTPS配置可能有问题"
fi
# 检查防火墙规则
if command -v iptables >/dev/null 2>&1; then
local open_ports=$(iptables -L INPUT -n | grep ACCEPT | grep -E "dpt:(8080|8443|50000)")
if [ -n "$open_ports" ]; then
echo "检测到开放的Jenkins端口"
fi
fi
}
check_log_anomalies() {
echo "检查日志异常..."
local log_file="/var/log/jenkins/jenkins.log"
# 检查错误日志
local error_count=$(grep -c "ERROR" "$log_file" 2>/dev/null || echo 0)
if [ "$error_count" -gt 10 ]; then
echo "警告: 发现大量错误日志 ($error_count)"
fi
# 检查安全相关日志
local security_events=$(grep -c -i "security\|authentication\|authorization" "$log_file" 2>/dev/null || echo 0)
echo "安全相关事件: $security_events"
}
generate_daily_report() {
echo "生成日常安全报告..."
local report_file="/var/log/jenkins/security-check-$(date +%Y%m%d).txt"
{
echo "Jenkins日常安全检查报告"
echo "检查时间: $(date)"
echo "==========================================="
echo
# 这里可以添加更详细的报告内容
} > "$report_file"
echo "报告已生成: $report_file"
}
# 执行日常检查
daily_security_check
本章小结
本章详细介绍了Jenkins的安全配置,包括:
- 安全概述:了解Jenkins面临的安全威胁和防护框架
- 身份认证:配置内置用户管理、LDAP集成和单点登录
- 访问控制:实施矩阵授权、基于角色的授权和项目级权限
- 凭据管理:安全地存储和使用各种类型的凭据
- 网络安全:配置HTTPS、防火墙和网络隔离
- 安全监控:实施审计日志、威胁检测和安全监控
- 最佳实践:建立安全配置检查清单和运维流程
安全是Jenkins部署中最重要的考虑因素之一,需要从多个层面进行综合防护。
下一章预告
下一章我们将学习Jenkins的Pipeline基础,包括声明式和脚本式Pipeline的语法、最佳实践和高级用法。
练习与思考
理论练习
安全威胁分析:
- 分析Jenkins环境中可能面临的主要安全威胁
- 设计相应的防护措施
- 评估不同威胁的风险等级
权限设计:
- 为一个包含开发、测试、运维团队的组织设计权限方案
- 考虑项目隔离和最小权限原则
- 设计权限审查流程
实践练习
安全配置实施:
- 在测试环境中实施完整的安全配置
- 配置LDAP认证和基于角色的授权
- 测试不同用户的访问权限
安全监控部署:
- 部署安全监控系统
- 配置威胁检测规则
- 测试安全警报机制
思考题
安全与便利性平衡:
- 如何在保证安全的前提下提供良好的用户体验?
- 哪些安全措施可能影响开发效率?
- 如何制定合理的安全策略?
零信任架构:
- 如何在Jenkins中实施零信任安全模型?
- 需要考虑哪些技术和流程改进?
- 如何验证零信任实施的效果?