Terraform中AWS Lambda权限策略超限问题及解决方案咨询
我有一个名为console的Lambda函数,用于通过PHP Symfony的控制台命令运行所有定时任务。由于代码库是大型单体应用,为每个命令单独创建Lambda仍需包含99.9%的相同代码,部署难度极大,因此只使用这一个Lambda函数。
该Lambda函数由CloudWatch Events触发,基本是1条CloudWatch规则对应1个任务。由于我们还有无关的CloudWatch规则,希望确保只有指定规则可以触发该Lambda函数,不允许所有CloudWatch规则调用它。
我们使用Terraform进行管理,代码结构如下:
resource "aws_lambda_function" "console" { # 无关配置省略 } # 以下3个资源会为每个任务重复N次 resource "aws_cloudwatch_event_rule" "run_task_foobar" { # 无关配置省略 } resource "aws_cloudwatch_event_target" "run_task_foobar" { rule = aws_cloudwatch_event_rule.run_task_foobar.name target_id = "foobar" arn = aws_lambda_function.console.arn input = "\" console run foobar \"" } resource "aws_lambda_permission" "run_task_foobar" { action = "lambda:InvokeFunction" function_name = aws_lambda_function.console.function_name principal = "events.amazonaws.com" source_arn = aws_cloudwatch_event_rule.run_task_foobar.arn # 不支持标签 }
目前我们共有53个任务,当尝试添加第54个任务时,出现以下错误:
PolicyLengthExceededException: The final policy size (20701) is bigger than the limit (20480).
在AWS控制台中可以看到生成的IAM策略非常庞大,示例如下:
{ "Version": "2012-10-17", "Id": "default", "Statement": [ { "Sid": "foo", "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:eu-west-1:xxxx:function:console", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:events:eu-west-1:xxxxx:rule/foo" } } }, { "Sid": "bar", "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:eu-west-1:xxxx:function:console", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:events:eu-west-1:xxxxx:rule/bar" } } }, ... // 重复53次 ] }
显然应该将Condition块合并为包含多个SourceArn的数组形式,伪代码如下:
{ "Version": "2012-10-17", "Id": "default", "Statement": [ { "Sid": "AllowConsoleTaskRules", "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:eu-west-1:xxxx:function:console", "Condition": { "ArnEquals": { "AWS:SourceArn": [ "arn:aws:events:eu-west-1:xxxxx:rule/bar", "arn:aws:events:eu-west-1:xxxxx:rule/foo", ... // 53个ARN ] } } } ] }
但Terraform和AWS控制台均自动生成独立的Statement条目,无法对Condition进行精细控制。
问题
- 是否存在实际可行的合并策略的方法?
- 是否可以通过直接创建IAM策略来作为替代方案?
我当前的临时解决方案是约定所有相关CloudWatch事件规则以"console-"开头,使用通配符匹配,但这似乎不够稳健。
解决方案
1. 合并策略的可行方法
可以通过Terraform直接构建Lambda的资源策略文档,将所有CloudWatch规则的ARN合并到同一个Condition中,替代逐个创建aws_lambda_permission资源的方式。
具体实现思路:
- 先将所有CloudWatch事件规则的ARN收集到一个列表中(如果是通过
for_each批量创建规则,可直接通过values(aws_cloudwatch_event_rule.run_task_*)获取所有ARN) - 使用
jsonencode构建完整的IAM策略文档,在Condition的ArnEquals中使用ARN数组 - 将该策略文档直接赋值给
aws_lambda_function的policy参数
示例Terraform代码:
# 假设通过for_each批量创建CloudWatch规则 locals { tasks = { foobar = "console run foobar" baz = "console run baz" # 其他任务... } } resource "aws_cloudwatch_event_rule" "run_task" { for_each = local.tasks name = "run-task-${each.key}" # 其他规则配置(比如schedule_expression) } resource "aws_cloudwatch_event_target" "run_task" { for_each = local.tasks rule = aws_cloudwatch_event_rule.run_task[each.key].name target_id = each.key arn = aws_lambda_function.console.arn input = "\"${each.value}\"" } # 构建合并后的Lambda资源策略 resource "aws_lambda_function" "console" { # 原有配置(filename, handler, runtime等) policy = jsonencode({ Version = "2012-10-17" Id = "default" Statement = [ { Sid = "AllowConsoleTaskRules" Effect = "Allow" Principal = { Service = "events.amazonaws.com" } Action = "lambda:InvokeFunction" Resource = self.arn Condition = { ArnEquals = { "AWS:SourceArn" = values(aws_cloudwatch_event_rule.run_task)[*].arn } } } ] }) }
这种方式会生成一条包含所有CloudWatch规则ARN的Statement,避免策略大小超限,同时严格限定了允许触发Lambda的规则范围。
2. 直接创建IAM策略作为替代方案
是的,可以通过单独创建IAM策略资源,然后将其作为Lambda的资源策略附加。注意Lambda的资源策略是直接关联到函数本身的,而非通过IAM角色(IAM角色策略控制Lambda能访问的资源,资源策略控制哪些实体能调用Lambda)。
示例代码:
# 定义合并后的IAM策略 resource "aws_iam_policy" "console_lambda_invoke" { name = "console-lambda-invoke-policy" description = "Allow specific CloudWatch Events rules to invoke the console Lambda" policy = jsonencode({ Version = "2012-10-17" Statement = [ { Sid = "AllowConsoleTaskRules" Effect = "Allow" Principal = { Service = "events.amazonaws.com" } Action = "lambda:InvokeFunction" Resource = aws_lambda_function.console.arn Condition = { ArnEquals = { "AWS:SourceArn" = values(aws_cloudwatch_event_rule.run_task)[*].arn } } } ] }) } # 将策略附加到Lambda函数 resource "aws_lambda_function" "console" { # 原有配置 policy = aws_iam_policy.console_lambda_invoke.policy }
这种方式和直接在Lambda函数中定义策略效果一致,好处是可以将策略单独管理,便于复用或修改。
注意事项
- 如果之前已创建多个
aws_lambda_permission资源,需先删除这些资源,避免新旧策略冲突 - 使用
values(aws_cloudwatch_event_rule.run_task)[*].arn可自动收集所有通过for_each创建的规则ARN,无需手动维护列表 - AWS的Condition支持
ArnEquals(精确匹配)或ArnLike(通配符匹配),通配符方式需确保不会包含无关规则,不如精确匹配稳健
内容的提问来源于stack exchange,提问作者allan.simon

