0

我试图用CloudFormation创建一个Api-Gateway作为Lambda代理。在Lambda函数上获得正确的权限似乎存在问题,即使我已经全面查看并似乎尝试了一切可能,但我无处可去。围绕一些重要的小细节的文档似乎缺少,(或者我只是曲解它们?)。CloudFormation: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门户网站控制台API网关的测试,我得到的错误:Execution failed due to configuration error: Invalid permissions on Lambda function

这是推动我疯了。这里的任何方向都会很棒。我猜想我的权限在某种程度上是错误的,但我不知道如何(这是我与文档争执的地方)。

+0

我提供我的回答能解决你所描述的问题,一个完整的工作模板。如果您的问题仍未得到解答,请添加更多详细信息(例如,[最小,完整且可验证的示例](http://stackoverflow.com/help/mcve),其中包含您当前模板的确切源代码尝试仍然有问题)。 – wjordan

回答

0

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

arn:aws:execute-api:region:account-id:api-id/stage-name/HTTP-VERB/resource-path-specifier

其中:

  • region is the AWS region (such as us-east-1 or * for all AWS regions) that corresponds to the deployed API for the method.
  • account-id is the 12-digit AWS account Id of the REST API owner.
  • api-id is the identifier API Gateway has assigned to the API for the method. (* can be used for all APIs, regardless of the API's identifier.)
  • stage-name is the name of the stage associated with the method (* can be used for all stages, regardless of the stage's name.)
  • HTTP-VERB is the HTTP verb for the method. It can be one of the following: GET , POST , PUT , DELETE , PATCH , HEAD , OPTIONS .
  • resource-path-specifier is the path to the desired method. (* can be used for all paths).

您当前的模板提供:

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网关正确执行的拉姆达代理功能模板(我已经转换原来的例子YAML的可读性):

Launch Stack

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区域中存在不受支持的服务。

+0

我得到了和以前一样的错误。事实上,如果我没有任何lambda权限,我仍然会得到相同的错误。我可以用另一个'*'替换'$ {RestApi.RootResourceId}'使其更通用? 也许它不是lambda权限? – bwighthunter

+0

@bwighthunter预计您会在没有任何权限的情况下发生相同的错误,因为需要权限才能使事件源有权访问Lambda函数。我认为尝试使用另一个'*'(甚至完全删除'SourceArn'属性)是一个好主意,以查看该函数是否可以根据您创建的权限资源工作,然后从那里向后工作以添加更多限制性权限。 – wjordan

+0

对,我知道它会有相同的错误。权限错误=没有权限。 我摆脱了'SourceArn'完全没有得到。也许这不是lambda权限? – bwighthunter

0

与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 。 https://github.com/AWSinAction/apigateway/blob/master/template.json

此致: 我根据迈克尔·维蒂希的GitHub上的例子编辑我的回答

"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

+0

我同意,这是它应该看起来,但即使我完全删除'SourceArn',我仍然得到我的错误。删除'SourceArn'意味着它给任何api网关方法调用它的能力,是正确的? – bwighthunter

+0

我认为没有SourceArn会意味着什么都不会说,但我不确定。 我编辑了我的答案。看看是否有效。如果没有,我会再试一次。 –

+0

我不认为这是问题。我改变了我的脚本以包括'AccountId',我仍然得到相同的东西。 – bwighthunter

0

您ApiGateway可能没有正确的打电话给你的lambda表达式。尝试下面的步骤;

选择API网关休息终点 选择资源 选择你的方法之一 选择“合并请求” 点击编辑按钮旁边的“lambda表达式” 点击确认按钮,这一步会询问您是否允许你是给你的权利ApiGateway打电话给你的lambda函数 确认接入权

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

+0

是的,这是有效的,但我希望这是脚本在云层。我需要能够运行该脚本,并且只需要这样做。在控制台中这个动作实际上做了什么,看起来像是在策略/角色中? – bwighthunter

+0

它会是这样的.... $ AWS拉姆达附加许可 --function名** ** LambdaFunctionOverHttps --statement-ID apigateway测试-2 --action拉姆达:InvokeFunction --principal apigateway.amazonaws.com --source-arn“arn:aws:execute-api:us-east-1:** aws-acct-id **:** api-id **/*/POST/DynamoDBManager” – vedat

+0

我希望能够在云层中形成它,而不是aws-cli。但是谢谢。 – bwighthunter