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的安全配置,包括:

  1. 安全概述:了解Jenkins面临的安全威胁和防护框架
  2. 身份认证:配置内置用户管理、LDAP集成和单点登录
  3. 访问控制:实施矩阵授权、基于角色的授权和项目级权限
  4. 凭据管理:安全地存储和使用各种类型的凭据
  5. 网络安全:配置HTTPS、防火墙和网络隔离
  6. 安全监控:实施审计日志、威胁检测和安全监控
  7. 最佳实践:建立安全配置检查清单和运维流程

安全是Jenkins部署中最重要的考虑因素之一,需要从多个层面进行综合防护。

下一章预告

下一章我们将学习Jenkins的Pipeline基础,包括声明式和脚本式Pipeline的语法、最佳实践和高级用法。

练习与思考

理论练习

  1. 安全威胁分析

    • 分析Jenkins环境中可能面临的主要安全威胁
    • 设计相应的防护措施
    • 评估不同威胁的风险等级
  2. 权限设计

    • 为一个包含开发、测试、运维团队的组织设计权限方案
    • 考虑项目隔离和最小权限原则
    • 设计权限审查流程

实践练习

  1. 安全配置实施

    • 在测试环境中实施完整的安全配置
    • 配置LDAP认证和基于角色的授权
    • 测试不同用户的访问权限
  2. 安全监控部署

    • 部署安全监控系统
    • 配置威胁检测规则
    • 测试安全警报机制

思考题

  1. 安全与便利性平衡

    • 如何在保证安全的前提下提供良好的用户体验?
    • 哪些安全措施可能影响开发效率?
    • 如何制定合理的安全策略?
  2. 零信任架构

    • 如何在Jenkins中实施零信任安全模型?
    • 需要考虑哪些技术和流程改进?
    • 如何验证零信任实施的效果?