【AWS学習記録5回目】Amazon CloudFrontおよびAWS WAFを用いたアプリを構築してみた

## 概要

https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-CF_WAF-2022-reg-event.html?trk=aws_introduction_page

をもとにAmazon CloudFrontおよびAWS WAFを用いた翻訳アプリを構築してみました
※ハンズオンの内容と全て一致していません

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747730/72b371b6-1221-f56d-c08b-0e92e5e07328.png)

## CloudFormation
米国東部 (バージニア北部)リージョンに作成
```waf.yml
AWSTemplateFormatVersion: 2010-09-09
Resources: 
  #======================
  # WebACL
  #======================  
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      DefaultAction:
        Allow: {}
      Name: handson-waf
      Rules:
        - Action:
            Block: 
              CustomResponse: 
                ResponseCode: 400
          Name: handson-rule
          Priority: 1
          Statement: 
            GeoMatchStatement: 
              CountryCodes: 
                - JP
          VisibilityConfig: 
            CloudWatchMetricsEnabled: true
            MetricName: BlockRequestsFromJapan
            SampledRequestsEnabled: true
      Scope: CLOUDFRONT
      VisibilityConfig: 
        CloudWatchMetricsEnabled: true
        MetricName: handson-waf
        SampledRequestsEnabled: true
```

アジアパシフィック (東京)に作成
```app.yml
AWSTemplateFormatVersion: 2010-09-09
Resources: 
  #======================
  # Distribution
  #======================
  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        CacheBehaviors:
          - PathPattern: static/*
            TargetOriginId: S3
            ViewerProtocolPolicy: redirect-to-https
            CachePolicyId: !Ref CachePolicy
          - PathPattern: api
            TargetOriginId: RestApi
            ViewerProtocolPolicy: redirect-to-https
            CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
            OriginRequestPolicyId: !Ref OriginRequestPolicy
        DefaultCacheBehavior:
          TargetOriginId: S3
          ViewerProtocolPolicy: redirect-to-https
          CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
        DefaultRootObject: index.html
        Enabled: true
        Origins: 
          - DomainName: !Sub "${RestApi.RestApiId}.execute-api.${AWS::Region}.amazonaws.com"
            Id: RestApi
            CustomOriginConfig: 
              OriginProtocolPolicy: match-viewer
          - DomainName: !GetAtt Bucket.DomainName
            Id: S3
            OriginAccessControlId: !GetAtt OriginAccessControl.Id
            S3OriginConfig:
              OriginAccessIdentity: ''
        WebACLId: !Sub "arn:aws:wafv2:us-east-1:${AWS::AccountId}:global/webacl/handson-waf/b5739b66-17f5-4e82-b61c-101517f3f028"
  
  #======================
  # CachePolicy
  #======================
  CachePolicy:
    Type: AWS::CloudFront::CachePolicy
    Properties:
      CachePolicyConfig: 
        DefaultTTL: 86400
        MaxTTL: 31536000
        MinTTL: 1
        Name: CachePolicy
        ParametersInCacheKeyAndForwardedToOrigin: 
          CookiesConfig: 
            CookieBehavior: none
          EnableAcceptEncodingBrotli: true
          EnableAcceptEncodingGzip: true
          HeadersConfig: 
            HeaderBehavior: none
          QueryStringsConfig: 
            QueryStringBehavior: none
  
  #======================
  # OriginRequestPolicy
  #======================
  OriginRequestPolicy:
    Type: AWS::CloudFront::OriginRequestPolicy
    Properties:
      OriginRequestPolicyConfig: 
        CookiesConfig: 
          CookieBehavior: none
        HeadersConfig: 
          HeaderBehavior: none
        Name: OriginRequestPolicy
        QueryStringsConfig: 
          QueryStringBehavior: whitelist
          QueryStrings: 
            - input_text
  
  #======================
  # OriginAccessControl
  #======================
  OriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig: 
        Name: OACj7rotyh3xxkt1uj0wocu
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  #======================
  # Bucket
  #======================
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: bucketaeb934895097f22ba2d64e053c0c3fa099
      PublicAccessBlockConfiguration: 
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false
      WebsiteConfiguration: 
        ErrorDocument: error.html
        IndexDocument: index.html
  
  #======================
  # BucketPolicy
  #======================
  BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref Bucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: PublicReadGetObject
            Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action:
              - s3:GetObject
            Resource:
              - arn:aws:s3:::bucketaeb934895097f22ba2d64e053c0c3fa099/*
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${Distribution}"
  
  #======================
  # RestApi
  #======================
  RestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Name: RestApi
  
  #======================
  # Method
  #======================
  Method:
    Type: AWS::ApiGateway::Method
    Properties:
      AuthorizationType: NONE
      HttpMethod: GET
      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:Lambdafunction/invocations"
        RequestParameters: 
          integration.request.querystring.input_text: method.request.querystring.input_text
      RequestParameters: 
        method.request.querystring.input_text: true
      ResourceId: !GetAtt RestApi.RootResourceId
      RestApiId: !Ref RestApi

  #======================
  # Stage
  #======================
  Stage:
    Type: AWS::ApiGateway::Stage
    Properties:
      StageName: api
      RestApiId: !Ref RestApi
      DeploymentId: !Ref Deployment
  
  #======================
  # Deployment
  #======================
  Deployment:
    DependsOn: Method
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref RestApi

  #======================
  # Permission
  #======================
  Permission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref Function
      Principal: apigateway.amazonaws.com
  
  #======================
  # Function
  #======================
  Function:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: Lambdafunction
      Handler: index.lambda_handler
      Runtime: python3.11
      Role: !GetAtt Role.Arn
      Code: 
        ZipFile: !Sub |
          import boto3

          def lambda_handler(event, context):
              translate = boto3.client(service_name='translate', use_ssl=True)
              result = translate.translate_text(Text=event['queryStringParameters']['input_text'], SourceLanguageCode="ja", TargetLanguageCode="en").get('TranslatedText')
              return {
                  'statusCode': 200,
                  'body': result
              }

  #======================
  # Role
  #======================
  Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/TranslateReadOnly
```

## S3バケットの内容
```index.html
<!DOCTYPE html>
<html>
  <head>
    <link rel="icon" type="image/x-icon" href="static/image/cloudfront.png">
    <link href="static/css/styles.css" rel="stylesheet" type="text/css">
    <script src="static/js/script.js"></script>
  </head>
  <body>
    <form id="form1" action="#">
        <ul>
            <li>
              <img src="static/image/cloudfront.png" width="100" height="100">
            </li>
            <li>
              <p>Input Message</p>
              <textarea id="input_message" rows="4" cols="40"></textarea>
            </li>
            <li>
                <input type="button" onclick="func1()" value="Send" />
                <input type="button" onclick="func2()" value="All Clear"  />
            </li>
            <li>
                <br />
                <p>Output Message</p>
                <textarea id="output_message" rows="4" cols="40"></textarea>
            </li>
          </ul>
    </form>
  </body>
</html>
```

```static/css/style.css
#form1 { text-align : center ;}
ul li{
    list-style: none;
    padding: 10px;
}
```

```static/js/script.js
function func1() {
  var apiurl = 'https://' + document.domain + '/api?input_text=' + document.getElementById("input_message").value;
  var request = new XMLHttpRequest();

  request.open('GET', apiurl, true);
  request.responseType = 'text';
  request.onload = function () {
      var data = this.response;
      document.getElementById("output_message").innerHTML = data;
  };
  request.send();
}

function func2() {
    document.getElementById("input_message").value = '';
    document.getElementById("output_message").innerHTML = '';
}
```

![static/image/cloudfront.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3747730/e1acce7c-a30e-2669-9d46-f74a560fd48c.png)

## 参考文献

https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-CF_WAF-2022-reg-event.html?trk=aws_introduction_page

https://www.geekfeed.co.jp/geekblog/aws-cloudformation

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-s3-bucket.html#cfn-s3-bucket-accesscontrol

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-method.html

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-resource.html

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html

https://qiita.com/hayao_k/items/8558042d52be4f29c6e6