授予 SNS 调用对 lambda 函数别名的访问权限,同时使用 CDK 在单独的堆栈中创建 SNS 和 lambda 函数



我的问题和这个差不多。但我在问一遍,因为没有提供答案(包括接受)为我工作。

场景是这样的。我有3个堆栈

  1. SNS +主题堆栈
  2. Lambda函数+别名栈
  3. 集成堆栈(其中我将lambda函数作为订阅者连接到SNS主题)

我不打算用前两个堆栈的细节来烦你,因为它们在这里不重要。下面是最后一层的CDK代码:

const topicArn = `arn:aws:sns:${stack.region}:${Aws.ACCOUNT_ID}:${topic_name}`;
const snsTopic = Topic.fromTopicArn(stack, `${props.name}Topic`, topicArn);
const lambdaArn = `arn:aws:lambda:${stack.region}:${Aws.ACCOUNT_ID}:function:${lambda.name}`;
const version = Version.fromVersionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
lambda: Function.fromFunctionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
functionArn: lambdaArn,
role: Role.fromRoleArn( stack, `sns-${props.name}-${lambda.name}-role-${stack.region}`, props.roleArn),
}),
version: `some-alias`,
});
snsTopic.addSubscription(new LambdaSubscription(version, {
filterPolicy: {/*some filter policy*/},
}));
version.grantInvoke(new ArnPrincipal(topicArn));

在我们继续之前,我知道我正在使用Version.fromVersionAttributes,同时提供"别名"。而不是版本号。感谢上帝,这是有效的,因为如果没有,我就无法完成我的目标。您可以看到,方法Alias.fromAliasAttributes需要一个"版本";对象返回别名。但是我没有版本号来返回lambda函数的version对象。至少,使用Version.fromVersionAttributes,如果我提供别名字符串而不是版本号,我可以获得别名!

无论如何,上面的代码片段将lambda函数的别名加载到version对象中,并将SNS的主题加载到snsTopic中。然后它做两件事。首先,它将lambda函数的别名下标到主题。其次,它授予SNS调用lambda函数的权限。

这在纸面上都很好,但它不起作用。当我尝试合成CDK代码时,它会抛出以下异常:

Error: Cannot modify permission to lambda function. Function is either imported or $LATEST version.
If the function is imported from the same account use `fromFunctionAttributes()` API with the `sameEnvironment` flag.
If the function is imported from a different account and already has the correct permissions use `fromFunctionAttributes()` API with the `skipPermissions` flag.
at Object.addToResourcePolicy (/home/.../node_modules/@aws-cdk/aws-lambda/lib/function-base.ts:522:19)
at Function.addToPrincipalOrResource (/home/.../node_modules/@aws-cdk/aws-iam/lib/grant.ts:143:45)
at Import.grant (/home/.../node_modules/@aws-cdk/aws-lambda/lib/function-base.ts:504:29)
at Import.grantInvoke (/home/.../node_modules/@aws-cdk/aws-lambda/lib/function-base.ts:422:20)

grantInvoke方法抛出。所以,我尝试了另一种方法:

const topicArn = `arn:aws:sns:${stack.region}:${Aws.ACCOUNT_ID}:${topic_name}`;
const snsTopic = Topic.fromTopicArn(stack, `${props.name}Topic`, topicArn);
const lambdaArn = `arn:aws:lambda:${stack.region}:${Aws.ACCOUNT_ID}:function:${lambda.name}`;
const version = Version.fromVersionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
lambda: Function.fromFunctionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
functionArn: lambdaArn,
role: Role.fromRoleArn( stack, `sns-${props.name}-${lambda.name}-role-${stack.region}`, props.roleArn),
}),
version: `some-alias`,
});
snsTopic.addSubscription(new LambdaSubscription(version, {
filterPolicy: {/*some filter policy*/},
}));
version.addPermission(`SNS-${props.name}-${lambda.name}`, {
scope: stack,
principal: new ServicePrincipal("sns.amazonaws.com"),
sourceArn: topicArn,
action: "lambda.InvokeFunction",
});

很像之前的那个,但它使用的不是grantInvoke,而是addPermission方法。这一次,它没有抛出任何异常。但同时,生成的模板没有添加权限的概念。并且它不授予SNS调用lambda函数的权限。基本上,它什么也不做。

最后需要注意的是,如果我不尝试向SNS授予调用权限,生成的模板就可以正常工作。它将lambda函数的别名集成为SNS主题的订阅者。只是当我尝试将消息发布到SNS主题时,因为它没有调用lambda函数别名的权限,它将出错,我可以在CloudWatch日志中看到。但至少CDK代码和CloudFormation是高兴的(并不是说我关心他们的感受)。

有谁知道怎么做这个工作吗?

(更新)

再澄清一下。如果我忘记了授予SNS调用lambda函数和部署堆栈权限的那部分代码,那么我将得到一个SNS主题、一个lambda函数和一个别名。别名正确地订阅了SNS主题,但是正如我前面提到的,因为SNS没有被授予调用lambda函数别名的权限,所以如果我发布消息,它将失败。但是,如果我使用AWS控制台手动向SNS授予调用权限,那么一切都将按照我希望的方式进行。所以基本上,我知道我要做的是可行的,唯一的问题是如何使用CDK。

(更新)

为了涵盖所有可能的解决方案,我还测试了这两个属性(在抛出的错误消息中提到)。

const version = Version.fromVersionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
lambda: Function.fromFunctionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
functionArn: lambdaArn,
role: Role.fromRoleArn( stack, `sns-${props.name}-${lambda.name}-role-${stack.region}`, props.roleArn),
skipPermissions: true,
}),
version: `some-alias`,
});

const version = Version.fromVersionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
lambda: Function.fromFunctionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
functionArn: lambdaArn,
role: Role.fromRoleArn( stack, `sns-${props.name}-${lambda.name}-role-${stack.region}`, props.roleArn),
sameEnvironment: true,
}),
version: `some-alias`,
});

但是两者都会抛出与之前完全相同的异常。

(更新)

正如我所指出的,我没有提到我使用的CDK版本。

$ npm run cdk -- --version
2.21.1 (build a6ee543)

但是我使用的库是不同的版本。从我的包裹里。json文件:

"dependencies": {
"@aws-cdk/aws-apigateway": "^1.129.0",
"@aws-cdk/aws-applicationautoscaling": "^1.129.0",
"@aws-cdk/aws-certificatemanager": "^1.129.0",
"@aws-cdk/aws-cloudwatch": "^1.129.0",
"@aws-cdk/aws-codebuild": "^1.129.0",
"@aws-cdk/aws-codecommit": "^1.129.0",
"@aws-cdk/aws-codepipeline": "^1.129.0",
"@aws-cdk/aws-codepipeline-actions": "^1.129.0",
"@aws-cdk/aws-dynamodb": "^1.129.0",
"@aws-cdk/aws-iam": "^1.129.0",
"@aws-cdk/aws-kms": "^1.129.0",
"@aws-cdk/aws-lambda": "^1.129.0",

显然,这是CDK的一个缺点(我不确定这是否是一个bug)。

解决方案是使用底层的CloudFormation api。这样的:

const topicArn = `arn:aws:sns:${stack.region}:${Aws.ACCOUNT_ID}:${topic_name}`;
const snsTopic = Topic.fromTopicArn(stack, `${props.name}Topic`, topicArn);
const lambdaArn = `arn:aws:lambda:${stack.region}:${Aws.ACCOUNT_ID}:function:${lambda.name}`;
const version = Version.fromVersionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
lambda: Function.fromFunctionAttributes(stack, `sns-${props.name}-${lambda.name}`, {
functionArn: lambdaArn,
role: Role.fromRoleArn( stack, `sns-${props.name}-${lambda.name}-role-${stack.region}`, props.roleArn),
sameEnvironment: true,
}),
version: `some-alias`,
});
snsTopic.addSubscription(new LambdaSubscription(version, {
filterPolicy: {/*some filter policy*/},
}));
new CfnPermission(stack, `SNS-${props.name}-${lambda.name}`, {
action: "lambda:InvokeFunction",
principal: "sns.amazonaws.com",
functionName: `${lambdaArn}:${props.tenant}-${props.env}`,
sourceArn: topicArn,
});

最新更新