cfn_nag icon indicating copy to clipboard operation
cfn_nag copied to clipboard

Tool Is Not Transforming SAM (`AWS::Serverless-2016-10-31`) Correctly

Open chrisoverzero opened this issue 4 years ago • 3 comments

Given the following reduced test template:

---
AWSTemplateFormatVersion: 2010-09-09

Transform: AWS::Serverless-2016-10-31

Description: A reduced test template.

Globals:
  Api:
    OpenApiVersion: '3.0.1'
    AccessLogSetting:
      Format: $context.requestId
    Auth:
      UsagePlan:
        CreateUsagePlan: PER_API

Resources:
  Http:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: nodejs14.x
      Handler: index.default
      InlineCode: |-
        exports.default = () => { }
      Events:
        ProxyApi:
          Type: Api
          Properties:
            Method: ANY
            Path: /{proxy+}

cfn_nag reports the following:

------------------------------------------------------------
./template.yml
------------------------------------------------------------------------------------------------------------------------
| WARN W68
|
| Resources: ["ServerlessRestApiDeployment"]
| Line Numbers: [-1]
|
| AWS::ApiGateway::Deployment resources should be associated with an AWS::ApiGateway::UsagePlan. 
------------------------------------------------------------
| WARN W69
|
| Resources: ["ServerlessRestApiProdStage"]
| Line Numbers: [-1]
|
| AWS::ApiGateway::Stage should have the AccessLogSetting property defined.
------------------------------------------------------------
| WARN W64
|
| Resources: ["ServerlessRestApiProdStage"]
| Line Numbers: [-1]
|
| AWS::ApiGateway::Stage resources should be associated with an AWS::ApiGateway::UsagePlan. 

Failures count: 0
Warnings count: 3

These associations and property do exist, however. When transformed by SAM, the template produced is this:

The transformed template is kinda verbose.
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "A reduced test template.",
  "Resources": {
    "Http": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "ZipFile": "exports.default = () => { }"
        },
        "Handler": "index.default",
        "Role": {
          "Fn::GetAtt": [
            "HttpRole",
            "Arn"
          ]
        },
        "Runtime": "nodejs14.x",
        "Tags": [
          {
            "Key": "lambda:createdBy",
            "Value": "SAM"
          }
        ]
      }
    },
    "HttpRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": [
                "sts:AssumeRole"
              ],
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "lambda.amazonaws.com"
                ]
              }
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
        ],
        "Tags": [
          {
            "Key": "lambda:createdBy",
            "Value": "SAM"
          }
        ]
      }
    },
    "HttpProxyApiPermissionProd": {
      "Type": "AWS::Lambda::Permission",
      "Properties": {
        "Action": "lambda:InvokeFunction",
        "FunctionName": {
          "Ref": "Http"
        },
        "Principal": "apigateway.amazonaws.com",
        "SourceArn": {
          "Fn::Sub": [
            "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/*",
            {
              "__ApiId__": {
                "Ref": "ServerlessRestApi"
              },
              "__Stage__": "*"
            }
          ]
        }
      }
    },
    "ServerlessRestApi": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Body": {
          "info": {
            "version": "1.0",
            "title": {
              "Ref": "AWS::StackName"
            }
          },
          "paths": {
            "/{proxy+}": {
              "x-amazon-apigateway-any-method": {
                "x-amazon-apigateway-integration": {
                  "type": "aws_proxy",
                  "httpMethod": "POST",
                  "uri": {
                    "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Http.Arn}/invocations"
                  }
                },
                "responses": {}
              }
            }
          },
          "openapi": "3.0.1"
        }
      }
    },
    "ServerlessRestApiDeployment7a650ad703": {
      "Type": "AWS::ApiGateway::Deployment",
      "Properties": {
        "Description": "RestApi deployment id: 7a650ad703b8db9ec385abf58d5a42934aa71554",
        "RestApiId": {
          "Ref": "ServerlessRestApi"
        }
      }
    },
    "ServerlessRestApiProdStage": {
      "Type": "AWS::ApiGateway::Stage",
      "Properties": {
        "AccessLogSetting": {
          "Format": "$context.requestId"
        },
        "DeploymentId": {
          "Ref": "ServerlessRestApiDeployment7a650ad703"
        },
        "RestApiId": {
          "Ref": "ServerlessRestApi"
        },
        "StageName": "Prod"
      }
    },
    "ServerlessRestApiUsagePlan": {
      "Type": "AWS::ApiGateway::UsagePlan",
      "DependsOn": [
        "ServerlessRestApi"
      ],
      "Properties": {
        "ApiStages": [
          {
            "ApiId": {
              "Ref": "ServerlessRestApi"
            },
            "Stage": {
              "Ref": "ServerlessRestApiProdStage"
            }
          }
        ]
      }
    },
    "ServerlessRestApiApiKey": {
      "Type": "AWS::ApiGateway::ApiKey",
      "DependsOn": [
        "ServerlessRestApiUsagePlan"
      ],
      "Properties": {
        "Enabled": true,
        "StageKeys": [
          {
            "RestApiId": {
              "Ref": "ServerlessRestApi"
            },
            "StageName": {
              "Ref": "ServerlessRestApiProdStage"
            }
          }
        ]
      }
    },
    "ServerlessRestApiUsagePlanKey": {
      "Type": "AWS::ApiGateway::UsagePlanKey",
      "DependsOn": [
        "ServerlessRestApiApiKey"
      ],
      "Properties": {
        "KeyId": {
          "Ref": "ServerlessRestApiApiKey"
        },
        "KeyType": "API_KEY",
        "UsagePlanId": {
          "Ref": "ServerlessRestApiUsagePlan"
        }
      }
    }
  }
}

When scanned, this produces no warnings or failures:

------------------------------------------------------------
./template.processed.json
------------------------------------------------------------
Failures count: 0
Warnings count: 0

This was run via the Docker image stelligent/cfn_nag:latest.

chrisoverzero avatar Feb 08 '21 16:02 chrisoverzero

Thanks for the detailed report @chrisoverzero . The way that cfn_nag models the transformation for AWS::Serverless is somewhat static. We'll review and update as appropriate.

arothian avatar Feb 10 '21 22:02 arothian

Thanks for the detailed report @chrisoverzero . The way that cfn_nag models the transformation for AWS::Serverless is somewhat static. We'll review and update as appropriate.

@arothian For the time being is there anyway to use metadata to ignore this?

mongoDynamo avatar Feb 15 '21 18:02 mongoDynamo

@mongoDynamo Not currently, metadata on the serverless resource will currently pass through some of the generated resources(https://github.com/stelligent/cfn-model/pull/84), but I don't think it will work for the ones warning in this issue.

arothian avatar Feb 15 '21 18:02 arothian