通过 API 创建 Jenkins 凭证证书 - 此页面需要提交

问题描述 投票:0回答:1

我们正在尝试在脚本中通过 API 创建 Jenkins 证书凭据,该脚本会在 Azure 中生成应用程序注册,然后应将相应的证书放入 Jenkins 凭据存储中。

我们使用的是 Jenkins 版本 2.426.2

有人知道如何提交表格才能创建凭证吗?

这是我们的代码:

$form = @{
        'id'          = $CredentialId
        'description' = $CredentialDescription
        'file'        = $CertificateFilePath
        'password'    = $CertificatePassword
    }
$userId = $env:JENKINSAPIUSER
$token = $env:JENKINSTOKEN
$pair = "$($userId):$($token)"
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
$basicAuthValue = "Basic $encodedCreds"
$headers = @{
        'Authorization' = "Basic $encodedCreds"
    }
    $jenkinsUri = "$JenkinsUrl/credentials/store/$CredentialStore/domain/_/createCredentials/"
    Invoke-RestMethod -uri $jenkinsUri -Method Post -Headers $headers -Form $form

返回:

文本:错误 400 此页面需要提交表单

同样的行为也发生在 Postman 中:

如您所见,它还添加了所需的标头:

我们还尝试通过 xml api 添加它:

$xmlPayload = @"
    <com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl>
        <scope>GLOBAL</scope>
        <id>$CredentialId</id>
        <description>$CredentialDescription</description>
        <file>$([System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($CertificateFilePath)))</file>
        <password>$($CertificatePassword | ConvertFrom-SecureString -AsPlainText)</password>
    </com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl>
"@

    #$response = Invoke-JenkinsWebRequest -Uri $jenkinsUri -Method Post -Form $json
    $response = Invoke-JenkinsWebRequest -Uri "$jenkinsUri/config.xml" -Method Post -Body $xmlPayload -ContentType "application/xml"

这会导致凭证条目为空

此外,我们尝试对 Web UI 进行逆向工程,但这并没有带来任何重要的见解。然而,这是我们确定的用于准备表单数据的函数:

 
function buildFormTree(form) {
  try {
    // I initially tried to use an associative array with DOM elements as keys
    // but that doesn't seem to work neither on IE nor Firefox.
    // so I switch back to adding a dynamic property on DOM.
    form.formDom = {}; // root object
 
    var doms = []; // DOMs that we added 'formDom' for.
    doms.push(form);
 
    let addProperty = function (parent, name, value) {
      name = shortenName(name);
      if (parent[name] != null) {
        if (parent[name].push == null) {
          // is this array?
          parent[name] = [parent[name]];
        }
        parent[name].push(value);
      } else {
        parent[name] = value;
      }
    };
 
    // find the grouping parent node, which will have @name.
    // then return the corresponding object in the map
    let findParent = function (e) {
      var p = findFormParent(e, form);
      if (p == null) {
        return {};
      }
 
      var m = p.formDom;
      if (m == null) {
        // this is a new grouping node
        doms.push(p);
        p.formDom = m = {};
        addProperty(findParent(p), p.getAttribute("name"), m);
      }
      return m;
    };
 
    var jsonElement = null;
 
    for (var i = 0; i < form.elements.length; i++) {
      var e = form.elements[i];
      if (e.name == "json") {
        jsonElement = e;
        continue;
      }
      if (e.tagName == "FIELDSET") {
        continue;
      }
      if (e.tagName == "SELECT" && e.multiple) {
        var values = [];
        for (var o = 0; o < e.options.length; o++) {
          var opt = e.options.item(o);
          if (opt.selected) {
            values.push(opt.value);
          }
        }
        addProperty(findParent(e), e.name, values);
        continue;
      }
 
      var p;
      var r;
      var type = e.getAttribute("type");
      if (type == null) {
        type = "";
      }
      switch (type.toLowerCase()) {
        case "button":
        case "submit":
          break;
        case "checkbox":
          p = findParent(e);
          var checked = xor(e.checked, e.classList.contains("negative"));
          if (!e.groupingNode) {
            let v = e.getAttribute("json");
            if (v) {
              // if the special attribute is present, we'll either set the value or not. useful for an array of checkboxes
              // we can't use @value because IE6 sets the value to be "on" if it's left unspecified.
              if (checked) {
                addProperty(p, e.name, v);
              }
            } else {
              // otherwise it'll bind to boolean
              addProperty(p, e.name, checked);
            }
          } else {
            if (checked) {
              addProperty(p, e.name, (e.formDom = {}));
            }
          }
          break;
        case "file":
          // to support structured form submission with file uploads,
          // rename form field names to unique ones, and leave this name mapping information
          // in JSON. this behavior is backward incompatible, so only do
          // this when
          p = findParent(e);
          if (e.getAttribute("jsonAware") != null) {
            var on = e.getAttribute("originalName");
            if (on != null) {
              addProperty(p, on, e.name);
            } else {
              var uniqName = "file" + iota++;
              addProperty(p, e.name, uniqName);
              e.setAttribute("originalName", e.name);
              e.name = uniqName;
            }
          }
          // switch to multipart/form-data to support file submission
          // @enctype is the standard, but IE needs @encoding.
          form.enctype = form.encoding = "multipart/form-data";
          crumb.appendToForm(form);
          break;
        case "radio":
          if (!e.checked) {
            break;
          }
          r = 0;
          while (e.name.substring(r, r + 8) == "removeme") {
            r = e.name.indexOf("_", r + 8) + 1;
          }
          p = findParent(e);
          if (e.groupingNode) {
            addProperty(
              p,
              e.name.substring(r),
              (e.formDom = { value: e.value }),
            );
          } else {
            addProperty(p, e.name.substring(r), e.value);
          }
          break;
        case "password":
          p = findParent(e);
          addProperty(p, e.name, e.value);
          // must be kept in sync with RedactSecretJsonForTraceSanitizer.REDACT_KEY
          addProperty(p, "$redact", shortenName(e.name));
          break;
        default:
          p = findParent(e);
          addProperty(p, e.name, e.value);
          if (e.classList.contains("complex-password-field")) {
            addProperty(p, "$redact", shortenName(e.name));
          }
          break;
      }
    }
 
    jsonElement.value = JSON.stringify(form.formDom);
 
    // clean up
    for (i = 0; i < doms.length; i++) {
      doms[i].formDom = null;
    }
 
    return true;
  } catch (e) {
    alert(e + "\n(form not submitted)");
    return false;
  }
}
powershell jenkins jenkins-plugins credentials jenkins-api
1个回答
0
投票

查看您的 PowerShell 代码,问题出在表单数据编码上。 Jenkins 期望

application/x-www-form-urlencoded
,但是当您提供
-Form
参数
Invoke-RestMethod
时,会发送
multipart/form-data
。将
-Form
替换为
-Body
参数

© www.soinside.com 2019 - 2024. All rights reserved.