【问题标题】:CloudFormation: Invalid permissions on Lambda functionCloudFormation:Lambda 函数的权限无效
【发布时间】:2017-06-08 14:18:10
【问题描述】:

我正在尝试仅使用 CloudFormation 创建一个 Api-Gateway 作为 Lambda 代理。获得 Lambda 函数的正确权限似乎存在问题,即使我已经查看了所有内容并且似乎尝试了所有可能的方法,但我一无所获。围绕一些重要小细节的文档似乎丢失了,(或者我只是误解了它们?)。

这是我所拥有的:

    {
        "Description": "",
        "Parameters": {
            "IngressLambdaName": {
                "Type": "String",
                "Description": "Name of the lambda behind Api Gateway",
                "Default": "LambdaIngress"
            }
        },

        "Mappings": {

        },

        "Resources": {
            "ApiGatewayToLambdaRole": {
                "Type": "AWS::IAM::Role",
                "Properties": {
                    "AssumeRolePolicyDocument": {
                        "Version": "2012-10-17",
                        "Statement": [ {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": [ "apigateway.amazonaws.com" ]
                            },
                            "Action": "sts:AssumeRole"
                        }]
                    },
                    "Policies": [{
                        "PolicyName": "ApiGatewayToLambdaPolicy",
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [{
                                "Effect": "Allow",
                                "Action": [
                                    "lambda:InvokeFunction"
                                ],
                                "Resource": "*"
                            }]
                        }
                    }]
                }
            },

            "IngressLambda":{
                "Type": "AWS::Lambda::Function",
                "Properties": {
                    "Handler": "index.handler",
                    "FunctionName": {"Ref": "IngressLambdaName"},
                    "Runtime": "nodejs4.3",
                    "Role": { "Fn::GetAtt": ["**Role that isn't shown here**", "Arn"]},
                    "Code": {
                        "ZipFile": { "Fn::Join": ["", [
                            "exports.handler = function(event, context) {",
                    "  console.log('invoked the lambda!');",
                    "  context.succeed({statusCode: 200, headers: {}, body: JSON.stringify({message: 'invoked the lambda!'})});",
                    "};"
                        ]]}
                    }
                }

            },

            "IngressLambdaPermission":{
                "Type" : "AWS::Lambda::Permission",
                "Properties" : {
                    "Action" : "lambda:InvokeFunction",
                    "FunctionName" : { "Ref" : "IngressLambdaName"},
                    "Principal" : "apigateway.amazonaws.com",
                    "SourceArn" : {"Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/POST/*"}
                },
                "DependsOn": ["IngressLambda"]
            },

            "RestApi": {
                "Type": "AWS::ApiGateway::RestApi",
                "Properties": {
                    "Name": "API Gateway"
                }
            },

            "TagModel": {
                "Type": "AWS::ApiGateway::Model",
                "Properties": {
                    "ContentType": "application/json",
                    "Name": "Tag",
                    "RestApiId": { "Ref": "RestApi" },
                    "Schema": {
                        "$schema": "http://json-schema.org/draft-04/schema#",
                        "title": "TagModel",
                        "type": "object",
                        "properties": {
                            "payload": {"type": "object"},
                            "domain": {"type": "string"}
                        }
                    }
                }
            },

            "TagsResource": {
                "Type": "AWS::ApiGateway::Resource",
                "Properties": {
                    "RestApiId": { "Ref": "RestApi" },
                    "ParentId": { "Fn::GetAtt": ["RestApi", "RootResourceId"] },
                    "PathPart": "tag"
                }
            },

            "TagsPost": {
                "Type": "AWS::ApiGateway::Method",
                "Properties": {
                    "ApiKeyRequired": "False",
                    "AuthorizationType": "NONE",
                    "HttpMethod": "POST",
                    "RestApiId": {"Ref": "RestApi"},
                    "ResourceId": { "Fn::GetAtt": ["RestApi", "RootResourceId"] },
                    "Integration": {
                        "Type": "AWS_PROXY",
                        "IntegrationHttpMethod": "POST",
                        "PassthroughBehavior": "NEVER", 
                        "Uri": {"Fn::Join" : ["", ["arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/", {"Fn::GetAtt": ["IngressLambda", "Arn"]}, "/invocations"]]}
                    }
                }
            },

            "RestApiDeployment": {
                "Type": "AWS::ApiGateway::Deployment",
                "Properties": {
                    "RestApiId": { "Ref": "RestApi" },
                    "StageName": "v1"
                },
                "DependsOn": ["RestApi", "TagModel", "TagsResource", "TagsPost"]
            },

        },

        "Outputs": {

        }

    }

在 aws Web 门户控制台中的 API Gateway 中运行测试时,我收到错误:Execution failed due to configuration error: Invalid permissions on Lambda function

这快把我逼疯了。这里的任何方向都会很棒。我猜我的权限在某种程度上是错误的,但我不确定如何(这是我与文档斗争的地方)。

【问题讨论】:

  • 我在回答中提供了一个完整的工作模板,可以解决您描述的问题。如果您的问题仍未得到解答,请添加更多详细信息(例如,minimal, complete and verifiable example 包含您当前正在尝试但仍有问题的模板的确切源代码)。

标签: amazon-web-services aws-lambda aws-api-gateway amazon-cloudformation


【解决方案1】:

类似于 wjordan 最近的评论,我认为源 arn 是问题所在。它应该是这样的格式:

arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/API_NAME

因为这是我使用 CLI 执行命令的方式:

aws lambda add-permission --function-name ${FUNCTION_ARN} --action "lambda:InvokeFunction" --statement-id 1 --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:"${REGION}":"${ACCOUNT_ID}":"${API_ID}"/*/*/"${API_NAME}

我进行了一些挖掘,这可能是因为您错过了帐户 ID。 我正在根据 Michael Wittig 在 GitHub 上的示例编辑我的答案: https://github.com/AWSinAction/apigateway/blob/master/template.json

你的:

"SourceArn" : { "Fn::Join" : ["", ["arn:aws:execute-api:us-west-2::", {"Fn::GetAtt": ["RestApi", "RootResourceId"]}, "/null/POST" ]]}

他的:

"SourceArn": {"Fn::Join": ["", ["arn:aws:execute-api:", {"Ref": "AWS::Region"}, ":", {"Ref": "AWS::AccountId"}, ":", {"Ref": "RestApi"}, "/*"]]}

注意他是如何使用参考的:

{"Ref": "AWS::AccountId"}

亚马逊表示“某些资源的 ARN 不需要帐号,因此可能会省略此组件。” 但不清楚哪些需要哪些不需要。

参考:http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html

【讨论】:

  • 我同意,这就是它的外观,但即使我完全删除了SourceArn,我仍然会收到错误消息。删除 SourceArn 意味着它可以让任何 api 网关方法调用它,对吗?
  • 我认为没有 SourceArn 意味着没有什么可以调用它,但我不确定。我已经编辑了我的答案。看看这是否有效。如果没有,我会再试一次。
  • 我不认为这是问题所在。我更改了上面的脚本以包含AccountId,但我仍然得到相同的东西。
  • 在原帖中不再是“/null/POST”,而是“/*/POST/*”
【解决方案2】:

根据 API Gateway 文档部分 Resource Format of Permissions for Executing API in API GatewaySourceArn 属性应具有以下结构:

arn:aws:execute-api:region:account-id:api-id/stage-name/HTTP-VERB/资源路径说明符

地点:

  • region 是与为该方法部署的 API 对应的 AWS 区域(例如 us-east-1 或 * 表示所有 AWS 区域)。
  • account-id 是 REST API 所有者的 12 位 AWS 账户 ID。
  • api-id 是 API Gateway 为方法分配给 API 的标识符。 (* 可用于所有 API,无论 API 的标识符如何。)
  • stage-name 是与方法关联的阶段的名称(* 可用于所有阶段,无论阶段名称如何。)
  • HTTP-VERB 是该方法的 HTTP 动词。它可以是以下之一:GETPOSTPUTDELETEPATCHHEADOPTIONS
  • resource-path-specifier 是所需方法的路径。 (* 可用于所有路径)。

您当前提供的模板:

arn:aws:execute-api:us-west-2::${RestApi.RootResourceId}/null/POST

尝试这样的事情(使用Fn::Sub${} 语法来简化引用):

arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/POST/*

另外,代理集成的Lambda函数必须根据Output Format of a Lambda Function for Proxy Integration返回输出,否则会返回502 Bad Gateway错误:

{
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "body": "..."
}

将您的 Lambda 函数更改为以下内容:

exports.handler = function(event, context) {
  context.succeed({statusCode: 200, headers: {}, body: "invoked the lambda!"});
};

这是一个完整的、独立的、有效的示例模板,它演示了从 API 网关正确执行的 Lambda 代理函数(为了便于阅读,我已将原始示例转换为 YAML):

Resources:
  ApiGatewayToLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal: {Service: [apigateway.amazonaws.com]}
          Action: "sts:AssumeRole"
      Policies:
      - PolicyName: ApiGatewayToLambdaPolicy
        PolicyDocument:
          Version: 2012-10-17
          Statement:
          - Effect: Allow
            Action: ["lambda:InvokeFunction"]
            Resource: "*"
  IngressLambda:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs4.3
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          exports.handler = (event, context) =>
            context.succeed({statusCode: 200, headers: {}, body: "Invoked the Lambda!"});
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal: {Service: [lambda.amazonaws.com]}
          Action: ['sts:AssumeRole']
      Path: /
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  IngressLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: "lambda:InvokeFunction"
      FunctionName: !Ref IngressLambda
      Principal: "apigateway.amazonaws.com"
      SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/POST/*"
  RestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: API Gateway
  TagModel:
    Type: AWS::ApiGateway::Model
    Properties:
      ContentType: application/json
      Name: Tag
      RestApiId: !Ref RestApi
      Schema:
        $schema: "http://json-schema.org/draft-04/schema#"
        title: TagModel
        type: object
        properties:
          payload: {type: object}
          domain: {type: string}
  TagsResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref RestApi
      ParentId: !GetAtt RestApi.RootResourceId
      PathPart: tag
  TagsPost:
    Type: AWS::ApiGateway::Method
    Properties:
      ApiKeyRequired: False
      AuthorizationType: NONE
      HttpMethod: POST
      RestApiId: !Ref RestApi
      ResourceId: !GetAtt RestApi.RootResourceId
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        PassthroughBehavior: NEVER
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${IngressLambda.Arn}/invocations"
  RestApiDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: [RestApi, TagModel, TagsResource, TagsPost]
    Properties:
      RestApiId: !Ref RestApi
      StageName: v1
  HttpRequestFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: Returns an HTTP request as a Custom Resource
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: !Sub |
          var response = require('cfn-response');
          exports.handler = (event, context) => {
              console.log("Request received:\n", JSON.stringify(event));
              var success = data => response.send(event, context, response.SUCCESS, data);
              var failed = e => response.send(event, context, response.FAILED, e);
              process.on('uncaughtException', e=>failed(e));
            try {
              if (event.RequestType == 'Delete') { return success({}); }
              var https = require("https");
              var url = require("url");
              var parsedUrl = url.parse(event.ResourceProperties.Url);
              var options = {
                hostname: parsedUrl.hostname,
                path: parsedUrl.path,
                method: event.ResourceProperties.Method || 'GET',
              };
              var request = https.request(options, response => {
                console.log("Status code: " + response.statusCode);
                console.log("Status message: " + response.statusMessage);
                var body = '';
                response.setEncoding('utf8');
                response.on('data', chunk => body += chunk);
                response.on('end', ()=>success({Data: body}));
              });
              request.on("error", e=>failed(e));
              request.end();
            } catch (e) { failed(e); }
          };
      Timeout: 30
      Runtime: nodejs4.3
  HttpRequest:
    Type: Custom::HttpRequest
    DependsOn: RestApiDeployment
    Properties:
      ServiceToken: !GetAtt HttpRequestFunction.Arn
      Url: !Sub "https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/v1"
      Method: POST
Outputs:
  Result:
    Value: !GetAtt HttpRequest.Data

此堆栈在其堆栈输出中返回 Invoked the lambda!,指示对由简单 Lambda 函数支持的新创建的 API 网关的成功 HTTP 请求。如果失败,可能是您的 CloudFormation 堆栈缺少 IAM 权限,或者您的 AWS 区域中存在不受支持的服务。

【讨论】:

  • 我得到了和以前一样的错误。事实上,如果我根本没有 lambda 权限,我仍然会得到同样的错误。我也许可以用另一个* 替换${RestApi.RootResourceId} 以使其更通用?也许根本就不是 lambda 权限?
  • @bwighthunter 预计您在没有任何权限的情况下也会遇到相同的错误,因为需要权限才能让事件源有权调用 Lambda 函数。我认为尝试使用另一个 *(甚至完全删除 SourceArn 属性)来查看该函数是否完全适用于您创建的 Permission 资源是一个好主意,然后从那里向后工作以添加更多限制性权限。
  • 对,我知道它会有同样的错误。错误写入的权限 = 没有权限。我完全摆脱了SourceArn,我什么也没得到。也许不是 lambda 权限?
  • @bwighthunter 我开始直接测试您提供的模板,并对其进行了另外两个更改:首先,api-id SourceArn 组件需要为${RestApi} 而不是${RestApi.RootResourceId}。其次,Lambda 函数需要返回符合 Lambda-Proxy 集成预期结构的响应,例如,context.succeed({statusCode: 200, headers: {}, body: JSON.stringify({message: "invoked the lambda!"})});。希望这会有所帮助。
  • 我按照你的建议做了(我想)。这就是 SourceArn 的样子:{"Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${RestApi}/*/POST/*"},但仍然没有骰子。我还更改了我的 lambda 代码以执行 context.succeed()
【解决方案3】:

您的 ApiGateway 可能无权调用您的 lambda 函数。尝试以下步骤;

选择您的 API Gateway Rest 端点 选择资源 选择一种方法 选择“集成请求” 单击“Lambda 函数”旁边的编辑按钮 单击确认按钮,此步骤将要求您批准您授权您的 ApiGateway 调用您的 lambda 函数 确认访问权限

--- 现在你可以重新测试你的方法了

【讨论】:

  • 是的,这行得通,但我希望在 Cloud Formation 中编写脚本。我需要能够运行脚本并让它正常工作。控制台中的此操作在策略/角色中的实际作用和外观是什么?
  • 会是这样的...... $ aws lambda add-permission --function-name LambdaFunctionOverHttps --statement-id apigateway-test-2 --action lambda:InvokeFunction --principal apigateway.amazonaws.com --source-arn "arn:aws:execute-api:us-east-1: aws-acct-id : api-id /*/POST/DynamoDBManager"
  • 我希望将它用于云形成,而不是 aws-cli。不过谢谢。
  • 你有没有想过如何通过 CloudFormation 做到这一点?
猜你喜欢
  • 1970-01-01
  • 2017-04-03
  • 1970-01-01
  • 2013-07-17
  • 2017-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-03
相关资源
最近更新 更多