jenkins06-凭证管理
1、凭证管理介绍
1、为什么要管理凭证
- 众所周知,在Jenkinsfile或部署脚本中使用明文密码会造成安全隐患。但是为什么还频繁出现明文密码被上传到GitHub上的情况呢?笔者认为有两个主要原因(当然,现实的原因可能更多):
- (1)程序员或运维人员不知道如何保护密码。
- (2)管理者没有足够重视,否则会给更多的时间让程序员或运维人员想办法隐藏明文密码。
2、凭证是什么
- 凭证(cridential)是Jenkins进行受限操作时的凭据。比如使用SSH登录远程机器时,用户名和密码或SSH key就是凭证。而这些凭证不可能以明文写在Jenkinsfile中。Jenkins凭证管理指的就是对这些凭证进行管理。
- 凭证可以用来存储需要密文保护的数据库密码、Gitlab密码、Docker私有仓库密码等,以便Jenkins可以和这些应用进行交互。
- 为了最大限度地提高安全性,在Jenkins master节点上对凭证进行加密存储(通过Jenkins实例ID加密),只有通过凭证ID才能在pipeline中使用,并且限制了将证书从一个Jenkins实例复制到另一个Jenkins实例的能力。
- 因为所有的凭证都被存储在Jenkins master上,所以在Jenkins master上最好不要执行任务,以免被pipeline非法读取出来。那么在哪里执行pipeline呢?应该分配到Jenkins agent上执行。
2、创建凭证
2.1、安装插件
- 可以使用"Credentials Binding"插件来管理Jenkins的凭证。
- 安装插件后可以在"Jenkins --> Manage Jenkins"中看到多了两个选项。
2.2、创建凭证
- 在创建凭证前,请确保当前用户有添加凭证的权限。
- Jenkins --> Manage Jenkins --> Manage Credentials --> Stores scoped to Jenkins:[Jenkins] --> 全局凭证 (unrestricted) --> Add Credentials
- Kind:选择凭证类型。
- Username with password:用户名和密码
- SSH Username with private key:使用SSH用户的公私密钥。使用时Jenkins会将SSH key复制到一个临时目录中,再将文件路径设置到一个变量中。
- Secret text:需要保存的一个加密的文本串,如钉钉机器人或Github的API token。
- Secret file:需要保密的文本文件。使用时Jenkins会将文件复制到一个临时目录中,再将文件路径设置到一个变量中,等构建结束后就会被删除。
- Certificate:通过上传证书文件的方式。
- Scope:凭证的作用域。有两种作用域:
- Global,全局作用域。如果凭证用于pipeline,则使用此种作用域。
- System,如果凭证用于Jenkins本身的系统管理,例如电子邮件身份验证、代理连接等,则使用此种作用域。
- ID:在pipeline使用凭证的唯一标识。
- 将凭证绑定到变量
- 方法一:添加凭证后,安装Credentials Binding Plugin(credentials-binding)插件,通过其提供的withCredentials步骤就可以在pipeline中使用凭证了。
- 方法二:声明式pipeline提供了credentials helper方法(只能在environment指令中使用)来简化凭证的使用。比方法一简明。
//方法一 withCredentials([usernameColonPassword(credentialsId: '18729147-da8c-49c5-8573-4bdb05984519', variable: 'BITBUCKET_CREDS')]) { echo "${BITBUCKET_CREDS} } //方法二 environment { BITBUCKET_CREDS = credentials('18729147-da8c-49c5-8573-4bdb05984519') }
3、使用HashiCorp Vault插件管理凭证
- 如果觉得Jenkins的凭证管理功能太弱,无法满足你的需求,则可以考虑使用HashiCorp Vault。
- HashiCorp Vault是一款对敏感信息(密码、token、密钥等)进行存储,并进行访问控制的工具。它不仅可以存储敏感信息,还具有滚动更新、审计等功能。
- HashiCorp Vault插件并没有提供pipeline步骤,提供此步骤的是Hashicorp Vault Pipeline 插件。
- 但是它依赖的是2.138.1或以上的Jenkins版本。如果你的Jenkins版本低于2.138.1,但是又想用Hashicorp Vault Pipeline插件,可以将该插件的源码下载到本地,将pom.xml中的jenkins.version值从2.138.1修改成你的Jenkins版本,然后运行mvn clean package进行编译打包。如果没有报错, 则接着找到target/hashicorp-vaultpipeline.hpi进行手动安装即可。
4、在Jenkins日志中隐藏敏感信息
4.1、使用credentials helper方法或者withCredentials步骤
- 如果使用的是credentials helper方法或者withCredentials步骤为变量赋值的,那么这个变量的值是不会被明文打印到Jenkins日志中的。除非使用以下方法:
pipeline { agent any stages { stage('Hidden information') { steps { script { def user //定义变量,变量的作用域是script{} def passwd //定义变量 withCredentials([usernamePassword(credentialsId: '18729147-da8c-49c5-8573-4bdb05984519', passwordVariable: 'passwdValue', usernameVariable: 'userValue')]) { echo "${userValue}" //输出的是密文:**** echo "${passwdValue}" //输出的是密文:**** user = "${userValue}" passwd = "${passwdValue}" echo "${user}" //输出的是密文:**** echo "${passwd}" //输出的是密文:**** } echo "${user}" //输出的是明文:user01 echo "${passwd}" //输出的是明文:user01123 } } } } }
4.2、使用Masked Pass-word插件
- 在没有使用credential的场景中,可以使用Masked Pass-word(mask-passwords)插件在日志中隐藏变量。
- 通过该插件提供的包装器,可以隐藏指定的敏感信息。
//该示例会抛出一个异常:java.lang.IllegalStateException: wrap step must be called with a body pipeline { agent any environment { SECRET1 = "secret1" SECRET2 = "secret2" NOT_SECRET1 = "not secret" } stages { stage("read vault key") { steps { wrap(maskPasswords(varPasswordPairs: [ [password: 'secret1', var: 's1'], [password: 'secret2', var: 's2'] ]){ echo "${SECRET1}" //输出的是密文 echo "${SECRET2}" //输出的是密文 echo "secret1" //输出的是密文 echo "${NOT_SECRET1}" //输出的是明文 } ) } } } }
- 初次使用Masked Password插件很容易以为是使用s1和s2作为变量的,如 echo "${s1}和${s2}"。实际上,var参数只是用于方便在自由风格的Jenkins项目中区分不同的需要隐藏的密文。在pipeline中使用,它就没有存在的意义了。但是即使这样也不能省略它,必须传一个值。password参数传的是真正要隐藏的密文。
- 为什么echo "secret1"这条语句中并没有使用预定义的变量,也会被隐藏呢?这是由Masked Password插件的实现方式决定的。
- Jenkins提供了ConsoleLogFilter接口,可以在日志打印阶段实现我们自己的业务逻辑。Masked Password插件实现了ConsoleLogFilter接口,然后利用正则表达式将匹配到的文本replaceAll成********。
- MaskPasswordsBuildWrapper包装器除了支持varPasswordPairs参数,还支持varMask Regexes参数,使用自定义的正则表达式匹配需要隐藏的文本。
//该示例会抛出一个异常:java.lang.IllegalStateException: wrap step must be called with a body pipeline { agent any environment { SECRET1 = "abc-xxx" SECRET2 = "xxxabc-xxx" NOT_SECRET1 = "abcxxx" } stages { stage("read vault key") { steps { wrap(maskPasswords( varMaskRegexes: [[regex: 'abc-.*']]) { echo "${SECRET1}" //输出的是密文 echo "${SECRET1}" //输出的是密文 echo "${NOT_SECRET1}" //输出的是明文 } ) } } } }
4.3、设置全局级别的密文隐藏
- 通过Masked Password插件还可以设置全局级别的密文隐藏。
- Jenkins --> Manage Jenkins --> Configure System
1
# #