【发布时间】:2021-01-01 15:47:56
【问题描述】:
我想在我的 Jekyll 网站上有一个表单供访问者填写,form action 应该 POST 到 AWS Lambda 函数。网站上不允许使用 JavaScript,因此POST 不得要求签名。
我想要尽可能简单的设置,并且不需要高安全性。如果有办法避免使用 AWS API Gateway 创建 HTTP API,并以某种方式让 Lambda 函数直接从用户的 Web 浏览器接收POST,那将是完美的。如果需要 API 网关,那么最简单的解决方案将是最好的。
我想专门使用命令行命令(不是 Web 浏览器)来使用 AWS API。这允许使用脚本解决方案。
我在这个问题上花了一些时间,这就是我所拥有的。我在deploy 脚本中用TODO 标记了问题。该脚本中有一些可能不需要的额外代码。问题是,我不确定要删除什么,因为我不知道如何将POST 提供给 lambda。
脚本使用jq 和yq,因此 bash 脚本可以分别解析 JSON 和 YAML。
_config.yml
aws:
cloudfront:
distributionId: "" # Provide value if CloudFront is used on this site
lambda:
addSubscriber:
custom: # TODO change these values to suit your website
iamRoleName: lambda-ex
name: addSubscriberAwsLambdaSample
handler: addSubscriberAwsLambda.lambda_handler
runtime: python3.8
computed: # These values are computed by the _bin/awsLambda setup and deploy scripts
arn: arn:aws:lambda:us-east-1:031372724784:function:addSubscriberAwsLambdaSample:3
iamRoleArn: arn:aws:iam::031372724784:role/lambda-ex
utils 源 bash 脚本
#!/bin/bash
function readYaml {
# $1 - path
yq r _config.yml "$1"
}
function writeYaml {
# $1 - path
# $2 - value
yq w -i _config.yml "$1" "$2"
}
# AWS Lambda values
export LAMBDA_IAM_ROLE_ARN="$( readYaml aws.lambda.addSubscriber.computed.iamRoleArn )"
export LAMBDA_NAME="$( readYaml aws.lambda.addSubscriber.custom.name )"
export LAMBDA_RUNTIME="$( readYaml aws.lambda.addSubscriber.custom.runtime )"
export LAMBDA_HANDLER="$( readYaml aws.lambda.addSubscriber.custom.handler )"
export LAMBDA_IAM_ROLE_NAME="$( readYaml aws.lambda.addSubscriber.custom.iamRoleName )"
export PACKAGE_DIR="${GIT_ROOT}/_package"
export LAMBDA_ZIP="${PACKAGE_DIR}/function.zip"
# Misc values
export TITLE="$( readYaml title )"
export URL="$( readYaml url )"
export DOMAIN="$( echo "$URL" | sed -n -e 's,^https\?://,,p' )"
设置 bash 脚本
#!/bin/bash
# Inspired by https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-awscli.html
SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
GIT_ROOT="$( git rev-parse --show-toplevel )"
cd "${GIT_ROOT}"
source _bin/utils
# Define the execution role that gives an AWS Lambda function permission to access AWS resources.
read -r -d '' ROLE_POLICY_JSON <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
# If a role named $LAMBDA_IAM_ROLE_NAME is already defined then use it
ROLE_RESULT="$( aws iam get-role --role-name "$LAMBDA_IAM_ROLE_NAME" 2> /dev/null )"
if [ $? -ne 0 ]; then
ROLE_RESULT="$( aws iam create-role \
--role-name "$LAMBDA_IAM_ROLE_NAME" \
--assume-role-policy-document "$ROLE_POLICY_JSON"
)"
fi
LAMBDA_IAM_ROLE_ARN="$( jq -r .Role.Arn <<< "$ROLE_RESULT" )"
writeYaml aws.lambda.addSubscriber.computed.iamRoleArn "$LAMBDA_IAM_ROLE_ARN"
部署 bash 脚本
# Call this script after the setup script has created the IAM role
# that gives the addSubscriber AWS Lambda function permission to access AWS resources
#
# 1) This script builds the AWS Lambda package and deploys it, with permissions.
# Any previous version of the AWS Lambda is deleted.
#
# 2) The newly (re)created AWS Lambda ARN is stored in _config.yml
#
# 3) An AWS Gateway HTTP API is created so static web pages can POST subscriber information to the AWS Lambda function.
# Because the web page is not allowed to have JavaScript, the POST is unsigned.
# *** The API must allow for an unsigned POST!!! ***
# Set cwd to the git project root
GIT_ROOT="$( git rev-parse --show-toplevel )"
cd "${GIT_ROOT}"
# Load configuration environment variables from _bin/utils:
# DOMAIN, LAMBDA_IAM_ROLE_ARN, LAMBDA_IAM_ROLE_NAME, LAMBDA_HANDLER, LAMBDA_NAME, LAMBDA_RUNTIME, LAMBDA_ZIP, PACKAGE_DIR, and URL
source _bin/utils
# Directory that this script resides in
SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
echo "Building the AWS Lambda and packaging it into a zip file"
"$SOURCE_DIR/package" "$PACKAGE_DIR" > /dev/null
# Check to see if the Lambda function already exists.
LAMBDA="$( aws lambda list-functions | jq ".Functions[] | select(.FunctionName | contains(\"$LAMBDA_NAME\"))" )"
if [ -z "$LAMBDA" ]; then
echo "The AWS Lambda function '$LAMBDA_NAME' does not exist yet, so create it"
LAMBDA_METADATA="$( aws lambda create-function \
--description "Add subscriber to the MailChimp list with ID '$MC_LIST_ID_MSLINN' for the '$DOMAIN' website" \
--environment "{
\"Variables\": {
\"MC_API_KEY_MSLINN\": \"$MC_API_KEY_MSLINN\",
\"MC_LIST_ID_MSLINN\": \"$MC_LIST_ID_MSLINN\",
\"MC_USER_NAME_MSLINN\": \"$MC_USER_NAME_MSLINN\"
}
}" \
--function-name "$LAMBDA_NAME" \
--handler "$LAMBDA_HANDLER" \
--role "arn:aws:iam::${AWS_ACCOUNT_ID}:role/$LAMBDA_IAM_ROLE_NAME" \
--runtime "$LAMBDA_RUNTIME" \
--zip-file "fileb://$LAMBDA_ZIP" \
| jq -S .
)"
LAMBDA_ARN="$( jq -r .Configuration.FunctionArn <<< "$LAMBDA_METADATA" )"
else
echo "The AWS Lambda function '$LAMBDA_NAME' already exists, so update it"
LAMBDA_METADATA="$( aws lambda update-function-code \
--function-name "$LAMBDA_NAME" \
--publish \
--zip-file "fileb://$LAMBDA_ZIP" \
| jq -S .
)"
LAMBDA_ARN="$( jq -r .FunctionArn <<< "$LAMBDA_METADATA" )"
fi
echo "AWS Lambda ARN is $LAMBDA_ARN"
writeYaml aws.lambda.addSubscriber.computed.arn "$LAMBDA_ARN"
echo "Attach the AWSLambdaBasicExecutionRole managed policy to $LAMBDA_IAM_ROLE_NAME."
aws iam attach-role-policy \
--role-name $LAMBDA_IAM_ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
#### Integrate with API Gateway for REST
#### Some or all of the following code is probably not required
GATEWAY_NAME="addSubscriberTo_$MC_LIST_ID_MSLINN"
API_GATEWAYS="$( aws apigateway get-rest-apis )"
if [ "$( jq ".items[] | select(.name | contains(\"$GATEWAY_NAME\"))" <<< "$API_GATEWAYS" )" ]; then
echo "API gateway '$GATEWAY_NAME' already exists."
else
echo "Creating API gateway '$GATEWAY_NAME'."
API_JSON="$( aws apigateway create-rest-api \
--name "$GATEWAY_NAME" \
--description "API for adding a subscriber to the Mailchimp list with ID '$MC_LIST_ID_MSLINN' for the '$DOMAIN' website"
)"
REST_API_ID="$( jq -r .id <<< "$API_JSON" )"
API_RESOURCES="$( aws apigateway get-resources --rest-api-id $REST_API_ID )"
ROOT_RESOURCE_ID="$( jq -r .items[0].id <<< "$API_RESOURCES" )"
NEW_RESOURCE="$( aws apigateway create-resource \
--rest-api-id "$REST_API_ID" \
--parent-id "$RESOURCE_ID" \
--path-part "{proxy+}"
)"
NEW_RESOURCE_ID=$( jq -r .id <<< $NEW_RESOURCE )
if false; then
# Is this step useful for any reason?
aws apigateway put-method \
--authorization-type "NONE" \
--http-method ANY \
--resource-id "$NEW_RESOURCE_ID" \
--rest-api-id "$REST_API_ID"
fi
# The following came from https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#set-up-lambda-proxy-integration-using-cli
# Instead of supplying an IAM role for --credentials, call the add-permission command to add resource-based permissions.
# I need an example of this.
# Alternatively, how to obtain IAM_ROLE_ID? Again, I need an example.
aws apigateway put-integration \
--credentials "arn:aws:iam::${IAM_ROLE_ID}:role/apigAwsProxyRole" \
--http-method ANY \
--integration-http-method POST \
--rest-api-id "$REST_API_ID" \
--resource-id "$NEW_RESOURCE_ID" \
--type AWS_PROXY \
--uri arn:aws:apigateway:`aws configure get region`:lambda:path/2015-03-31/functions/$LAMBDA_ARN
if [ "$LAMBDA_TEST"]; then
# Deploy the API to a test stage
aws apigateway create-deployment \
--rest-api-id "$REST_API_ID" \
--stage-name test
else
# Deploy the API live
aws apigateway create-deployment \
--rest-api-id "$REST_API_ID" \
--stage-name TODO_WhatNameGoesHere
fi
fi
echo "Check out the defined lambdas at https://console.aws.amazon.com/lambda/home?region=us-east-1#/functions"
【问题讨论】:
-
你不能直接从没有 JavaScript 的浏览器访问 lambda。您需要 api 网关。只是不要在上面设置任何安全性。这很容易。
-
我不同意“简单”,但如果你想要一个 lambda 函数来处理来自普通 HTML 表单的匿名 POST 请求的提交,那么 api gateway 是正确的工具。
-
@bryan60,我很想知道如何为简单的解决方案做些什么。如果你能提供一个简单的答案,请这样做。我可能已经在
deploy脚本的下半部分编写了大部分 API 网关代码,尽管它肯定需要更正。 -
只需使用 aws sam cli。设置脚本需要 15 分钟部署网关和 lambda
-
@bryan60 是的,是的,我知道。请具体。我一直在努力尝试得到一个正确的答案。
标签: amazon-web-services rest aws-lambda aws-api-gateway