通过REST XML API创建Jenkins SSH用户名和私钥凭据

4
基本上,我正在尝试通过Rest API在Jenkins上创建凭据。使用下面的xml数据:
<?xml version='1.0' encoding='UTF-8'?>
<com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey>
    <scope>GLOBAL</scope>
    <id>jenkins-github-ssh</id>
    <description>jenkins-github-ssh</description>
    <username>username</username>
    <directEntryPrivateKeySource>
        <privateKey>-----BEGIN OPENSSH PRIVATE KEY-----
*****************************************
-----END OPENSSH PRIVATE KEY-----</privateKey>
    </directEntryPrivateKeySource>
</com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey>

调用REST post请求后,我可以看到凭证。但是当我使用这个凭证访问GitHub存储库时,Jenkins会显示:

无法连接到存储库:命令“git ls-remote -h -- git@github.com:***.git HEAD”返回状态代码128: stdout: stderr:Load key“/tmp/ssh3978703187838467164.key”:格式无效 git@github.com:权限被拒绝(publickey)。 fatal: Could not read from remote repository。

如果我手动使用相同的私钥更新由rest api创建的凭据,则可以正常工作。某种方式,在发布时密钥已损坏。您们有任何想法指向解决方案吗?
Jenkins 2.198&SSH凭据插件1.17.3
谢谢

Ali,我为你创建了这个 https://issues.jenkins-ci.org/browse/JENKINS-60714 :) - samthebest
2个回答

4

我在使用Python脚本将私人SSH密钥推送到Jenkins时遇到了完全相同的问题。 我正在使用Requests库在Jenkins服务器上的任意凭据存储中创建和更新SSH密钥凭据集。

通常问题是您的XML结构部分错误。 标签

<directEntryPrivateKeySource>

必须被替换为

<privateKeySource class="com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource">

获取基本的XML结构

按照以下步骤,您可以从Jenkins服务器自行获取正确的XML结构:

  1. 手动创建一个SSH密钥凭据项目。在下面的示例中,密钥的id是test-sshkey。让我们将其放置在凭据存储中,该存储位于“Playground”的子文件夹“API-Test”中,即Playground/API-Test
  2. 当您单击Jenkins UI中新创建的凭据项目时,它的URL应如下所示:

    https://JENKINS_HOSTNAME/job/Playground/job/API-Test/credentials/store/folder/domain/_/credential/test-sshkey/

  3. 将上面的URL添加/config.xml,使其看起来像这样:

    https://JENKINS_HOSTNAME/job/Playground/job/API-Test/credentials/store/folder/domain/_/credential/test-sshkey/config.xml

步骤3返回的URL的XML结构几乎具备我们使用凭据API所需的结构,但部分不完整。
<com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey plugin="ssh-credentials@1.18.1">
  <id>test-sshkey</id>
  <description>DELETE AFTER USE</description>
  <username>test</username>
  <privateKeySource class="com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource">
    <privateKey>
      <secret-redacted/>
    </privateKey>
  </privateKeySource>
</com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey>

使用凭据 API

添加标签<scope><passphrase>以获得一个有效的 XML 模板,您可以将其 POST 到凭据 API:

<com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey>
    <scope>GLOBAL</scope>
    <id>CREDENTIAL_ID</id>
    <description>MY_DESCRIPTION</description>
    <username>A_USERNAME</username>
    <passphrase>OPTIONAL_KEY_PASSWORD</passphrase>
    <privateKeySource class="com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource">
        <privateKey>YOUR_PRIVATE_SSH_KEY_GOES_HERE</privateKey>
    </privateKeySource>
</com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey>

注意:如果提交的XML结构错误,凭据插件的REST API仍将接受它并返回误导性的HTTP状态码200!
现在我们可以使用这个XML结构通过cURL或类似工具将其POST到API端点以创建或更新。我们假设所有操作都在文件夹“Playground/API-Test”的凭据存储中执行。
以下Python代码示例完全简化了,重点是一般的方法:
def addCredentialSetSshPrivateKey(self, credentialDataObj):
    """
    Adds a credential set with a private SSH key to a credential store
    credentialDataObj: An instance of a simple DTO
    """
    jenkinsRequestUrl = "https://ci-yoda-new.codemanufaktur.com/job/Playground/job/API-Test/credentials/store/folder/domain/_/createCredentials"
    authentication = ("jenkins_admin_user", "API-TOKEN_FOR_THE_USER")

    completeSamlData = """
    <com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey>
        <scope>GLOBAL</scope>
        <id>{0}</id>
        <description>{1}</description>
        <username>{2}</username>
        <passphrase>{3}</passphrase>
        <privateKeySource class="com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource">
            <privateKey>{4}</privateKey>
        </privateKeySource>
    </com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey>
    """.format(credentialDataObj.id(), credentialDataObj.description(), credentialDataObj.username(), credentialDataObj.key_passphrase(), credentialDataObj.private_ssh_key())

    # When using CSRF protection in Jenkins a API crumb must be included in the actual REST call.
    # The following method requests the Jenkins Crumb Issuer for a API crumb and returns a JSON object like this:
    # {'_class': 'hudson.security.csrf.DefaultCrumbIssuer', 'crumb': 'a5d36ef09e063322169888f0b81341fe13b4109482a7936bc08c6f9a01badd39', 'crumbRequestField': 'Jenkins-Crumb'}
    jsonCrumb = self._requestApiCrumb()

    # The actual REST call with headers, XML payload and all other bells and whistles.
    remoteSession = requests.Session()
    return remoteSession.post(jenkinsRequestUrl, auth = authentication, headers = {"content-type":"application/xml", jsonCrumb['crumbRequestField']:jsonCrumb['crumb']}, data = completeSamlData)

创建SSH凭据项的REST端点https://JENKINS_HOSTNAME/job/Playground/job/API-Test/credentials/store/folder/domain/_/createCredentials

更新SSH凭据项的REST端点https://ci-yoda-new.codemanufaktur.com/job/Playground/job/API-Test/credentials/store/folder/domain/_/credential/credential_ci-yoda-new-project-apex_privatekey/config.xml

显然,在后一种情况下,您只需更新现有凭据项的config.xml文件即可。

此外,还请查看Credentials插件的user guide,特别是关于构建正确的REST URL的REST API部分。如果您需要使用Python请求Jenkins crumb发行者,请参阅this StackOverflow answer

解决方案测试版本:

  • Jenkins 2.214
  • Credentials Plugin 2.3.1
  • SSH Credentials Plugin 1.18.1

当我尝试使用这个答案时,我得到了HTTP 404错误。这是因为我正在使用“基于角色的授权策略”插件进行最小特权配置,并且我的API令牌没有凭据管理权限。为“hudson.security.SidACL”添加日志过滤器对我的问题有所启示。 - jws

0

针对那些遇到相同问题的人;

我尝试过上传文件、使用API上传、使用Jenkins CLI等方法,但都失败了。同样的问题也在这里发布了;

https://issues.jenkins.io/browse/JENKINS-60714

实现的步骤如下:

  1. 安装并配置Jenkins Configuration as Code插件。

  2. 上传你类似yaml格式的配置文件。

你可能还想在Jenkins实例中将私钥内容定义为环境变量,并使用"${private_key}"来代替明文粘贴私钥。

jenkins:
  systemMessage: "Example of configuring credentials in Jenkins"

credentials:
  system:
    domainCredentials:
    - credentials:
      - basicSSHUserPrivateKey:
          description: "kro"
          id: "kro"
          scope: GLOBAL
          username: "kro"
          privateKeySource:
            directEntry:
              privateKey: |
                -----BEGIN RSA PRIVATE KEY-----
                MIIG5AIBAAKCAYEAvuiaIDs+ydzR7Xxo5Owvv+G9/arbqN0YwhaGQQlicJjM4ZvI
              ..........YOUR KEY.............
                53Zg4QmSb1XGKUTXxIeGd27OIvgkwAn7K/cjQsU9t802iYV3tisnfA==
                -----END RSA PRIVATE KEY-----

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接