CloudFront DistributionからAPI Gatewayを呼び出す構築ハンズオン

## はじめに
##### IAM認証や、ACM,Route53などの検証の際に、自分でよく利用する構築をCloudFormation化していなかったと思い着手してみました。
##### まずは背骨となる、　CloudFront から APIGateway - Lambdaを呼び出す構築部分のCFn化を行なっていきたいと思います。

------------

## 構成図
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-10-15.03.31-640x295.png)

------------

## ハンズオン
### 構築の流れ
#### 1.Lambda作成
#### 2.APIGateway作成
#### 3.CloudFront作成
##### 上記の順番で構築を行なっていきます。

##### 最終的には、CloudFrontのディストリビューションドメインから、Lambdaを呼び出せるまでを行なっていきます。
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-10-14.33.30-640x276.png)

-----------

### 1.Lambda作成
##### 特別な設定はせず`statusCode`が`200`を返す、Lambdaを構築します。

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  Lambda Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Lambda Configuration"
        Parameters:
        - FunctionName
        - Description
        - Handler
        - MemorySize
        - Runtime
        - Timeout

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  FunctionName:
    Type: String
    Default: "cfn-lmd-inamura"
  Description:
    Type: String
    Default: "cfn-lmd-inamura"
  Handler:
    Type: String
    Default: "index.lambda_handler"
  MemorySize:
    Type: String
    Default: "128"
  Runtime:
    Type: String
    Default: "python3.9"
  Timeout:
    Type: String
    Default: "10"

# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        ZipFile: |
          import json

          def lambda_handler(event, context):
              # TODO implement
              print(event)
              return {
                  'statusCode': 200,
                  'body': json.dumps('Hello CloudFront!')
              }

      Description: !Ref Description
      FunctionName: !Ref FunctionName
      Handler: !Ref Handler 
      MemorySize: !Ref MemorySize
      Runtime: !Ref Runtime
      Timeout: !Ref Timeout
      Role: !GetAtt LambdaRole.Arn

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${FunctionName}-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com
      Policies:
        - PolicyName: !Sub "${FunctionName}-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "logs:CreateLogGroup"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  LambdaArn:
    Value: !GetAtt Lambda.Arn
    Export:
      Name: !Sub "${FunctionName}-arn"
  LambdaName:
    Value: !Ref FunctionName
    Export:
      Name: !Sub "${FunctionName}-name"
```

### 2.APIGateway作成
#### 2.1 APIGateway作成
##### 次はAPIGatewayを構築します。
##### 順次構築できるようにするため、それぞれのリソースに`DependsOn`を設定しています。
##### 1.で構築したLambdaに対して`"AWS::Lambda::Permission"`でリソースベースポリシーの追加を行なっています。

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description:
  APIGateway Create
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "APIGateway Configuration"
        Parameters:
        - Name
        - FunctionName
        - StageName
# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  Name:
    Type: String
    Default: "cfn-agw-inamura"
  FunctionName:
    Type: String
    Default: "cfn-lmd-inamura"
  StageName:
    Type: String
    Default: "dev"
# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  APIGateway
# ------------------------------------------------------------#
  Api:
    Type: "AWS::ApiGateway::RestApi"
    Properties:
      Name: !Ref Name
      EndpointConfiguration:
        Types:
          - REGIONAL

  Resource:
    Type: "AWS::ApiGateway::Resource"
    Properties:
      RestApiId: !Ref Api
      ParentId: !GetAtt Api.RootResourceId
      PathPart: !Ref FunctionName
    DependsOn: Api

  LambdaPermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      FunctionName: !Ref FunctionName
      Action: "lambda:InvokeFunction"
      Principal: "apigateway.amazonaws.com"
    DependsOn: Resource 

  ResourceMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      RestApiId: !Ref Api
      ResourceId: !Ref Resource
      AuthorizationType: None
      HttpMethod: ANY
      Integration:
        Type: "AWS_PROXY"
        IntegrationHttpMethod: POST
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${FunctionName}/invocations"
    DependsOn: LambdaPermission
 
  Deployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref Api
    DependsOn: ResourceMethod
 
  Stage:
    Type: AWS::ApiGateway::Stage
    Properties:
      StageName: !Ref StageName
      Description: dev stage
      RestApiId: !Ref Api
      DeploymentId: !Ref Deployment
    DependsOn: Deployment

# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  Endpoint:
    Value: !Sub 'https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${StageName}/${FunctionName}'
    Export:
      Name: Endpoint
  OriginDomain:
    Value: !Sub '${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}'   
    Export:
      Name: OriginDomain
  StageName:
    Value: !Sub '${StageName}'
    Export:
      Name: StageName
```
#### 2.2.構築されたAPIGatewayのエンドポイントを押下する
##### CloudFormation ＞　スタック　＞ 2.1.で構築したスタック　＞　タブ『出力』 に移動する
##### 下記画面のように Endpoint にエンドポイントが出力されているので、URIを押下する
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-10-14.08.35-640x212.png)

##### URI押下後、下記のような画面が表示されることを確認する
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-10-14.14.40-640x169.png)

##### ※自分だけの現象か、上記構築後Lambdaを確認しても`トリガー`にはAPIGatewayが表示されませんでした
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-10-14.16.31-640x384.png)

### 3.CloudFront作成
##### 2.で構築したAPIGatewayをオリジンにしたCloudFrontを構築していきます。
##### `CachePolicyId`はマネージドポリシーを利用しているので、AWS公式：[Using the managed cache policies](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html)からId番号を確認しました。

```yaml

AWSTemplateFormatVersion: '2010-09-09'
Description:
  CloudFront Create
# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  CloudFront
# ------------------------------------------------------------#
  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: "Create CloudFront with CloudFormation"
        PriceClass: PrinceClass_All
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
          Compress: true
          TargetOriginId: !ImportValue cfn-lmd-inamura-name
          ViewerProtocolPolicy: https-only
        Enabled: true
        HttpVersion: http2and3
        Origins:
          - Id: !ImportValue cfn-lmd-inamura-name
            DomainName: !ImportValue OriginDomain
            CustomOriginConfig:
              HTTPSPort: '443'
              OriginProtocolPolicy: https-only
            OriginPath: !Sub
              - /${StageName}/${FunctionName}
              - StageName: {'Fn::ImportValue': StageName}
                FunctionName: {'Fn::ImportValue': cfn-lmd-inamura-name}
        PriceClass: PriceClass_All
# ------------------------------------------------------------#
# Output Parameters
#------------------------------------------------------------#          
Outputs:
  DomainName:
    Value: !GetAtt Distribution.DomainName
    Export:
      Name: cln-domainname-inamura
```


### 挙動の確認
#### ①構築されたCloudFrontからドメイン名を確認する
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-10-14.36.35-640x130.png)

#### ②ブラウザにドメインを入力して、画面が表示されるか確認をする
![](https://cloud5.jp/wp-content/uploads/2022/12/スクリーンショット-2022-12-10-14.33.30-1-640x276.png)

------------

## さいごに
##### CloudFrontからS3（OAI）の静的構築におけるハンズオンは多く見つかりましたが、あまりAPIGateway-Lambdaが見当たりませんでしたが、公式と睨めっこをしながら無事に構築が出来ました。
##### 簡単に検証する環境もつくることが出来るようになったので、次はIAM認証でACMなどの構成や、Cognitoでの認証などを検証していきたいです。
##### 今年も残り少ないですが、時間を見つけては検証して自分の作れるものを増やしていきたいと思います！