🍉

Claude 3.5 sonnetがCloudFormationを書くのに優秀だった

2024/07/28に公開
CloudFrontでCloudFront Functionsを使ってBASIC認証を実装するCloudFormationテンプレートを書いてください

というちょっとイマイチなプロンプトでGPT-4oとClaude 3.5 sonnetに聞いてみたら結構違いました。

関連記事
https://zenn.dev/the2ndloop/articles/c69eb789dae30c

GPT-4o

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  CloudFrontDistribution:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
      DistributionConfig:
        Origins:
          - Id: myOrigin
            DomainName: your-origin-domain-name
            CustomOriginConfig:
              HTTPPort: 80
              HTTPSPort: 443
              OriginProtocolPolicy: https-only
        DefaultCacheBehavior:
          TargetOriginId: myOrigin
          ViewerProtocolPolicy: redirect-to-https
          FunctionAssociations:
            - EventType: viewer-request
              FunctionARN: !GetAtt BasicAuthFunction.FunctionARN
        Enabled: true

  BasicAuthFunction:
    Type: 'AWS::CloudFront::Function'
    Properties:
      Name: 'BasicAuthFunction'
      FunctionCode: |
        function handler(event) {
          var request = event.request;
          var headers = request.headers;

          // Username and password (base64 encoded)
          var authString = 'Basic ' + 'YWRtaW46cGFzc3dvcmQ='; // replace 'YWRtaW46cGFzc3dvcmQ=' with your base64 encoded "username:password"

          if (typeof headers.authorization === 'undefined' || headers.authorization[0].value !== authString) {
            return {
              statusCode: 401,
              statusDescription: 'Unauthorized',
              headers: {
                'www-authenticate': { value: 'Basic' }
              }
            };
          }

          return request;
        }

  CloudFrontFunctionIAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service: 'lambda.amazonaws.com'
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: 'CloudFrontFunctionPolicy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource: 'arn:aws:logs:*:*:*'

Outputs:
  CloudFrontDistributionId:
    Value: !Ref CloudFrontDistribution
    Description: 'The ID of the CloudFront distribution'
  BasicAuthFunctionARN:
    Value: !GetAtt BasicAuthFunction.FunctionARN
    Description: 'The ARN of the Basic Auth CloudFront Function'

Claude 3.5 Sonnet

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFront distribution with Basic Auth using CloudFront Functions

Parameters:
  Username:
    Type: String
    Description: Username for Basic Auth
  Password:
    Type: String
    Description: Password for Basic Auth
    NoEcho: true

Resources:
  BasicAuthFunction:
    Type: AWS::CloudFront::Function
    Properties:
      Name: basic-auth-function
      AutoPublish: true
      FunctionConfig:
        Comment: Implement Basic Auth
        Runtime: cloudfront-js-1.0
      FunctionCode: !Sub |
        function handler(event) {
          var request = event.request;
          var headers = request.headers;
          
          var authString = '${Username}:${Password}';
          var expectedAuth = 'Basic ' + btoa(authString);
          
          if (typeof headers.authorization === 'undefined' || headers.authorization.value !== expectedAuth) {
            return {
              statusCode: 401,
              statusDescription: 'Unauthorized',
              headers: {
                'www-authenticate': { value: 'Basic' }
              }
            };
          }
          
          return request;
        }

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Enabled: true
        DefaultCacheBehavior:
          ViewerProtocolPolicy: redirect-to-https
          TargetOriginId: example-origin
          FunctionAssociations:
            - EventType: viewer-request
              FunctionARN: !GetAtt BasicAuthFunction.FunctionARN
        Origins:
          - Id: example-origin
            DomainName: example.com
            CustomOriginConfig:
              OriginProtocolPolicy: https-only

Outputs:
  CloudFrontDomainName:
    Description: Domain name of the CloudFront distribution
    Value: !GetAtt CloudFrontDistribution.DomainName

比較

まず、Claudeの方はDescriptionがありますね。
GPTも場合によってはDescriptionを入れて返してくれるので、たまたまかもしれません

GPTあんまりCloudFormationに強くなさそう

GPTの方は'AWS::CloudFront::Function'に必須のプロパティが抜けてます。

Claudeの方にはちゃんと入ってます。
ここです。

      FunctionConfig:
        Comment: Implement Basic Auth
        Runtime: cloudfront-js-1.0

ただ、私の疑問はなぜRuntimeにcloudfront-js-1.0を使ったのかというところです。
最新はcloudfront-js-2.0です。
Claudeに聞いてみました。

Q: なぜ、cloudfront-js-2.0でなくcloudfront-js-1.0を使用したのですか

ご質問ありがとうございます。確かに、より新しいバージョンの cloudfront-js-2.0 を使用する方が良い選択肢であったかもしれません。私の回答で cloudfront-js-1.0 を使用した理由を説明し、そして cloudfront-js-2.0 を使用する利点について説明します。

なぜ cloudfront-js-1.0 を使用したか:
互換性: 多くの既存の実装例やドキュメントが cloudfront-js-1.0 を使用しているため、互換性の観点から選択しました。
習慣: CloudFront Functions が最初にリリースされた時のバージョンであり、多くの開発者にとってなじみ深いものです。
なぜ cloudfront-js-2.0 を使用すべきだったか:
最新機能: より新しいバージョンには、パフォーマンスの向上や新しい機能が含まれている可能性があります。
将来性: 新しいバージョンはより長期的なサポートが期待できます。
セキュリティ: 新しいバージョンには、セキュリティの改善が含まれている可能性があります。

cloudfront-js-2.0 の利点:
より多くの組み込み関数やAPIが利用可能である可能性があります。
パフォーマンスが向上している可能性があります。
今後追加される機能との互換性が高い可能性があります。

テンプレートを改善するには、以下のように Runtime を変更することをお勧めします

素晴らしいですね。

あと、GPTの方は変なIAMロールが混じってますね

あとこれはjavascriptの間違いですが、GPTはここもおかしいですね

if (typeof headers.authorization === 'undefined' || headers.authorization[0].value !== authString)

headers.authorization.valueですね

Claudeの方はちゃんとそうなってますね。

Claude気が利く

少し驚いたのが、Claudeの方は、Basic認証用のユーザー名とパスワードをパラメータで設定していることです。
しかもNoEcho: trueがついてますね
気が利きますね。
悪くいうとそんな指示はしていないということにもなりますが。

個人的にはこの気の利き方は好きです。
1聞いて10返ってきた方が、その10の中に自分のわからないことがあった場合、調べるきっかけになるので嬉しいです。

ただ、この回答、1つ気になるのが、CloudFront Functionsは実行可能時間がかなり短いというところです。Base64エンコーディングのbtoa()関数の実行時間がどれぐらいか?というところを考える必要があります。

関数コード内では、Base64エンコーディングにbtoa()関数を使用していますが、これはCloudFront Functionsの実行環境で利用可能です。

Claudeはこう言ってますが、本当かなと思うので実験してみます。

全然余裕でした。

ただ正直、個人的に毎回btoa関数走らせるのはしっくりこないのと、流石に平文のままコードにパスワードが入ってしまうのが嫌なので!Subと!Base64をうまく組み合わせてみました。

  BasicAuthFunction:
    Type: "AWS::CloudFront::Function"
    Properties:
      Name: "BasicAuthFunction"
      FunctionConfig:
        Comment: "Basic authentication"
        Runtime: "cloudfront-js-2.0"
      AutoPublish: true
      FunctionCode: !Sub 
        - | 
          function handler(event) {
            const request = event.request
            const headers = request.headers

            if (!headers.authorization || headers.authorization.value !== Basic "${AuthString}") {
              return {
                statusCode: 401,
                statusDescription: 'Unauthorized',
                headers: {
                  'www-authenticate': { value: 'Basic' }
                }
              };
            }

            return request;
          }
        - AuthString: !Base64 {Fn::Sub: "${Username}:${Password}"}

これだとBase64エンコードした状態でコードに埋め込まれるのでまだマシです。
ハッシュじゃないので少しマシなぐらいですが。
あと少しJavascriptを今っぽくしました。

限定したユーザーしかそもそもコンソールにアクセスできないのと、BASIC認証を使うであろう場合は限定的だと考えるとこれで十分かなと思いますね。

まとめ

GPT有料会員だったのですが、やめてClaudeに引っ越ししました。

Discussion