我有一个通过 apex 创建的 AWS lambda 函数。我还通过 terraform 创建了一个 SNS 主题和订阅。
我的主题是:
arn:aws:sns:ap-southeast-1:178284945954:fetch_realm_auctions
我有一个订阅:
arn:aws:sns:ap-southeast-1:178284945954:fetch_realm_auctions:2da1d182-946d-4afd-91cb-1ed3453c5d86
,类型为 lambda
,端点为:arn:aws:lambda:ap-southeast-1:178284945954:function:wowauctions_get_auction_data
我已确认这是正确的函数 ARN。一切似乎都正确连接:
我手动触发SNS:
aws sns publish
--topic-arn arn:aws:sns:ap-southeast-1:178284945954:fetch_realm_auctions
--message '{"endpoint": "https://us.api.battle.net", "realm": "spinebreaker"}'
它返回消息ID,但没有发生调用。为什么?
我添加了一个内联策略以允许调用 lambda:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1474873816000",
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": [
"arn:aws:lambda:ap-southeast-1:178284945954:function:wowauctions_get_auction_data"
]
}
]
}
现在可以工作了。
SNS主题需要有调用Lambda的权限。
这是一个如何在 Terraform 中表达这一点的示例:
# Assumption: both SNS topic and Lambda are deployed in the same region
# resource "aws_sns_topic" "instance" { ... }
# resource "aws_lambda_function" "instance" {... }
# Step 1: Allow the SNS topic to invoke the Lambda
resource "aws_lambda_permission" "allow_invocation_from_sns" {
statement_id = "AllowExecutionFromSNS"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.instance.function_name}"
principal = "sns.amazonaws.com"
source_arn = "${aws_sns_topic.instance.arn}"
}
# Step 2: Subscribe the Lambda to the SNS topic
resource "aws_sns_topic_subscription" "instance" {
topic_arn = "${aws_sns_topic.instance.arn}"
protocol = "lambda"
endpoint = "${aws_lambda_function.instance.arn}"
}
解决此问题的一些一般技巧(Lambda 未被触发):
endpoint
必须与Lambda的ARN完全匹配)确认这些基本检查后,您仍然没有看到任何调用,这一定是权限错误。当您在 AWS 控制台中打开 Lambda 时,您应该会看到 SNS 被列为触发器:
作为比较,如果缺少权限,您将看不到 SNS:
如果您不使用自动部署(例如,使用 CloudFormation 或 Terraform),您还可以手动添加缺少的权限:
SNS
下的Add triggers
(您需要在列表中向下滚动才能看到它)Configure triggers
中,选择SNS主题Add
并保存 Lambda对我来说,问题是我在 cloudformation 模板中的
SourceAccount
中指定了 AWS::Lambda::Permission
参数,并且 documentation 说明了以下内容:
添加策略时,请勿使用 --source-account 参数将源账户添加到 Lambda 策略。 Amazon SNS 事件源不支持源账户,将导致访问被拒绝。这不会影响安全,因为源帐户包含在源 ARN 中。
当我删除
SourceAccount
后,一切正常。
正如 Robo 在评论中提到的,添加基于
Principal
的权限是最简单的方法:
"FooFunctionPermission" : {
"Type" : "AWS::Lambda::Permission",
"Properties" : {
"Action" : "lambda:InvokeFunction",
"FunctionName" : { "Ref" : "FooFunction" },
"Principal" : "sns.amazonaws.com"
}
}
有同样的问题: 1)创建并部署简单的lambda 2)从java sdk手动创建aws sns主题 3)从java sdk创建sns订阅(sns主题和sns主题之间的订阅) 拉姆达)
然后我遇到了一个问题,当从控制台向主题推送一些消息时 - 它没有被 lambda 拦截。而且,sns 触发器甚至没有在 lambda 中注册。
所以我简单地使用这个命令修复了这个问题: https://docs.aws.amazon.com/cli/latest/reference/lambda/add-permission.html
运行后
aws lambda add-permission ......
,一切都恢复正常。
这篇文章帮助我走得更远,但有一个缺失的部分。 Terraform 将创建错误的订阅。你必须放弃
$LATEST
resource "aws_sns_topic" "cloudwatch_notifications" {
name = "aws-${var.service_name}-${var.stage}-alarm"
}
data "aws_lambda_function" "cloudwatch_lambda" {
function_name = "sls-${var.service_name}-${var.stage}-cloudwatch-alarms"
}
resource "aws_lambda_permission" "with_sns" {
statement_id = "AllowExecutionFromSNS"
action = "lambda:InvokeFunction"
function_name = "${replace(data.aws_lambda_function.cloudwatch_lambda.arn, ":$LATEST", "")}"
principal = "sns.amazonaws.com"
source_arn = "${aws_sns_topic.cloudwatch_notifications.arn}"
}
resource "aws_sns_topic_subscription" "cloudwatch_subscription" {
topic_arn = "${aws_sns_topic.cloudwatch_notifications.arn}"
protocol = "lambda"
endpoint = "${replace(data.aws_lambda_function.cloudwatch_lambda.arn, ":$LATEST", "")}"
}
这是这个问题的具体答案 - 我已经在其他地方删除了我的其他答案!
对于 Terraform 用户,另请参阅此处: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission
显示“aws_lambda_permission”资源的使用;其中一个示例涵盖了 SNS,复制到此处:
resource "aws_lambda_permission" "with_sns" {
statement_id = "AllowExecutionFromSNS"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.func.function_name
principal = "sns.amazonaws.com"
source_arn = aws_sns_topic.default.arn
}
在我的例子中,lambda被触发,我收到了消息,但有些消息丢失/没有到达我的服务器,然后我承认我犯了一个非常愚蠢的错误,没有返回响应,因此连接从未关闭
return {
statusCode: 500,
body: JSON.stringify({ message: "Error sending message" })
};
只需发回响应,您就会收到发出的所有消息