[小ネタ] コンソール上で確認出来ないCloudWatchメトリクスにCFnでアラームを設定したい

[小ネタ] コンソール上で確認出来ないCloudWatchメトリクスにCFnでアラームを設定したい

2025.10.15

はじめに

コンサルティング部のぐっさんです。

最近WAFのBlockedRequestsについてアラーム発報したいけれどコンソールでメトリクスが出力されていない!といった事象に出会いましたので対処方法について確認した記録です。

原因

以下ドキュメントのレポート条件が原因です。

https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/waf-metrics.html

レポート条件: ゼロ以外の値がある。

対象のメトリクスで何らかの値が検知されないと、コンソール上で確認できないわけです。
WAFのBlockedRequestsの場合は、何らかのアクセスブロックが発生しない限りは見えない状態です。

CLIやマネジメントコンソールでの設定方法

これらについては既にわかりやすい記事がありますので、以下参照ください。

  • CLI編

https://dev.classmethod.jp/articles/tsnote-cloudwatch-alarm-metrics-not-found-001/

  • マネジメントコンソール編

https://dev.classmethod.jp/articles/tsnote-cloudwatch-alarm-metrics-not-found-002/

今回はIaC(CFn)からもメトリクスを直接指定してアラーム設定できるのか?にフォーカスして検証した記事となります。

結果

IaCを使用しても問題なくアラームの作成が可能です。
対象の名前空間とメトリクスをプロパティで直指定します。

今回の検証で使用したCFnで指定する場合の書き方の一例は以下です。

			
			Resources:
  WAFBlockedRequestsAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref AlarmName
      AlarmDescription: !Sub 'Alarm when WAF BlockedRequests exceeds ${Threshold} in ${Period} seconds. WebACL: ${WebACLName} (ID: ${WebACLId}), Region: ${Region}, Rule: ${Rule}'
      MetricName: BlockedRequests
      Namespace: AWS/WAFV2
      Statistic: !Ref Statistic
      Period: !Ref Period
      EvaluationPeriods: !Ref EvaluationPeriods
      DatapointsToAlarm: !Ref DatapointsToAlarm
      Threshold: !Ref Threshold
      ComparisonOperator: !Ref ComparisonOperator
      TreatMissingData: !Ref TreatMissingData
      Dimensions:
        - Name: WebACL
          Value: !Ref WebACLName
        - Name: Region
          Value: !Ref Region
        - Name: Rule
          Value: !Ref Rule
      AlarmActions:
        - !Ref SNSTopicArn

		

以下は実際に検証した手順を記載します。

検証内容

前提

  • 既存のALBに対してWAFを作成しアタッチ
  • ALBのデフォルトアクションに固定レスポンスを返す設定を入れアクセス成功時はメンテナンス画面を表示する想定
  • WAFによりアクセスがブロックされた際にはWAFのカスタムレスポンスを返す
  • WAFのルールはIPセットを使用しホワイトリスト以外のIPからの接続は拒否する
  • アラーム通知先: 既存のSNS・Chatbot経由でSlackへ通知

手順

  • WAFのIPセット(ホワイトリスト用)を作成し自身のIPを入れる
  • WAF作成
  • ALBのDNS名でアクセス実行
  • WAFのCloudWatchメトリクスでAllowedRequestsは更新されるがBlockedRequestsは見えないことを確認
  • CFnでBlockedRequestsのアラーム作成
  • WAFのホワイトリストから自身のIPを削除しアクセスがブロックされる(WAFのカスタムレスポンスが返される)ことを確認
  • アラーム通知がくることを確認
  • BlockedRequestsがメトリクス上で見えるようになることを確認

内容

WAFのIPセット(ホワイトリスト用)を作成し自身のIPを入れる

waf_1

waf_2

WAF作成

名前を適当につけてコンソールから作成します

waf_3

リソースに既存のALBを設定

waf_4

waf_5

ルールに最初に作成したIPセットを指定

waf_6

waf_7

ホワイトリスト想定のためルールに合致した時のアクションは「Allow」を選択

waf_8

WAFのデフォルトアクションは「Block」としてついでにカスタムレスポンスを設定

waf_9

waf_10

ブロックされた時に表示するHTMLを設定

waf_11

ちなみにHTMLはAIにサッと作ってもらったものです。

			
			<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>アクセス制限</title>
<style>
body{margin:0;font-family:sans-serif;background:linear-gradient(45deg,#ff6b6b,#feca57,#48dbfb,#ff9ff3);background-size:400% 400%;animation:bg 15s ease infinite;min-height:100vh;display:flex;justify-content:center;align-items:center}
@keyframes bg{0%,100%{background-position:0 50%}50%{background-position:100% 50%}}
.card{background:#fff;border-radius:40px;padding:40px;max-width:500px;box-shadow:0 30px 60px rgba(0,0,0,.2);text-align:center}
.emoji{font-size:60px;margin-bottom:20px;animation:wiggle 1s ease-in-out infinite}
@keyframes wiggle{0%,100%{transform:rotate(-5deg)}50%{transform:rotate(5deg)}}
h1{background:linear-gradient(45deg,#ff6b6b,#feca57,#48dbfb);-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-size:32px;margin-bottom:15px}
p{color:#555;font-size:16px;line-height:1.6;margin-bottom:20px}
.cards{display:flex;gap:10px;margin:20px 0;flex-wrap:wrap;justify-content:center}
.mini{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:15px;border-radius:15px;font-size:12px;flex:1;min-width:100px;transition:transform .3s}
.mini:hover{transform:scale(1.05)}
.mini .i{font-size:24px;margin-bottom:5px}
.btn{display:inline-block;background:linear-gradient(135deg,#ff6b6b,#feca57);color:#fff;padding:15px 40px;border-radius:30px;text-decoration:none;font-weight:bold;box-shadow:0 10px 30px rgba(255,107,107,.4);transition:transform .3s}
.btn:hover{transform:translateY(-3px)}
.link{margin-top:20px;color:#999;font-size:12px}
.link a{color:#667eea;text-decoration:none}
</style>
</head>
<body>
<div class="card">
<div class="emoji">🚫✨</div>
<h1>ちょっと待って!</h1>
<p>セキュリティシステムがリクエストをブロックしました</p>
<div class="cards">
<div class="mini"><div class="i">🤖</div>Bot検出</div>
<div class="mini"><div class="i">🌍</div>地域制限</div>
<div class="mini"><div class="i">⚡</div>レート超過</div>
</div>
<a href="/" class="btn">🏠 ホームへ</a>
<div class="link">正当なアクセスですか?<br><a href="mailto:support@example.com">サポートに連絡</a></div>
</div>
</body>
</html>

		

その他の設定はすべてデフォルトでWAFを作成します。

ALBのDNS名でアクセス実行

ホワイトリストで自身のIPが許可されている状態なので、アクセスするとALBに設定した固定レスポンスの画面が表示されます。

web_1

これもAI産です。最大1024文字までなのでシンプルなコードを入れています。

			
			<!DOCTYPE html><html><head><meta charset=UTF-8><meta name=viewport content=width=device-width,initial-scale=1><style>body{margin:0;font-family:sans-serif;background:#667eea;min-height:100vh;display:flex;justify-content:center;align-items:center}.box{background:white;border-radius:20px;padding:40px;max-width:500px;text-align:center;box-shadow:0 10px 40px black}.icon{font-size:60px;margin-bottom:15px}h1{color:#667eea;font-size:28px;margin:10px 0}p{color:gray;font-size:16px;margin:15px 0}.btn{display:inline-block;background:#667eea;color:white;padding:12px 30px;border-radius:20px;text-decoration:none;font-weight:bold;margin-top:10px}</style></head><body><div class=box><div class=icon>&#128736;</div><h1>メンテナンス中</h1><p>システムメンテナンスを実施中です</p><p>しばらくお待ちください</p><a href=/ class=btn>&#128260; 再読み込み</a></div></body></html>

		

WAFのCloudWatchメトリクスでAllowedRequestsは更新されるがBlockedRequestsは見えないことを確認

アクセスを行うまではメトリクスが見えないかと思いますが、アクセス後少し経過するとコンソールから対象のWAF名で検索すると確認できます。

メトリクスの検索窓にWAF名を入れて確認
赤枠の場所でAllowedRequestsを確認できます。

met_1

AllowedRequestsが確認できるが、BlockedRequestsのメトリクスが表示されていないことを確認

met_2

今回はこの状態で直接見えないBlockedRequestsを指定してアラームを作成できるか実験します。

CFnでBlockedRequestsのアラーム作成

全体のコードは以下の通りです。

			
			AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudWatch Alarm for WAF BlockedRequests'

Parameters:
  WebACLName:
    Type: String
    Description: 'Name of the WAF WebACL to monitor'

  WebACLId:
    Type: String
    Description: 'ID of the WAF WebACL'

  Region:
    Type: String
    Description: 'Region of the WAF WebACL'
    Default: 'ap-northeast-1'

  Rule:
    Type: String
    Description: 'Rule name to monitor'
    Default: 'ALL'

  SNSTopicArn:
    Type: String
    Description: 'ARN of the SNS topic for alarm notifications'

  AlarmName:
    Type: String
    Description: 'Name of the CloudWatch alarm'
    Default: 'WAF-BlockedRequests'

  Threshold:
    Type: Number
    Description: 'Number of blocked requests to trigger alarm'
    Default: 1

  EvaluationPeriods:
    Type: Number
    Description: 'Number of periods to evaluate'
    Default: 1
    MinValue: 1

  DatapointsToAlarm:
    Type: Number
    Description: 'Number of datapoints that must be breaching to trigger alarm'
    Default: 1
    MinValue: 1

  Period:
    Type: Number
    Description: 'Period in seconds for evaluation'
    Default: 60
    AllowedValues:
      - 60
      - 300
      - 900
      - 3600

  Statistic:
    Type: String
    Description: 'Statistic to apply to the metric'
    Default: 'Sum'
    AllowedValues:
      - Sum
      - Average
      - Maximum
      - Minimum
      - SampleCount

  ComparisonOperator:
    Type: String
    Description: 'Comparison operator for the alarm'
    Default: 'GreaterThanOrEqualToThreshold'
    AllowedValues:
      - GreaterThanThreshold
      - GreaterThanOrEqualToThreshold
      - LessThanThreshold
      - LessThanOrEqualToThreshold

  TreatMissingData:
    Type: String
    Description: 'How to treat missing data'
    Default: 'notBreaching'
    AllowedValues:
      - notBreaching
      - breaching
      - ignore
      - missing

Resources:
  WAFBlockedRequestsAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: !Ref AlarmName
      AlarmDescription: !Sub 'Alarm when WAF BlockedRequests exceeds ${Threshold} in ${Period} seconds. WebACL: ${WebACLName} (ID: ${WebACLId}), Region: ${Region}, Rule: ${Rule}'
      MetricName: BlockedRequests
      Namespace: AWS/WAFV2
      Statistic: !Ref Statistic
      Period: !Ref Period
      EvaluationPeriods: !Ref EvaluationPeriods
      DatapointsToAlarm: !Ref DatapointsToAlarm
      Threshold: !Ref Threshold
      ComparisonOperator: !Ref ComparisonOperator
      TreatMissingData: !Ref TreatMissingData
      Dimensions:
        - Name: WebACL
          Value: !Ref WebACLName
        - Name: Region
          Value: !Ref Region
        - Name: Rule
          Value: !Ref Rule
      AlarmActions:
        - !Ref SNSTopicArn

Outputs:
  AlarmName:
    Description: 'Name of the created alarm'
    Value: !Ref WAFBlockedRequestsAlarm
    Export:
      Name: !Sub '${AWS::StackName}-AlarmName'

  AlarmArn:
    Description: 'ARN of the created alarm'
    Value: !GetAtt WAFBlockedRequestsAlarm.Arn
    Export:
      Name: !Sub '${AWS::StackName}-AlarmArn'

		

コンソール上は見えませんが、取得したいメトリクス名を直接指定します。
パラメータで送信先のトピック名等を指定します。

リソースが作成されたことを確認

cfn_1

特にエラーなくアラームが作成されました!
ではアクセスを失敗させてアラームが本当に発報されるか確認します。

WAFのホワイトリストから自身のIPを削除しアクセスがブロックされる(WAFのカスタムレスポンスが返される)ことを確認

ホワイトリストからIPを削除

waf_100

waf_110

再度ALBのDNS名でアクセスを実行

web_2

すると先ほどの画面とは異なる表示になりました。
これはWAF側に設定したHTMLなので、きちんとアクセスがブロックされていることがわかります。

アラーム通知がくることを確認

Slackを見ると、以下の通知が来ていました。

alarm_2

ちゃんとアラームアクションが動いていますね!

CloudWatchのコンソールでもアラーム状態に遷移していました。

alarm_1

BlockedRequestsがメトリクス上で見えるようになることを確認

さきほどは見えなかったメトリクスを確認します。

alarm_3

BlockedRequestsがひょこっと現れました。さらにこのメトリクスがアラーム状態であることがわかります。

終わりに

今回はCFnで直指定してアラームの設定ができることを確認したくて検証しました。
ついでにカスタムレスポンス等も設定してこんなにフロント画面を簡単に設定できるようになったことに感動しました。

このブログが何かの役に立てたら幸いです。
最後までお読みいただきありがとうございました!

この記事をシェアする

FacebookHatena blogX

関連記事