🐥

AWS WAFもCDKでサクッとデプロイしよう!ログ設計も忘れずに

2023/12/17に公開

概要

Qiita Advent Calendar 2023 「AWS CDK」 17日目の記事です。

こんなことを話します。

  • AWS WAFもCDK管理するススメ
  • 忘れがちなログ設計についての話
  • その他入れる際に気にすると良いこと
    • 明示的に外部公開していないパスはブロックするルールを設定することで403を返す
    • 思わぬBLOCKに注意

AWS WAFもCDK管理するススメ

CDKについてのアドベントカレンダーだからという感じもしますが、AWS WAFについてもIaC(CDK)管理していくことを個人的にはお勧めしたいです。

AWS WAFはCloudFrontにワンクリックでコンソールから適用できるようになったり、

https://aws.amazon.com/jp/about-aws/whats-new/2023/05/amazon-cloudfront-one-click-security-protections/

CloudFrontにセキュリティダッシュボードが統合され、コンソール上でWAFが防御している情報の可視化などが進み、

https://aws.amazon.com/jp/about-aws/whats-new/2023/11/amazon-cloudfront-unified-security-dashboard/

操作が簡単になったり、導入後にモニタリングしやすくなってきています。

この辺りは、ありがたいアップデートだと思うのですが、AWS WAFをどうやって導入するかについては、検討の余地があると思います。

例えば、以下のような状況下ではどうでしょうか。

  • 1プロダクトで複数環境(Production, Staging, Developなど)が存在する
  • 複数プロダクトで共通のルールを適用したい

このような状況では、

  • デプロイが自動化されていること
  • 設定がコード管理されていて環境による差分・設定ミスが発生しにくいこと

が大事になってくると思います。そんな場合、CDKでWAFをアタッチすると便利でしょう!!

CDKでCloudFrontにWAFをアタッチする場合は、こんな感じでアタッチできます。

web ACLの作成:

    const webAcl = new awswaf.CfnWebACL(scope, appNamespace, {
      defaultAction: {
        allow: {},
      },
      name: 'XXXACL',
      scope: 'CLOUDFRONT',
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        sampledRequestsEnabled: true,
        metricName: 'XXXACLMetrics',
      },
      rules: [
        {
          name: "AWSManagedRulesCommonRuleSet",
          priority: 1,
          statement: {
            managedRuleGroupStatement: {
              vendorName: "AWS",
              name: "AWSManagedRulesCommonRuleSet",
              excludedRules: [
                { name: "SizeRestrictions_BODY" }
              ]
            },
          },
          overrideAction: { none: {} },
          visibilityConfig: {
            cloudWatchMetricsEnabled: true,
            sampledRequestsEnabled: true,
            metricName: "AWSManagedRulesCommonRuleSet",
          },
        },
      ]
    });

CloudFrontの設定:

    const distribution = new cloudfront.Distribution(this, 'Distribution', {
      defaultBehavior: { 
        origin: new origins.HttpOrigin(props.albDomainName,{}),
      },
      webAclId: webAcl.wafAclArn,

パラメータ名はwebAclIdとあって、何かIdを指定するように見えたのですが、webAclのARNを指定すればいいようでした。

以上が、AWS WAFをCloudFrontへ適用するCDKコードになります。
サクッと導入できる感じがしてきましたよね(?)

忘れがちなログ設計についての話

上記のCDKコードだけでAWS WAFの導入はできるのですが、本番環境で運用する場合は、もう少し設定をした方がいいことがあります。それは、AWS WAFのログについてです。

例えば、アプリケーションを使っているユーザーが悪意のない場合でも使っている環境によってはAWS WAFがBLOCKしてしまう可能性があります。
その際に、

  • ユーザーからの視点:アプリケーションが使えなかった
  • 開発者の視点:アプリケーションログやアクセスログには特に情報がない
    といった困った状況になってしまいます。
    そのため、WAFを導入する際は、最低限BLOCKしたログについては出力するようにした方がいいでしょう。
    最低限と言ったのは全てのログを保存するようにしてしまうと全通信について保存することになるのでログの料金が嵩むなどの問題が発生するケースがあります。そのため、何のログを残すかは非機能要件としても検討しましょう。

実際に、BLOCKログだけをCloudWatch Logsに出力するCDKコードはこちらです。

    // loudWatchロググループ 
    const logGroup = new cdk.aws_logs.LogGroup(scope, "awsWafLogsLogGroup", {
      logGroupName: "aws-waf-logs-xxx-log-group", // **"aws-waf-logs-" で始まる必要がある**
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      retention: cdk.aws_logs.RetentionDays.SIX_MONTHS
    });
   
    // WAFログ出力設定
    const accountId = cdk.Stack.of(scope).account;
    const region = cdk.Stack.of(scope).region;
    const logConfig = new awswaf.CfnLoggingConfiguration(scope, 'WafLoggingConfiguration', {
      logDestinationConfigs: [
        `arn:aws:logs:${region}:${accountId}:log-group:${logGroup.logGroupName}` // **LogGroupのARNから':*'を取り除いたものを指定する必要がある**
      ], 
      resourceArn: this.webAcl.attrArn,// WebACLのみ指定
      
      // 以下フィルターを設定
      loggingFilter: {
          DefaultBehavior: 'DROP', //デフォルトはDROPとする
          // BLOCKした場合のみKEEPの設定とする
          Filters: [{
              Behavior: 'KEEP',
              Conditions: [{
                  ActionCondition: {
                      Action: 'BLOCK' // BLOCKされた場合のみ
                  }
              }],
              Requirement: 'MEETS_ALL' // 条件全てに合致した場合
          }],
      }
    })

logGroupNameに制約があったりはしますが、サクッといけそうですね(?)

その他入れる際に気にすると良いこと

明示的に外部公開していないパスはブロックするルールを設定することで403を返す

実際にサービスを公開すると、サービスが有名であるかに関わらず、クローラーやBOTと思われるリクエストがそこそこ飛んできます。そのようなリクエストはなるべく弾き、403を返すことができた方が、

  • セキュリティ対策を取っていると攻撃者に分からせることにもつながる
  • リクエスト数を減らすことでリソースを無駄に消費しなくていい
    メリットがあります。

例えば、REST APIを公開していて、"/"のパスは使っていない場合は、以下のように"/"へのリクエストをブロックするルールを記述できます。

      rules: [
        {
          action: { block: {} },
          name: 'BlockRootAccess',
          priority: 0,
          statement: {
            byteMatchStatement: {
              fieldToMatch: {
                uriPath: {}
              },
              positionalConstraint: 'EXACTLY',
              searchString: '/',
              textTransformations: [
                {
                  priority: 0,
                  type: 'NONE'
                }
              ]
            }
          },
          visibilityConfig: {
            cloudWatchMetricsEnabled: true,
            metricName: 'blockRootAccessMetric',
            sampledRequestsEnabled: true
          }
        },  

思わぬBLOCKに注意

AWS WAFを導入してみようとなった時に、AWSManagedRulesCommonRuleSetをまずは導入してみる、というケースも多いのではないでしょうか?
多くのルールが存在していてWAFに詳しくなくても導入しやすいAWSManagedRulesCommonRuleSetですが、

  • SizeRestrictions_BODY: 8kbを超える通信をBLOCK
    などの通常の仕様として使おうとしてもBLOCKルールにかかってしまうルールもあります。
    そのため、以下のことをオススメします。
  • なるべくリリース前にWAFを導入して、意図しないBLOCKがないように検証する
  • リリース後にWAFを導入する際はBLOCKモードでなくCOUNTモードで意図しないBLOCKをしないように注意

まとめ

「AWS WAFもCDKでサクッとデプロイしよう!ログ設計も忘れずに」という内容についての話については以上になります。
CDKは多くのメリットがあり、自分自身も好きなサービスですが、簡単に色々なサービスを扱える反面、考慮漏れなどでハマるケースもあるかと思うのでしっかりと設計できるようになりたいですね(自戒込め

以上、CDKを活用したい人、AWS WAFについて考えている人などの役に立てれば幸いです。

Discussion