Bitbucket 使用 SSH 拉取倉庫失敗的問題

問題

在 Bitbucket 使用 Linux 機器上 ssh-keygen 工具生成的公鑰作為 API KEY,然後在 Jenkins 裡面存儲對應的 SSH 私鑰,最後執行 Job 的時候,Windows Agent 提示 SSH 密鑰不正確。

 > git fetch --tags --force --progress -- ssh://[email protected]:7999/xxxx.git +refs/heads/*:refs/remotes/origin/* # timeout=10
ERROR: Error fetching remote repo 'origin'
hudson.plugins.git.GitException: Failed to fetch from [email protected]:7999/xxxx.git
	at hudson.plugins.git.GitSCM.fetchFrom(GitSCM.java:1001)
	at hudson.plugins.git.GitSCM.retrieveChanges(GitSCM.java:1242)
	at hudson.plugins.git.GitSCM.checkout(GitSCM.java:1302)
	at org.jenkinsci.plugins.workflow.steps.scm.SCMStep.checkout(SCMStep.java:129)
	at org.jenkinsci.plugins.workflow.steps.scm.SCMStep$StepExecutionImpl.run(SCMStep.java:97)
	at org.jenkinsci.plugins.workflow.steps.scm.SCMStep$StepExecutionImpl.run(SCMStep.java:84)
	at org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:47)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: hudson.plugins.git.GitException: Command "git fetch --tags --force --progress -- ssh://[email protected]:7999/xxxx.git +refs/heads/*:refs/remotes/origin/*" returned status code 128:
stdout: 
stderr: [email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:2671)
	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandWithCredentials(CliGitAPIImpl.java:2096)
	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.access$500(CliGitAPIImpl.java:84)
	at org.jenkinsci.plugins.gitclient.CliGitAPIImpl$1.execute(CliGitAPIImpl.java:618)
	at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler$GitCommandMasterToSlaveCallable.call(RemoteGitImpl.java:158)
	at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler$GitCommandMasterToSlaveCallable.call(RemoteGitImpl.java:151)
	at hudson.remoting.UserRequest.perform(UserRequest.java:211)
	at hudson.remoting.UserRequest.perform(UserRequest.java:54)
	at hudson.remoting.Request$2.run(Request.java:376)
	at hudson.remoting.InterceptingExecutorService.lambda$wrap$0(InterceptingExecutorService.java:78)
	at java.util.concurrent.FutureTask.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at hudson.remoting.Engine$1.lambda$newThread$0(Engine.java:122)
	at java.lang.Thread.run(Unknown Source)
	Suppressed: hudson.remoting.Channel$CallSiteStackTrace: Remote call to dev-eu01-njen03.solera.farm
		at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1797)
		at hudson.remoting.UserRequest$ExceptionResponse.retrieve(UserRequest.java:356)
		at hudson.remoting.Channel.call(Channel.java:1001)
		at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler.execute(RemoteGitImpl.java:143)
		at jdk.internal.reflect.GeneratedMethodAccessor8221.invoke(Unknown Source)
		at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.base/java.lang.reflect.Method.invoke(Method.java:566)
		at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler.invoke(RemoteGitImpl.java:129)
		at com.sun.proxy.$Proxy126.execute(Unknown Source)
		at hudson.plugins.git.GitSCM.fetchFrom(GitSCM.java:999)
		at hudson.plugins.git.GitSCM.retrieveChanges(GitSCM.java:1242)
		at hudson.plugins.git.GitSCM.checkout(GitSCM.java:1302)
		at org.jenkinsci.plugins.workflow.steps.scm.SCMStep.checkout(SCMStep.java:129)
		at org.jenkinsci.plugins.workflow.steps.scm.SCMStep$StepExecutionImpl.run(SCMStep.java:97)
		at org.jenkinsci.plugins.workflow.steps.scm.SCMStep$StepExecutionImpl.run(SCMStep.java:84)
		at org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution.lambda$start$0(SynchronousNonBlockingStepExecution.java:47)
		at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
		at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
		at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
		at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
		at java.base/java.lang.Thread.run(Thread.java:829)

最後登錄到對應的 Windows 節點,直接使用 git clone 命令也是這樣,說明不是 Jenkins 的許可權問題。

查詢資料得知,出現這種情況需要將對應公鑰(id_rsa)與私鑰文件(id_rsa.pub) 複製到 Jenkins Agent 執行用戶的 HOME 目錄下的 .ssh 文件夾。

普通用戶的 HOME 目錄就是 echo $HOME,一般也就是 C:\User\UserName 的路徑。但是 Jenkins Agent 一旦作為服務執行,那麼他的執行用戶就是 SYSTEM ACCOUNT,它的 HOME 目錄路徑是 C:\WINDOWS\system32\config\systemprofile\.ssh

根據上述情況試了,還是沒有解決,不管是 ssh-add xxx,還是更改 Git 的 SSH CONFIG 文件都無法解決。

原因

我一直以為是自己的問題,結果用 GitHub 試了,是正常的。最後將目光轉向了 BitBucket,根據官方的文章,設置了 GIT_SSH_COMMAND 環境變數,並指定 ssh -vvvv 命令,這樣能夠讓我們看到詳細的錯誤資訊。

最後得到了以下輸出:

查詢資料得知,根本原因是 BitBucket 現在不支援 RSA SHA-1 生成的密鑰。

解決

官方的解決方案有兩種,第一種是使用不安全的 RSA SHA-1 密鑰,只需要更改客戶端的配置文件,將 ssh-rsa 添加進去即可。不過這種做法並不安全,最好的做法還是使用 ssh-keygen 命令生成更加安全,基於 ED25519 演算法的密鑰。

ssh-keygen -t ed25519 -C "[email protected]"

將這個公鑰重新設置在 Bitbucket,然後將私鑰替換掉 Jenkins 裡面的 Credential,執行 Jenkins Job 成功。

參考資料