使用 AWS Signature V4 将 Google Apps 脚本连接到 AWS Redshift Data API

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


我正在尝试通过 Google Sheet 中的 Apps 脚本连接到 AWS Redshift Data API。我根据亚马逊的文档创建了请求签名,但我总是从亚马逊的 API 收到此响应:我们计算的请求签名与您提供的签名不匹配。

Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
所以我想我做错了什么。

下面是我的代码(经过编辑的凭据和项目特定内容以及每一步中的文档来源。

function awsRedshiftDataExecuteStatement() {
  let bodyPayload = {
                      "ClusterIdentifier": "<cluster_identifier>",
                      "Database": "<database>",
                      "DbUser": "<db_user>",
                      "MaxResults": 1,
                      "Sql": "select 1;",
                      "NextToken": ""
                    };
  let result = awsSendRequest(bodyPayload, "RedshiftData.ExecuteStatement");
  Logger.log(result);
}

function awsSendRequest(bodyPayload, headerXAmzTarget){
  let accessKeyId = "<access_key>";
  let secretKey = "<secret_key>";
  let regionName = "<region>"
  let serviceName = "redshift-data";
  let awsAlgorithm = "AWS4-HMAC-SHA256";
  let requestDate = Utilities.formatDate(today,'GTM','YYYYMMdd');
  //let requestDateTime = today.toISOString();
  let requestDateTime = Utilities.formatDate(today,'GTM','YYYYMMdd\'T\'HHmmss\'Z\'');
  let hashedPayload = Sha256Hash(String(bodyPayload));
  let headerArray = [
                      [{'name':'Host','value':'redshift-data.<region>.amazonaws.com'}],
                      [{'name':'X-Amz-Target','value':headerXAmzTarget}],
                      [{'name':'X-Requested-With','value':'XMLHttpRequest'}],
                      [{'name':'Content-Type','value':'application/x-amz-json-1.1'}],
                      [{'name':'X-Amz-Content-Sha256','value':hashedPayload}],
                      [{'name':'X-Amz-Date','value':requestDateTime}]
                    ];

  // Step 1: Create a canonical request > https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-canonical-request
  let httpMethod = 'POST';
  let canonicalURI = encodeURI('https://redshift-data.eu-central-1.amazonaws.com');
  let canonicalQueryString = "";
  let canonicalHeaders = awsCreateHeaders(headerArray, 'canonicalHeaders');
  let signedHeaders = awsCreateHeaders(headerArray, 'signedHeaders');
  let canonicalRequest = awsCreateCanonicalRequest(httpMethod, canonicalURI, canonicalQueryString, canonicalHeaders, signedHeaders, hashedPayload);
  Logger.log("Step 1: canonicalRequest: %s", canonicalRequest);

  // Step 2: Create a hash of the canonical request > https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-canonical-request-hash
  let hashedCanonicalRequest = Sha256Hash(canonicalRequest);
  Logger.log("Step 2: hashedCanonicalRequest: %s", hashedCanonicalRequest);

  // Step 3: Create a string to sign > https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-string-to-sign
  let stringToSign = awsCreateStringToSign(awsAlgorithm, hashedCanonicalRequest, requestDateTime, requestDate, regionName, serviceName);
  Logger.log("Step 3: stringToSign: %s", stringToSign);

  // Step 4: Calculate the signature > https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#calculate-signature
  let signatureKey = awsGetSignatureKey(secretKey, requestDate, regionName, serviceName, stringToSign);
  Logger.log("Step 4: signatureKey: %s", signatureKey);

  // Step 5: Add the signature to the request > https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#add-signature-to-request
  let headerRequestObject = awsCreateHeaders(headerArray, 'requestOptionsHeader');
  headerRequestObject["Authorization"] = awsAlgorithm+" Credential="+ awsCreatCredentialString(accessKeyId, requestDate, regionName, serviceName) +", SignedHeaders="+signedHeaders+", Signature="+signatureKey;;
  Logger.log("Step 5: headerRequestObject['Authorization']: %s", headerRequestObject["Authorization"]);

  // remove header "host" (Apps Script would throw an error otherwise)
  delete headerRequestObject['Host'];

  var requestOptions = {
    method: httpMethod,
    headers: headerRequestObject,
    body: bodyPayload,
    redirect: 'follow',
    muteHttpExceptions: true
  };

  Logger.log(requestOptions);

  var response;
  try {
    response = UrlFetchApp.fetch(canonicalURI, requestOptions);
  } catch (e) {
    throw (e);
  }
  var json = JSON.parse(response);
  return json;                    
}

function Sha256Hash(value) {
  return BytesToHex(Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, value));
}

function BytesToHex(bytes) {
  let hex = [];
  for (let i = 0; i < bytes.length; i++) {
    let b = parseInt(bytes[i]);
    if (b < 0) {
      c = (256+b).toString(16);
    } else {
      c = b.toString(16);
    }
    if (c.length == 1) {
      hex.push("0" + c);
    } else {
      hex.push(c);
    }
  }
  return hex.join("");
}

// Google Apps Script version of https://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-javascript
function awsGetSignatureKey(key, requestDate, regionName, serviceName, stringToSign) {
  const kDate = Utilities.computeHmacSha256Signature(requestDate, "AWS4" + key);
  const kRegion = Utilities.computeHmacSha256Signature(Utilities.newBlob(regionName).getBytes(), kDate);
  const kService = Utilities.computeHmacSha256Signature(Utilities.newBlob(serviceName).getBytes(), kRegion);
  const kSigning = Utilities.computeHmacSha256Signature(Utilities.newBlob("aws4_request").getBytes(), kService);
  const kSignature  = Utilities.computeHmacSha256Signature(Utilities.newBlob(stringToSign).getBytes(), kSigning);
  return BytesToHex(kSignature);
}

function awsCreateCanonicalRequest(httpMethod, canonicalURI, canonicalQueryString, canonicalHeaders, signedHeaders, hashedPayload){
  let canonicalRequest = httpMethod +"\n"+canonicalURI+"\n"+canonicalQueryString+"\n"+canonicalHeaders+"\n"+signedHeaders+"\n"+hashedPayload;
  return canonicalRequest;
}

function awsCreateHeaders(headerArray, type){
  switch(type){
    case 'canonicalHeaders':
      var returnValue = new String();
      for(let i=0; i < headerArray.length; i++ ){
        returnValue = returnValue + headerArray[i][0].name.toLowerCase() +":" + headerArray[i][0].value.trim() +"\n";
      }
    break;
    case 'signedHeaders':
      var returnValue = new Array();
      for(let i=0; i < headerArray.length; i++ ){
        returnValue.push(headerArray[i][0].name.toLowerCase());
      }
      //returnValue.push('host');
      returnValue = returnValue.sort().join(";");
    break;
    case 'requestOptionsHeader':
      var returnValue = {};
      for(let i=0; i < headerArray.length; i++ ){
        returnValue[headerArray[i][0].name] = headerArray[i][0].value;
      }
    break;
  }
  return returnValue;
}

function awsCreateStringToSign(awsAlgorithm, hashedCanonicalRequest, requestDateTime, requestDate, region, service){
  let returnValue = awsAlgorithm + "\n" + requestDateTime + "\n" + requestDate+"/"+region+"/"+service+"/aws4_request" + "\n" + hashedCanonicalRequest;
  return returnValue;
}
function awsCreatCredentialString(accessKeyId, requestDate, region, service){
  let returnValue = accessKeyId+"/"+ requestDate+"/"+region+"/"+service+"/aws4_request";
  return returnValue;
}

除了将散列数据作为散列的密钥再次返回不正确的结果之外,我找不到任何其他有关通过Apps脚本连接到AWS的主题。如果有任何更简单的方法来执行语句并检索其结果,我很乐意使用这些替代方案!

google-apps-script amazon-redshift
1个回答
0
投票
© www.soinside.com 2019 - 2024. All rights reserved.