silvermine / serverless-plugin-cloudfront-lambda-edge

Adds Lambda@Edge support to Serverless
MIT License
296 stars 41 forks source link

lambda doesn't have required permissions #74

Closed fusionbeam closed 3 years ago

fusionbeam commented 3 years ago

Hi,

I've recently added a lambda to an existing stack and it turns out the plugin has not created the lambda:InvokeFunction permissions.

As a result when accessing a path triggering the lambda@edge associated with it I get the following error:


If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation. ```

using ``` "@silvermine/serverless-plugin-cloudfront-lambda-edge": "^2.2.3",``
and ```"aws-sdk": "^2.983.0",```.

I don't have any permissions defined in service.yml, just a basic configuration that used to work 4-5 months ago when the stack was created. 

Anyone else encountered this error? Any thoughts on how to work around it?

Thank you,
R
jthomerson commented 3 years ago

can you create a mcve?

or at least provide some of your serverless.yml and the generated CloudFormation template

without those, there's really no way to help

fusionbeam commented 3 years ago

update stack

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "The AWS CloudFormation template for this Serverless application",
  "Resources": {
    "ServerlessDeploymentBucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketEncryption": {
          "ServerSideEncryptionConfiguration": [
            {
              "ServerSideEncryptionByDefault": {
                "SSEAlgorithm": "AES256"
              }
            }
          ]
        }
      }
    },
    "ServerlessDeploymentBucketPolicy": {
      "Type": "AWS::S3::BucketPolicy",
      "Properties": {
        "Bucket": {
          "Ref": "ServerlessDeploymentBucket"
        },
        "PolicyDocument": {
          "Statement": [
            {
              "Action": "s3:*",
              "Effect": "Deny",
              "Principal": "*",
              "Resource": [
                {
                  "Fn::Join": [
                    "",
                    [
                      "arn:",
                      {
                        "Ref": "AWS::Partition"
                      },
                      ":s3:::",
                      {
                        "Ref": "ServerlessDeploymentBucket"
                      },
                      "/*"
                    ]
                  ]
                },
                {
                  "Fn::Join": [
                    "",
                    [
                      "arn:",
                      {
                        "Ref": "AWS::Partition"
                      },
                      ":s3:::",
                      {
                        "Ref": "ServerlessDeploymentBucket"
                      }
                    ]
                  ]
                }
              ],
              "Condition": {
                "Bool": {
                  "aws:SecureTransport": false
                }
              }
            }
          ]
        }
      }
    },
    "IndexUnderscoreedgeLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/retainhubcdn-staging-ashta-index_edge"
      }
    },
    "CdnUnderscoreedgeLogGroup": {
      "Type": "AWS::Logs::LogGroup",
      "Properties": {
        "LogGroupName": "/aws/lambda/retainhubcdn-staging-ashta-cdn_edge"
      }
    },
    "IamRoleLambdaExecution": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com",
                  "edgelambda.amazonaws.com"
                ]
              },
              "Action": [
                "sts:AssumeRole"
              ]
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": {
              "Fn::Join": [
                "-",
                [
                  "retainhubcdn",
                  "staging-ashta",
                  "lambda"
                ]
              ]
            },
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogStream",
                    "logs:CreateLogGroup"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/retainhubcdn-staging-ashta*:*"
                    }
                  ]
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:PutLogEvents"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/retainhubcdn-staging-ashta*:*:*"
                    }
                  ]
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogGroup",
                    "logs:CreateLogStream",
                    "logs:PutLogEvents",
                    "logs:DescribeLogStreams"
                  ],
                  "Resource": "arn:aws:logs:*:*:*"
                }
              ]
            }
          }
        ],
        "Path": "/",
        "RoleName": {
          "Fn::Join": [
            "-",
            [
              "retainhubcdn",
              "staging-ashta",
              {
                "Ref": "AWS::Region"
              },
              "lambdaRole"
            ]
          ]
        }
      }
    },
    "IndexUnderscoreedgeLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/retainhubcdn/staging-ashta/1631024344717-2021-09-07T14:19:04.717Z/retainhubcdn.zip"
        },
        "Handler": "src/index_edge.handler",
        "Runtime": "nodejs12.x",
        "FunctionName": "retainhubcdn-staging-ashta-index_edge",
        "MemorySize": 128,
        "Timeout": 3,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        }
      },
      "DependsOn": [
        "IndexUnderscoreedgeLogGroup"
      ]
    },
    "CdnUnderscoreedgeLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": "ServerlessDeploymentBucket"
          },
          "S3Key": "serverless/retainhubcdn/staging-ashta/1631024344717-2021-09-07T14:19:04.717Z/retainhubcdn.zip"
        },
        "Handler": "src/cdn_edge.handler",
        "Runtime": "nodejs12.x",
        "FunctionName": "retainhubcdn-staging-ashta-cdn_edge",
        "MemorySize": 128,
        "Timeout": 3,
        "Role": {
          "Fn::GetAtt": [
            "IamRoleLambdaExecution",
            "Arn"
          ]
        }
      },
      "DependsOn": [
        "CdnUnderscoreedgeLogGroup"
      ]
    },
    "CdnUnderscoreedgeLambdaVersionvG67Bl74sOnb5zWTzg0QI0E1K5mWglgGC1ynxoPzUo": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "CdnUnderscoreedgeLambdaFunction"
        },
        "CodeSha256": "d1rM3i+ZFnvGeL5BbjG1uu8dgS8A/CWRc4n3hyUXHhM="
      }
    },
    "IndexUnderscoreedgeLambdaVersionE2ta9IOdRk8bXl6DpkLVFbWOEpvPA8hSP8yv677U": {
      "Type": "AWS::Lambda::Version",
      "DeletionPolicy": "Retain",
      "Properties": {
        "FunctionName": {
          "Ref": "IndexUnderscoreedgeLambdaFunction"
        },
        "CodeSha256": "d1rM3i+ZFnvGeL5BbjG1uu8dgS8A/CWRc4n3hyUXHhM="
      }
    },
    "WebsiteBucket": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": "retainhubcdn-staging-ashta",
        "AccessControl": "PublicRead",
        "WebsiteConfiguration": {
          "IndexDocument": "index.html",
          "ErrorDocument": "error.html"
        }
      }
    },
    "WebsiteDistribution": {
      "Type": "AWS::CloudFront::Distribution",
      "Properties": {
        "DistributionConfig": {
          "DefaultCacheBehavior": {
            "TargetOriginId": "WebsiteBucketOrigin",
            "ViewerProtocolPolicy": "redirect-to-https",
            "DefaultTTL": 600,
            "MaxTTL": 600,
            "Compress": true,
            "ForwardedValues": {
              "QueryString": false,
              "Cookies": {
                "Forward": "none"
              }
            }
          },
          "DefaultRootObject": "index.html",
          "Enabled": true,
          "PriceClass": "PriceClass_All",
          "HttpVersion": "http2",
          "Aliases": [
            "retaincdn-stg.ashta.io"
          ],
          "ViewerCertificate": {
            "AcmCertificateArn": "arn:aws:acm:us-east-1:571590628086:certificate/da1199bb-9fde-4b42-84f2-03cd07429e90",
            "SslSupportMethod": "sni-only"
          },
          "Origins": [
            {
              "Id": "WebsiteBucketOrigin",
              "DomainName": {
                "Fn::GetAtt": [
                  "WebsiteBucket",
                  "DomainName"
                ]
              },
              "S3OriginConfig": {}
            }
          ],
          "CacheBehaviors": [
            {
              "Compress": true,
              "TargetOriginId": "WebsiteBucketOrigin",
              "ViewerProtocolPolicy": "redirect-to-https",
              "PathPattern": "avatar/*",
              "ForwardedValues": {
                "QueryString": false,
                "Cookies": {
                  "Forward": "none"
                }
              }
            },
            {
              "Compress": true,
              "TargetOriginId": "WebsiteBucketOrigin",
              "ViewerProtocolPolicy": "redirect-to-https",
              "PathPattern": "splash_images/*",
              "ForwardedValues": {
                "QueryString": false,
                "Cookies": {
                  "Forward": "none"
                }
              }
            },
            {
              "Compress": true,
              "TargetOriginId": "WebsiteBucketOrigin",
              "ViewerProtocolPolicy": "redirect-to-https",
              "PathPattern": "document/*",
              "ForwardedValues": {
                "QueryString": false,
                "Cookies": {
                  "Forward": "none"
                }
              },
              "LambdaFunctionAssociations": [
                {
                  "EventType": "origin-request",
                  "IncludeBody": false,
                  "LambdaFunctionARN": {
                    "Ref": "IndexUnderscoreedgeLambdaVersionE2ta9IOdRk8bXl6DpkLVFbWOEpvPA8hSP8yv677U"
                  }
                }
              ]
            },
            {
              "Compress": true,
              "TargetOriginId": "WebsiteBucketOrigin",
              "ViewerProtocolPolicy": "redirect-to-https",
              "PathPattern": "purchase_order/*",
              "ForwardedValues": {
                "QueryString": false,
                "Cookies": {
                  "Forward": "none"
                }
              },
              "LambdaFunctionAssociations": [
                {
                  "EventType": "origin-request",
                  "IncludeBody": false,
                  "LambdaFunctionARN": {
                    "Ref": "CdnUnderscoreedgeLambdaVersionvG67Bl74sOnb5zWTzg0QI0E1K5mWglgGC1ynxoPzUo"
                  }
                }
              ]
            }
          ]
        }
      }
    }
  },
  "Outputs": {
    "ServerlessDeploymentBucketName": {
      "Value": {
        "Ref": "ServerlessDeploymentBucket"
      },
      "Export": {
        "Name": "sls-retainhubcdn-staging-ashta-ServerlessDeploymentBucketName"
      }
    },
    "CdnUnderscoreedgeLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "CdnUnderscoreedgeLambdaVersionvG67Bl74sOnb5zWTzg0QI0E1K5mWglgGC1ynxoPzUo"
      },
      "Export": {
        "Name": "sls-retainhubcdn-staging-ashta-CdnUnderscoreedgeLambdaFunctionQualifiedArn"
      }
    },
    "IndexUnderscoreedgeLambdaFunctionQualifiedArn": {
      "Description": "Current Lambda function version",
      "Value": {
        "Ref": "IndexUnderscoreedgeLambdaVersionE2ta9IOdRk8bXl6DpkLVFbWOEpvPA8hSP8yv677U"
      },
      "Export": {
        "Name": "sls-retainhubcdn-staging-ashta-IndexUnderscoreedgeLambdaFunctionQualifiedArn"
      }
    }
  }
}
fusionbeam commented 3 years ago

serverless.yml

....
functions:  

    cdn_edge:    
      handler: src/cdn_edge.handler      
      memorySize: 128
      timeout: 3
      lambdaAtEdge:
        distribution: 'WebsiteDistribution'
        eventType: 'origin-request'
        pathPattern: 'purchase_order/*'
....

esources:
  Resources:
    WebsiteBucket:
      Type: 'AWS::S3::Bucket'
      Properties:
        BucketName: '${self:custom.objectPrefix.${self:provider.stage}}'
        AccessControl: 'PublicRead'
        WebsiteConfiguration:
            IndexDocument: 'index.html'
            ErrorDocument: 'error.html'

    ## Specifying the policies to make sure all files inside the Bucket are avaialble to CloudFront
    WebsiteDistribution:
      Type: 'AWS::CloudFront::Distribution'
      Properties:
         DistributionConfig:
            DefaultCacheBehavior:
               TargetOriginId: 'WebsiteBucketOrigin'
               ViewerProtocolPolicy: 'redirect-to-https'
               DefaultTTL: 600 # ten minutes
               MaxTTL: 600 # ten minutes
               Compress: true
               ForwardedValues:
                  QueryString: false
                  Cookies:
                     Forward: 'none'
            DefaultRootObject: 'index.html'
            Enabled: true
            PriceClass: 'PriceClass_All'
            HttpVersion: 'http2'
            Aliases:
              - ${self:custom.frontAlias.${self:provider.stage}}
            ViewerCertificate:
               AcmCertificateArn: ${self:custom.acmCertificateArn.${self:provider.stage}}
               SslSupportMethod: sni-only
            Origins:
              -
                Id: 'WebsiteBucketOrigin'
                DomainName: { 'Fn::GetAtt': [ 'WebsiteBucket', 'DomainName' ] }
                S3OriginConfig: {}
            CacheBehaviors:
              -
                Compress: true
                TargetOriginId: 'WebsiteBucketOrigin'
                ViewerProtocolPolicy: 'redirect-to-https'
                PathPattern: 'avatar/*'
                ForwardedValues:
                  QueryString: false
                  Cookies:
                     Forward: 'none'
              -
                Compress: true
                TargetOriginId: 'WebsiteBucketOrigin'
                ViewerProtocolPolicy: 'redirect-to-https'
                PathPattern: 'splash_images/*'
                ForwardedValues:
                  QueryString: false
                  Cookies:
                     Forward: 'none' 
              -
                Compress: true
                TargetOriginId: 'WebsiteBucketOrigin'
                ViewerProtocolPolicy: 'redirect-to-https'
                PathPattern: 'document/*'
                ForwardedValues:
                  QueryString: false
                  Cookies:
                      Forward: 'none'
              -
                Compress: true
                TargetOriginId: 'WebsiteBucketOrigin'
                ViewerProtocolPolicy: 'redirect-to-https'
                PathPattern: 'purchase_order/*'
                ForwardedValues:
                  QueryString: false
                  Cookies:
                      Forward: 'none'
jthomerson commented 3 years ago

things look fairly normal to me. What is the actual error you are getting? You mention something about lambda:InvokeFunction permissions, but I don't see what the error is. We don't add any lambda:InvokeFunction permissions in the plugin. We simply modify the function's role's AssumeRolePolicyDocument to add edgelambda.amazonaws.com.

Also, what version of the Serverless Framework? Have you upgraded the framework since this was last working?

fusionbeam commented 3 years ago

Thanks for your assistance.

The error in request output and looks like this:

503 ERROR

The request could not be satisfied.

The Lambda function associated with the CloudFront distribution is invalid or doesn't have the required permissions. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner. 
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation. 
Generated by cloudfront (CloudFront)
Request ID: uur-Tbe8DYgTnz_IF5NkZg-ZVLB-LF9D_SJx0OLutjmvcynNE6i38A==
fusionbeam commented 3 years ago

@jthomerson genuine thanks for your assistance. After your assessment I looked deeper into the handler and realised it was a handler error, not a permission error.