Search code examples
amazon-web-servicesamazon-cloudfront

Default root Object Cloudfront + Api Gateway


I have SAM Lambda written in Python, i'm using Cloudfront and API Gateway to expose an endpoint to trigger the lambda, and I have a security issue to fix about: CloudFront distributions should have a default root object configured So I want to add a default root object to the cloudfront, and I need some help to configure it.

I've tried to add the default root object to s3 bucket, and also tried to add it to the root in the project but it's not working for me.

This is my template:

AWSTemplateFormatVersion: "2010-09-09"
Transform:
  - AWS::Serverless-2016-10-31

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names e.g. dev,test for feature branch use dev1,dev2...
    Type: String
  Stage:
    Type: String
    Description: The name for a project pipeline stage, such as Staging or Prod, for which resources are provisioned and deployed.
    Default: Stage
    AllowedValues:
      - Prod
      - Stage
  ApiKey:
    Description: A unique key that you can distribute to clients such as CloudFront who are executing API Gateway
    Type: String
    Default: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    NoEcho: true
  ServiceName:
    Description: Service name
    Type: String
    Default: work-item-creation

  LambdaFunc:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub "${EnvironmentName}-cw-${ServiceName}-lambda"
      Description: Work Item Creation Lambda
      Handler: app.lambda_handler
      Runtime: python3.9
      MemorySize: 128
      Timeout: 30
      CodeUri: lambda_func/
      Role: !GetAtt
        - LambdaExecutionRole
        - Arn
      Environment:
        Variables:
          PAT: "{{resolve:secretsmanager:secrets:SecretString:AZURE_PAT}}"
          
      Events:
        CreateWorkItem:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /create-work-item
            Method: post
            Stage: !Ref Stage
            Auth:
              ApiKeyRequired: true

  
  LambdaExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "${EnvironmentName}-cw-${ServiceName}-lambda-execution-role"
      Description: Creating service role in IAM for AWS Lambda
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: sts:AssumeRole
      Path: /service-role/
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      Policies:
        - PolicyName: "s3-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:                  
                  - s3:ListBucket
                Resource:
                  - !Sub 'arn:aws:s3:::artifacts-us-east-1'
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName
        - Key: Service
          Value: !Ref ServiceName

  ApiGatewayKey:
    Type: "AWS::ApiGateway::ApiKey"
    DependsOn:
      - LambdaFunc
    Properties:
      Name: !Sub "${EnvironmentName}-cw-${ServiceName}-api-gateway-key"
      Description: Work Item Creation API Key
      Enabled: true
      StageKeys:
        - RestApiId: !Ref ServerlessRestApi
          StageName: !Ref Stage
      Value: !Ref ApiKey
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName
        - Key: Service
          Value: !Ref ServiceName

  ApiKeyUsagePlan:
    Type: "AWS::ApiGateway::UsagePlan"
    DependsOn:
      - LambdaFunc
      - ApiGatewayKey
    Properties:
      UsagePlanName: !Sub "${EnvironmentName}-cw-${ServiceName}-api-key-usage-plan"
      Description: Work Item Creation API Key usage plan
      ApiStages:
        - ApiId: !Ref ServerlessRestApi
          Stage: !Ref Stage
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName
        - Key: Service
          Value: !Ref ServiceName

  LinkUsagePlanApiKey:
    Type: "AWS::ApiGateway::UsagePlanKey"
    Properties:
      KeyId: !Ref ApiGatewayKey
      KeyType: API_KEY
      UsagePlanId: !Ref ApiKeyUsagePlan

  CloudFront:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Enabled: true
        IPV6Enabled: true
        HttpVersion: http2
        Comment: !Sub "Work Item Creation CloudFront, Environment: ${EnvironmentName}"
        Aliases:
          - !Join [
              ".",
              [
                !Ref ServiceName,
                !Sub "{{resolve:ssm:/${EnvironmentName}/aws/route53/domain-name:1}}",
              ],
            ]
        ViewerCertificate:
          AcmCertificateArn: !Sub "{{resolve:ssm:/${EnvironmentName}/aws/acm/ssl-certificate-id:1}}"
          SslSupportMethod: sni-only
          MinimumProtocolVersion: TLSv1.2_2021
        Origins:
          - Id: APIGOrigin
            DomainName: !Sub ${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com
            OriginPath: !Sub /${Stage}
            CustomOriginConfig:
              HTTPSPort: 443
              OriginProtocolPolicy: https-only
            OriginCustomHeaders:
              - HeaderName: x-api-key
                HeaderValue: !Ref ApiKey
          - Id: S3Origin
            DomainName: artifacts-us-east-1.s3.amazonaws.com
            CustomOriginConfig:
              HTTPSPort: 443
              OriginProtocolPolicy: https-only
        DefaultRootObject: default.html
        DefaultCacheBehavior:
          AllowedMethods:
            ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
          CachedMethods: ["GET", "HEAD", "OPTIONS"]
          ForwardedValues:
            Headers:
              - Access-Control-Request-Headers
              - Access-Control-Request-Method
              - Origin
              - Authorization
            # - Host APIG needs to use SNI
            QueryString: true
          TargetOriginId: APIGOrigin
          ViewerProtocolPolicy: https-only
          Compress: true
          DefaultTTL: 0
        CustomErrorResponses:
          - ErrorCachingMinTTL: 0
            ErrorCode: 400
          - ErrorCachingMinTTL: 1
            ErrorCode: 403
          - ErrorCachingMinTTL: 5
            ErrorCode: 500
      Tags:
        - Key: Environment
          Value: !Ref EnvironmentName
        - Key: Service
          Value: !Ref ServiceName

  RecordSetGroup:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      HostedZoneId: !Sub "{{resolve:ssm:/${EnvironmentName}/aws/route53/internal-public-hosted-zone-id:1}}"
      RecordSets:
        - Name:
            !Join [
              ".",
              [
                !Ref ServiceName,
                !Sub "{{resolve:ssm:/${EnvironmentName}/aws/route53/domain-name:1}}",
              ],
            ]
          Type: A
          AliasTarget:
            HostedZoneId: XXXXXXXXX # hosted zone ID of CloudFront
            DNSName: !GetAtt CloudFront.DomainName

Outputs:
  WorkItemCreationEndPoint:
    Description: Work Item Creation Rest API endpoint
    Value: !Ref RecordSetGroup

Where and how do I need to configure the default root object? I have tried to set up the html in the bucket called artifact-us-east-1 but it doesn't seem to be working. Also, I have tried to add it to the project in the root location.


Solution

  • I have fugured it out and it's easier than I thoght. You need to return the html from your lambda using api gateway resposne definition. For example:

    html_response = {
    
        "statusCode": "404",
        "body": {},
        "headers": {
            'Content-Type': 'text/html',
        }
    }
    

    Then add to the template.yaml file another path /index.html and enable default root object. Actualy, when setting default root object, and routing to the root / aws routing in by default to /index.html.