首页 文章

CloudFormation:Lambda函数的权限无效

提问于
浏览
1

我正在尝试使用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网关中运行测试时,我收到错误: Execution failed due to configuration error: Invalid permissions on Lambda function

这让我很生气 . 这里任何方向都会很棒 . 我猜我的权限在某种程度上是错误的,但我不确定如何(这是我在文档中挣扎的地方) .

3 回答

  • 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 . 我在GitHub上的例子是'm editing my answer based on Michael Wittig':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"}
    

    亚马逊说"ARNs for some resources don't require an account number, so this component might be omitted."但是's unclear which ones require and which don' t .

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

  • 0

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

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

    哪里:

    region是AWS区域(例如us-east-1或所有AWS区域的*),对应于方法的已部署API . account-id是REST API所有者的12位AWS账户ID . api-id是API网关为该方法分配给API的标识符 . (*可用于所有API,无论API的标识符如何 . )stage-name是与方法关联的阶段的名称(*可用于所有阶段,无论阶段名称如何 . )HTTP-VERB是该方法的HTTP动词 . 它可以是以下之一:GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS . 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):

    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

    您的ApiGateway可能没有权利调用您的lambda函数 . 尝试以下步骤;

    选择你的API网关休息终点选择资源选择你的一种方法选择'整合请求'点击你的'Lambda函数'点击确认按钮旁边的编辑按钮,这一步将要求你批准你正在给你的ApiGateway打电话给你的lambda功能确认访问权限

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

相关问题