Jenkins Pipeline中withCredentials不同凭证类型的使用故障排查
Jenkins Pipeline访问Grafana的认证问题排查与解决
问题背景
我尝试通过Jenkins Pipeline,使用withCredentials插件结合httpRequest步骤访问Grafana,Jenkins中配置的凭证为Grafana管理员的「用户名密码」类型。
第一阶段错误:凭证类型不匹配
初始代码片段
script.withCredentials([script.string( credentialsId: "${credentialsEnvironment}", variable: 'usernamePassword') ]) { String auth = "${script.usernamePassword}".bytes.encodeBase64().toString() Map params = defaultArgs + httpParams def response = [] try { response = script.httpRequest( params + [ url: "${apiEndpoint}/${url}", customHeaders: [[name: 'Authorization', value: "Basic ${auth}"]] ] ) if (response && (response.status != 200 && response.status != 201)) { script.echo "Response status: ${response.status}" script.echo "Response message: ${response.content}" } return response } catch (ex) { LogUtil.echoStacktraceToScript(script, ex) throw new ObservabilityException("Error: An exception occured when sending request with params: ${params}", ex) } }
执行异常日志
10:49:56 credentialsEnvironment: sb_1365_kraken_monitoring_scp_west_zone01_z01_grafana_admin 10:49:56 [Pipeline] withCredentials 10:49:56 [Pipeline] // withCredentials 10:49:56 [Pipeline] echo 10:49:56 org.jenkinsci.plugins.credentialsbinding.impl.CredentialNotFoundException: Credentials 'sb_1365_kraken_monitoring_scp_west_zone01_z01_grafana_admin' is of type 'Username with password' where 'org.jenkinsci.plugins.plaincredentials.StringCredentials' was expected 10:49:56 at org.jenkinsci.plugins.credentialsbinding.MultiBinding.getCredentials(MultiBinding.java:208) 10:49:56 at org.jenkinsci.plugins.credentialsbinding.impl.StringBinding.bindSingle(StringBinding.java:57) 10:49:56 at org.jenkinsci.plugins.credentialsbinding.Binding.bind(Binding.java:149) 10:49:56 at org.jenkinsci.plugins.credentialsbinding.impl.BindingStep$Execution2.doStart(BindingStep.java:132) 10:49:56 at org.jenkinsci.plugins.workflow.steps.GeneralNonBlockingStepExecution.lambda$run$0(GeneralNonBlockingStepExecution.java:77) 10:49:56 at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) 10:49:56 at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) 10:49:56 at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) 10:49:56 at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) 10:49:56 at java.base/java.lang.Thread.run(Thread.java:829)
原因
代码中使用script.string()绑定凭证,但实际配置的是「用户名密码」类型,两者类型不匹配,导致抛出凭证类型错误。
第二阶段错误:401未授权
修改后的代码片段
script.println "Attempting to use ${credentialsEnvironment}" script.withCredentials([script.usernamePassword( credentialsId: "${credentialsEnvironment}", passwordVariable: 'passw', usernameVariable: 'user') ]) { script.println "user: ${script.user}" String auth = "${script.user}".bytes.encodeBase64().toString() script.println "auth: ${auth}" Map params = defaultArgs + httpParams def response = [] try { response = script.httpRequest( params + [ url: "${apiEndpoint}/${url}", customHeaders: [[name: 'Authorization', value: "Basic ${auth}"]] ] ) if (response && (response.status != 200 && response.status != 201)) { script.echo "Response status: ${response.status}" script.echo "Response message: ${response.content}" } return response } catch (ex) { LogUtil.echoStacktraceToScript(script, ex) throw new ObservabilityException("Error: An exception occured when sending request with params: ${params}", ex) } }
执行错误日志
credentialsEnvironment: sb_1365_kraken_monitoring_scp_west_zone01_z01_grafana_admin 13:05:52 [Pipeline] echo 13:05:52 Attempting to use sb_1365_kraken_monitoring_scp_west_zone01_z01_grafana_admin 13:05:52 [Pipeline] withCredentials 13:05:52 Masking supported pattern matches of $user or $passw 13:05:52 [Pipeline] { 13:05:52 [Pipeline] echo 13:05:52 user: **** 13:05:52 [Pipeline] echo 13:05:52 auth: YWRtaW50ZXN0 13:05:52 [Pipeline] httpRequest 13:05:52 HttpMethod: GET 13:05:52 URL: https://plg-grafana-sb-1365-kraken-monitoring.apps.scp-west-zone01-z01.net/api/folders 13:05:52 Content-Type: application/json 13:05:52 Accept: application/json 13:05:52 Authorization: ***** 13:05:52 Sending request to url: https://plg-grafana-sb-1365-kraken-monitoring.apps.scp-west-zone01-z01.net/api/folders 13:05:52 Response Code: HTTP/1.1 401 Unauthorized 13:05:52 Success: Status code 401 is in the accepted range: 100:500 13:05:52 [Pipeline] echo 13:05:52 Response status: 401 13:05:52 [Pipeline] echo 13:05:52 Response message: {"message":"Invalid Basic Auth Header","traceID":""}
原因
Basic认证的格式要求是用户名:密码的字符串经过Base64编码,但修改后的代码只对用户名单独编码,缺少密码部分,导致认证头部无效,触发401未授权错误。
解决方案
修正认证字符串的生成逻辑,按用户名:密码的格式拼接后再进行Base64编码:
script.println "Attempting to use ${credentialsEnvironment}" script.withCredentials([script.usernamePassword( credentialsId: "${credentialsEnvironment}", passwordVariable: 'passw', usernameVariable: 'user') ]) { // 按Basic认证要求拼接用户名和密码 String authString = "${script.user}:${script.passw}" // 对拼接后的字符串进行Base64编码 String auth = authString.bytes.encodeBase64().toString() script.println "auth: ${auth}" Map params = defaultArgs + httpParams def response = [] try { response = script.httpRequest( params + [ url: "${apiEndpoint}/${url}", customHeaders: [[name: 'Authorization', value: "Basic ${auth}"]] ] ) if (response && (response.status != 200 && response.status != 201)) { script.echo "Response status: ${response.status}" script.echo "Response message: ${response.content}" } return response } catch (ex) { LogUtil.echoStacktraceToScript(script, ex) throw new ObservabilityException("Error: An exception occured when sending request with params: ${params}", ex) } }
关键修正点
- 使用
script.usernamePassword()正确绑定「用户名密码」类型的凭证,分别获取用户名和密码变量 - 生成认证字符串时严格遵循
用户名:密码的格式,再进行Base64编码,而非单独编码用户名
内容的提问来源于stack exchange,提问作者Kaliyug Antagonist
相关产品推荐
相关产品推荐

