#amazon-web-services #rest #aws-lambda #aws-api-gateway
#amazon-веб-сервисы #rest #aws-lambda #aws-api-gateway
Вопрос:
Я хочу, чтобы на моем веб-сайте Jekyll была форма, которую посетители могли бы заполнять, и form
action
должна POST
использоваться функция AWS Lambda. На веб-сайте не разрешен JavaScript, поэтому POST
не должно требоваться подписание.
Я хочу максимально простую настройку и не нуждаюсь в высокой безопасности. Если есть способ избежать использования AWS API Gateway для создания HTTP API и каким-то образом заставить функцию Lambda напрямую получать POST
из веб-браузера пользователя, это было бы идеально. Если требуется API Gateway, то самое простое решение было бы наилучшим.
Я хочу использовать исключительно команды командной строки (не веб-браузер) для работы с AWS API. Это позволяет использовать скриптовое решение.
Я потратил некоторое время на проблему, и вот что у меня получилось. Я пометил вопросы в deploy
скрипте с помощью TODO
. В этом скрипте есть некоторый дополнительный код, который может не понадобиться. Проблема в том, что я не уверен, что удалять, потому что я просто не могу понять, как предоставить POST
лямбде.
Скрипты используют 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>amp;1 amp;amp; 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>amp;1 amp;amp; 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"
Комментарии:
1. Вы не можете получить доступ к lambda напрямую из браузера без JavaScript. Вам нужен шлюз api. Просто не устанавливайте на него никаких мер безопасности. Это просто.
2. Я не согласен с «простым», но если вы хотите, чтобы функция lambda обрабатывала отправку анонимного запроса POST из ванильной HTML-формы, api gateway — подходящий инструмент для использования.
3. @bryan60, я хотел бы знать, что делать для простого решения. Если вы можете дать простой ответ, пожалуйста, сделайте это. Я, вероятно, написал большую часть кода API Gateway в нижней половине
deploy
скрипта, хотя он, безусловно, нуждается в исправлениях.4. Просто используйте интерфейс командной строки aws sam. Настройка сценария для развертывания шлюза и лямбды занимает 15 минут
5. @bryan60 Да, да, я знаю. Пожалуйста, будьте конкретны. Я усердно работал, чтобы попытаться получить правильный ответ.
Ответ №1:
Инфраструктура сценариев Bash плоха. Возможно, в конечном итоге вы все сделаете правильно, но есть инструменты, которые делают процесс бесконечно проще.
Я предпочитаю Terraform, и вот как будет выглядеть API Gateway lambda:
provider "aws" {
}
# lambda
resource "random_id" "id" {
byte_length = 8
}
data "archive_file" "lambda_zip" {
type = "zip"
output_path = "/tmp/lambda.zip"
source {
content = <<EOF
module.exports.handler = async (event, context) => {
// write the lambda code here
}
};
EOF
filename = "main.js"
}
}
resource "aws_lambda_function" "lambda" {
function_name = "${random_id.id.hex}-function"
filename = data.archive_file.lambda_zip.output_path
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
handler = "main.handler"
runtime = "nodejs12.x"
role = aws_iam_role.lambda_exec.arn
}
data "aws_iam_policy_document" "lambda_exec_role_policy" {
statement {
actions = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
resources = [
"arn:aws:logs:*:*:*"
]
}
}
resource "aws_cloudwatch_log_group" "loggroup" {
name = "/aws/lambda/${aws_lambda_function.lambda.function_name}"
retention_in_days = 14
}
resource "aws_iam_role_policy" "lambda_exec_role" {
role = aws_iam_role.lambda_exec.id
policy = data.aws_iam_policy_document.lambda_exec_role_policy.json
}
resource "aws_iam_role" "lambda_exec" {
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow"
}
]
}
EOF
}
# api gw
resource "aws_apigatewayv2_api" "api" {
name = "api-${random_id.id.hex}"
protocol_type = "HTTP"
target = aws_lambda_function.lambda.arn
}
resource "aws_lambda_permission" "apigw" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda.arn
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.api.execution_arn}/*/*"
}
output "domain" {
value = aws_apigatewayv2_api.api.api_endpoint
}
Обратите внимание, что последние 2 ресурса являются шлюзом API, все предыдущие предназначены для функции Lambda.
Комментарии:
1. Спасибо за такой подробный ответ. Тем не менее, я на самом деле ищу решение bash. Кажется, что последние 2 ресурса содержат ответ. Если показанный вами код шлюза API был описан серией вызовов aws cli, тогда я мог бы протестировать, используя CURL для ОТПРАВКИ в конечную точку.