Create a signed AWS API request to a Lambda Function URL from Excel (VBA)

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

我有一个带有函数 URL 的 AWS Lambda 函数。它的身份验证类型是 AWS_IAM,我有凭据。目前,这个 Lambda 将两个数字作为输入并返回它们的总和(一旦我解决了第一个问题,未来复杂性的占位符)。我想使用 VBA 从 Excel 访问 AWS Lambda。

如果我关闭授权,我可以成功发送和接收响应。此外,我能够使用 Python 发送签名请求。但是,我需要 VBA 中的代码(这样我就可以分发给我的用户而无需他们安装 Python)。

我收到一条返回消息“我们计算的请求签名与您提供的签名不匹配。检查您的 AWS 秘密访问密钥和签名方法。查阅服务文档。”我咨询过。这使我找到了 Python 解决方案,但我无法让 VBA 工作。注意:如果我将时间戳设置为固定时间,我将在 Python 和 VBA 中获得相同的签名。

VBA:

Sub CallLambdaFunctionWithIAM()
    Dim req As New WinHttpRequest
    Dim url As String
    Dim trim_url As String
    Dim input_data, input_data_json As String
    Dim access_key As String
    Dim secret_key As String
    Dim a, b As Variant
    
    url = "https://myurlhere.lambda-url.eu-central-1.on.aws/"
    trim_url = "myurlhere.lambda-url.eu-central-1.on.aws"
    
    a = ThisWorkbook.Names("a").RefersToRange.Value
    b = ThisWorkbook.Names("b").RefersToRange.Value
    
    input_data = "{'a': '" & a & "', 'b': '" & b & "'}"
    input_data_json = Replace(input_data, "'", Chr(34))

    access_key = "accesskey"
    secret_key = "secretkey"
    
    ' Generate a signature for the request
    Dim timestamp As String
    Dim signature As String
    timestamp = Format(DateAdd("h", -1, Now()), "yyyyMMddTHHmmssZ")
    Debug.Print timestamp
    Debug.Print datestamp(timestamp)
    signature = GenerateSignature(trim_url, "POST", input_data_json, access_key, secret_key, timestamp)
    
    Debug.Print "signature:  " & signature
       
    With req
        .Open "POST", url, False
        .SetRequestHeader "content-type", "application/json"
        .SetRequestHeader "host", trim_url
        .SetRequestHeader "x-amz-date", timestamp
        .SetRequestHeader "authorization", "AWS4-HMAC-SHA256 Credential=" & access_key & "/" & GetScope(timestamp) & ", SignedHeaders=content-type;host;x-amz-date, Signature=" & signature
        .Send input_data
    End With
    
    ' Parse the response and extract the output data
    Dim output_data As String

    output_data = req.ResponseText
    Debug.Print "Return message:  " & output_data
    Debug.Print "ResponseHeaders:  " & req.GetAllResponseHeaders

End Sub

Function GenerateSignature(url As String, method As String, data As String, access_key As String, secret_key As String, timestamp As String) As String
    ' Generate a signature for the given request
    
    ' Compute the required hash values
    Dim service As String
    Dim region As String
    Dim canonical_uri As String
    Dim canonical_querystring As String
    Dim canonical_headers As String
    Dim signed_headers As String
    Dim payload_hash As String
    
    service = "lambda"
    region = "eu-central-1"
    canonical_uri = "/"
    canonical_querystring = ""
    canonical_headers = "content-type:application/json" & Chr(10) & "host:" & url & Chr(10) & "x-amz-date:" & timestamp & Chr(10)
    signed_headers = "content-type;host;x-amz-date"
    payload_hash = HexString(HashBytes(data))
    Debug.Print "payload_hash:  " & payload_hash
    
    
    ' Compute the string-to-sign and the signing key
    Dim scope As String
    Dim string_to_sign As String
    Dim signing_key() As Byte
    Dim kDate() As Byte
    Dim kRegion() As Byte
    Dim kService() As Byte
    Dim kSigning() As Byte
    Dim asc As Object
    Set asc = CreateObject("System.Text.UTF8Encoding")
    
    
    scope = GetScope(timestamp)
    string_to_sign = "AWS4-HMAC-SHA256" & Chr(10) & timestamp & Chr(10) & scope & Chr(10) & canonical_request(url, method, data, canonical_uri, canonical_querystring, canonical_headers, signed_headers, payload_hash)

    signing_key = asc.getbytes_4("AWS4" & secret_key)
    kDate = HmacSHA256(signing_key, datestamp(timestamp))
    kRegion = HmacSHA256(kDate, region)
    kService = HmacSHA256(kRegion, service)
    kSigning = HmacSHA256(kService, "aws4_request")
    
    ' Compute the signature
    GenerateSignature = HexString(HmacSHA256(kSigning, string_to_sign))
    
End Function

Function canonical_request(url As String, method As String, data As String, canonical_uri As String, canonical_querystring As String, canonical_headers As String, signed_headers As String, payload_hash As String) As String
    ' Generate the canonical request for the given inputs

    canonical_request = method & Chr(10) & canonical_uri & Chr(10) & canonical_querystring & Chr(10) & canonical_headers & Chr(10) & signed_headers & Chr(10) & payload_hash
    canonical_request = HexString(HashBytes(canonical_request))
    Debug.Print "canonical_request (Hash & Hex):  " & canonical_request


End Function

Function GetScope(timestamp As String) As String
    ' Generate the scope for the given timestamp   
    GetScope = datestamp(timestamp) & "/eu-central-1/lambda/aws4_request"
End Function

Function datestamp(timestamp As String) As String
    ' Generate the datestamp for the given timestamp
    datestamp = Left(timestamp, 8)

End Function

Function HmacSHA256(key() As Byte, message As String) As Byte()
    ' Compute the HMAC-SHA256 digest for the given key and message
    Dim sha As Object
    Set sha = CreateObject("System.Security.Cryptography.HMACSHA256")
    sha.key = key
    
    Dim message_bytes() As Byte
    message_bytes = StrConv(message, vbFromUnicode)
    
    HmacSHA256 = sha.ComputeHash_2(message_bytes)
End Function


Function HashBytes(data As String) As Byte()
    ' Compute the SHA256 hash for the given data
    
    Dim sha As Object
    Set sha = CreateObject("System.Security.Cryptography.SHA256Managed")
    
    Dim data_bytes() As Byte
    data_bytes = StrConv(data, vbFromUnicode)
    
    HashBytes = sha.ComputeHash_2(data_bytes)
End Function

Function HexString(bytes() As Byte) As String
    ' Convert a byte array to a hex string
    Dim i As Long
    Dim temp As String
    For i = LBound(bytes) To UBound(bytes)
        temp = Hex(bytes(i))
        If Len(temp) = 1 Then temp = "0" & temp
        HexString = HexString & temp
    Next i
    HexString = LCase(HexString)
End Function

这是正在运行的 Python 代码。

import requests
import datetime
import hashlib
import hmac
import json

# AWS IAM credentials
access_key = "accesskey"
secret_key = "secretkey"
region = "eu-central-1"
service = "lambda"

# Request URL
url = "https://myurlhere.lambda-url.eu-central-1.on.aws/"

# Request headers
headers = {
    "Content-Type": "application/json",
}

# Request body
body = {
    "a": "1",
    "b": "3"
}

# Create a datetime object for the request timestamp
timestamp = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")

# Create a date object for the request date
datestamp = datetime.datetime.utcnow().strftime("%Y%m%d")

# Construct the canonical request string
http_method = "POST"
canonical_uri = "/"
canonical_querystring = ""
canonical_headers = "content-type:" + headers["Content-Type"] + "\n" + "host:" + url.split("/")[2] + "\n" + "x-amz-date:" + timestamp + "\n"
signed_headers = "content-type;host;x-amz-date"
payload_hash = hashlib.sha256(json.dumps(body).encode("UTF-8")).hexdigest()
print("payload_hash:  " + str(payload_hash))
canonical_request = http_method + "\n" + canonical_uri + "\n" + canonical_querystring + "\n" + canonical_headers + "\n" + signed_headers + "\n" + payload_hash
print("canonical_request:  "+str(hashlib.sha256(canonical_request.encode("UTF-8")).hexdigest())) 

# Construct the string to sign
algorithm = "AWS4-HMAC-SHA256"
credential_scope = datestamp + "/" + region + "/" + service + "/" + "aws4_request"
string_to_sign = algorithm + "\n" +  timestamp + "\n" +  credential_scope + "\n" +  hashlib.sha256(canonical_request.encode("UTF-8")).hexdigest()

# Derive the signing key
def sign(key, msg):
    return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()

kDate = sign(("AWS4" + secret_key).encode('utf-8'), datestamp)
kRegion = sign(kDate, region)
kService = sign(kRegion, service)
kSigning = sign(kService, "aws4_request")

# Calculate the signature
signature = hmac.new(kSigning, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
print("signature: "+"\n"+str(signature))

# Add the required authorization headers to the request
headers["Authorization"] = algorithm + " " + "Credential=" + access_key + "/" + credential_scope + ", " +  "SignedHeaders=" + signed_headers + ", " + "Signature=" + signature
headers["X-Amz-Date"] = timestamp

print("headers: "+"\n"+str(headers))

# Send the signed request to the Lambda function URL
response = requests.post(url, headers=headers, json=body)

# Print the response
print(response.text)
print(response.headers)

我怀疑这与我的请求标头有关,但我没有主意。

vba amazon-web-services aws-lambda
1个回答
0
投票

我已经“解决”了这个问题,尽管我对此并不满意。当我用 MSXML2.serverXMLHTTP 请求替换 WinHttpRequest 时,代码有效。我不知道为什么或如何。

在此之前,我有多个 aws 支持的电话。他们只提供了一点帮助,但我最大的收获是他们无法追踪 x-amzn-RequestId。

其他想法是问题是由代理在请求中进行了不必要的更改引起的,但我无法跟踪请求,因为我没有这样做的管理员权限。

长话短说,我现在可以将数据发送到我的 Lambda 并获得经过计算的响应。我很不高兴我花了几个星期在这上面,而且现在还不知道为什么这行得通。

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