kirin-ri / memo

0 stars 0 forks source link

cicd #34

Open kirin-ri opened 2 weeks ago

kirin-ri commented 2 weeks ago
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
import * as cwactions from "aws-cdk-lib/aws-cloudwatch-actions";
import * as sns from "aws-cdk-lib/aws-sns";
import * as subscription from "aws-cdk-lib/aws-sns-subscriptions";
import * as chatbot from "aws-cdk-lib/aws-chatbot";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as path from "path";
import * as logs from "aws-cdk-lib/aws-logs";
import * as events from "aws-cdk-lib/aws-events";
import * as targets from "aws-cdk-lib/aws-events-targets";
import * as iam from "aws-cdk-lib/aws-iam";
import * as nag from "cdk-nag"; // cdk-nagモジュールのインポート

/**
 * MonitorCommonクラスの初期化に必要なパラメータ定義
 */
export interface MonitorCommonProps {
  /**  
   * 環境名を小文字の英字で指定する。  
   * リソースの物理名やNameタグに利用される。
   */
  envName: string;
  /**  
   * SecurityHubイベントの監視対象アカウントIDを指定する。  
   */
  accountId: string[];
  /**  
   * SecurityHubイベントの監視対象リージョンを指定する。  
   */
  region: string[];
  /**  
   * 監視アラーム通知リソースに異常が発生した場合の通知先E-mailアドレスを指定する。  
   */  
  monitorFailedNotificationEmail: string;
  /**  
   * 監視アラーム通知先に使用するWebHookのURLを指定する。  
   */ 
  webhookURL: string;  
  /**  
   * NotificationToTeams Function Durationアラーム閾値を指定する。  
   */ 
  notificationToTeamsFunctionDurationAlarmThreshold: number;
  /**  
   * SecurityHubEventNotification EventBridgeRule DeadLetterInvocationアラーム閾値を指定する。  
   */ 
  securityHubEventNotificationRuleDeadLetterInvocationAlarmThreshold: number;
}

/**
 * 監視共通要素を展開するConstruct
 * 
 * @remarks 
 * 監視に必要なAlarm通知先トピックやTeams通知用Lambda、SecurityHub監視用Ruleリソースを展開する。
 * - **Teams通知用Lambda**  
 *   SNSから受け取ったイベントをTeamsに通知するLambdaを構成するための関連要素をDeployする。
 *   - Teams通知Lambda用Log GP  
 *   - Teams通知Lambda用Role
 *   - Teams通知Lambda
 *   - Teams通知LambdaのDLQ
 * 
 * - **Alarm通知先トピック**  
 *   監視Alarmの通知先として利用するSNSTopicをDeployする。
 *   - Alarm通知Topic
 *   - Subscriptionに関連付けるDLQ
 * 
 * - **監視通知用リソースの異常通知先トピック**  
 *   監視通知用リソースに異常が発生した場合の通知先として利用するSNSTopicをDeployする。
 *   - 監視リソース異常通知Topic
 * 
 * - **SecurityHub監視Rule**  
 *   SecurityHubのイベントソースから要通知であるイベントを通知するEventBridge RuleをDeployする。
 *   - SecurityHub Event監視Rule
 * 
 * - **各種リソース毎の監視アラーム**   
 *   Alarm通知トピックに対して以下のメトリクスに対する監視アラームを生成する。  
 *   - NumberOfNotificationsFailed
 *   - NumberOfNotificationsFailedToRedriveToDlq  
 * 
 *   Teams通知用Lambdaに対して以下のメトリクスに対する監視アラームを生成する。  
 *   - Errors
 *   - DeadLetterErrors
 *   - DestinationDeliveryFailures
 *   - Throttles
 *   - Duration
 *   - AsyncEventsDropped  
 * 
 *   SecurityHub監視Ruleに対して以下のメトリクス監視アラームを生成する。  
 *   - DeadLetterInvocations  
 *   - FailedInvocations  
 *   - InvocationsFailedToBeSentToDlq 
 *   - ThrottledRules
 */
export class MonitorCommon extends Construct {
  /**
   * 監視通知用のトピック
   */
  public alarmTopic: sns.Topic;

  /**
   * 監視共通リソースを展開する。  
   * 初期化時に指定する{@link MonitorCommonProps 引数}で生成される環境のパラメータを調整する。
   * 
   * @param props - 
   * MonitorCommonクラスインスタンス初期化用のプロパティを指定する。詳細は{@link MonitorCommonProps}を参照すること。
   */
  constructor(scope: Construct, id: string, props: MonitorCommonProps) {
    super(scope, id);

    /**
     * ===============================
     * Temas通知用Lambda("Incoming Webhook"を使用)
     * ===============================
     */
    // Lambda LogGroup
    const myNotificationToTeamsFunctionLogGp = new logs.LogGroup(this, "NotificationToTeamsFunctionLogGp", {
      logGroupName: "/gc-std-archi1/" + props.envName + "/NotificationToTeamsFunctionLog",
      retention: logs.RetentionDays.THREE_MONTHS,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    cdk.Tags.of(myNotificationToTeamsFunctionLogGp).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionLog"
    );    

    // Lambda Role
    const myNotificationToTeamsFunctionRole = new iam.Role(this, "NotificationToTeamsFunctionRole", {
      roleName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionRole",
      assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
    });
    cdk.Tags.of(myNotificationToTeamsFunctionRole).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionRole"
    );
    // LogsへのWrite権限追加
    myNotificationToTeamsFunctionLogGp.grantWrite(myNotificationToTeamsFunctionRole);

    // DeadLetterQueueの作成
    const myNotificationToTeamsFunctionDLQueue = new sqs.Queue(this, "NotificationToTeamsFunctionDLQueue" , {
      queueName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDLQueue",
      enforceSSL: true,
    });
    cdk.Tags.of(myNotificationToTeamsFunctionDLQueue).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDLQueue"
    );

    // Lambda Function
    const myNotificationToTeamsFunction = new lambda.Function(this, "NotificationToTeamsFunction", {
      functionName: `gc-std-archi1-${props.envName}-notification-to-Teams`,
      code: lambda.Code.fromAsset(path.join(__dirname, "../../lambda/notification_to_teams/")),
      handler: "main.lambda_handler",
      runtime: lambda.Runtime.PYTHON_3_8,
      environment: {
        TARGET_URL: props.webhookURL,
      },
      role: myNotificationToTeamsFunctionRole,
      logGroup: myNotificationToTeamsFunctionLogGp,
      deadLetterQueueEnabled: true,
      deadLetterQueue: myNotificationToTeamsFunctionDLQueue
    });
    cdk.Tags.of(myNotificationToTeamsFunction).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-notification-to-Teams"
    );

    // nagエラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      myNotificationToTeamsFunction, 
      [
        { id: "AwsSolutions-L1", reason: "Because of implemented in python3.8."},
      ],
      false
    );    

    // QueueのDLQ設定エラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      myNotificationToTeamsFunctionDLQueue, 
      [
        { id: "AwsSolutions-SQS3", reason: "Because This Queue is DeadLetterQueue."}
      ],
      false
    );

    /**
     * ===============================
     * 通知用SNSトピック作成
     * ※通知用Lambdaとの関連付けも実施する
     * ===============================
     */
    // 監視アラーム通知用トピック作成
    this.alarmTopic = new sns.Topic(this, "AlarmTopic", {
      topicName: `gc-std-archi1-${props.envName}-AlarmTopic`
    })
    cdk.Tags.of(this.alarmTopic).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-AlarmTopic"
    );

    // 送信時のデータ暗号化を強制するポリシー(「AwsSolutions-SNS3」対応)
    this.alarmTopic.addToResourcePolicy(new iam.PolicyStatement({
      actions: ["sns:Publish"],
      effect: iam.Effect.DENY,
      resources: [this.alarmTopic.topicArn],
      conditions: {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      },
      principals: [new iam.AnyPrincipal()]
    }))

    // DeadLetterQueueの作成
    const alarmTopicDLQueue = new sqs.Queue(this, "AlarmTopicDLQueue" , {
      queueName: "gc-std-archi1-" + props.envName + "-AlarmTopicDLQueue",
      enforceSSL: true,
    });
    cdk.Tags.of(alarmTopicDLQueue).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-AlarmTopicDLQueue"
    );

    // サブスクリプションに「Teams通知用Lambda関数」を追加
    this.alarmTopic.addSubscription(new subscription.LambdaSubscription(myNotificationToTeamsFunction, {
      deadLetterQueue: alarmTopicDLQueue
    }));

    // CloudWatchからトピックにイベント発行できるようにポリシーを追加
    this.alarmTopic.grantPublish(new iam.ServicePrincipal("cloudwatch.amazonaws.com"))

    // SNSトピックのサーバサイド暗号化エラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      this.alarmTopic, 
      [
        { id: "AwsSolutions-SNS2", reason: "Because the SNS Topic must require publishers to use SSL."}
      ],
      false
    );

    // QueueのDLQ設定エラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      alarmTopicDLQueue, 
      [
        { id: "AwsSolutions-SQS3", reason: "Because this Queue is DeadLetterQueue."}
      ],
      false
    );

    /**
     * ===============================
     * 監視用リソース異常時のSNSトピック作成
     * ===============================
     */
    // 監視用リソース異常時の通知用トピック作成
    const monitorFailedTopic = new sns.Topic(this, "MonitorFailedTopic", {
      topicName: `gc-std-archi1-${props.envName}-MonitorFailedTopic`
    })
    cdk.Tags.of(monitorFailedTopic).add(
      "Name",
      "gc-std-archi1-" + props.envName + "-MonitorFailedTopic"
    );

    // 送信時のデータ暗号化を強制するポリシー(「AwsSolutions-SNS3」対応)
    monitorFailedTopic.addToResourcePolicy(new iam.PolicyStatement({
      actions: ["sns:Publish"],
      effect: iam.Effect.DENY,
      resources: [monitorFailedTopic.topicArn],
      conditions: {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      },
      principals: [new iam.AnyPrincipal()]
    }))

    // サブスクリプションに「Eメール」を追加
    monitorFailedTopic.addSubscription(new subscription.EmailSubscription(props.monitorFailedNotificationEmail));

    // CloudWatchからトピックにイベント発行できるようにポリシーを追加
    monitorFailedTopic.grantPublish(new iam.ServicePrincipal("cloudwatch.amazonaws.com"))    

    // SNSトピックのサーバサイド暗号化エラー抑止
    nag.NagSuppressions.addResourceSuppressions(
      monitorFailedTopic, 
      [
        { id: "AwsSolutions-SNS2", reason: "Because the SNS Topic must require publishers to use SSL."}
      ],
      false
    );

    /**
     * ===============================
     * AWS Chatbot(Teams連携)作成
     * ※通知用SNSトピックとの関連付けも行う
     * ※TeamsにChatbot連携アプリ(aws)を導入している必要があるが、全社ポリシーにより原則導入不可のためコメントアウト
     * ===============================
     */ 
    // // AWS Chatbot用のIAM Role作成
    // const myChatbotRole = new iam.Role(this, "ChatbotRole", {
    //   description: `Permission sets for read-only`,
    //   roleName: `gc-std-archi1-${props.envName}-chatbotRole`,
    //   assumedBy: new iam.ServicePrincipal("chatbot.amazonaws.com"),
    //   managedPolicies: [
    //     iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess"),
    //   ],
    // });

    // // nagエラー抑止
    // nag.NagSuppressions.addResourceSuppressions(
    //   myChatbotRole, 
    //   [
    //     { id: "AwsSolutions-IAM4", reason: "Use AWS Managed Policies.", appliesTo: ["Policy::arn:<AWS::Partition>:iam::aws:policy/ReadOnlyAccess"]},
    //   ],
    //   false
    // );

    // AWS Chatbot作成
    // 予めChatbotマネジメントコンソール上でTemas初期認証を済ませておく必要があり、初期認証後にCDKでデプロイする
    /*
    const myTeamsChatbot = new chatbot.CfnMicrosoftTeamsChannelConfiguration(this, "TeamsChatbot", {
      configurationName: `gc-std-archi1-${props.envName}-chatbot`,
      iamRoleArn: myChatbotRole.roleArn,
      teamId: "8fb2fb73-3892-4013-a1d9-2c0ab1f8d461", // TemasチャネルURL中の「groupId=xxxxx」
      teamsChannelId: "19%3a525651d941884ec5b6c243dd301a187e%40thread.tacv2",  // TemasチャネルURL中の「threadId=xxxxx」
      teamsTenantId: "1fcf450d-bb71-4efd-ae5d-90c7be757e12", // TemasチャネルURL中の「tenantId=xxxxx」

      // オプションパラメータ
      loggingLevel: "ERROR",
      snsTopicArns: [ this.alarmTopic.topicArn ],
    });
    */

    /**
     * ===============================
     * EventBridge Rule(Security Hubイベント通知)
     * ※通知用SNSトピックとの関連付けも行う
     * ===============================
     */ 
    const mySecurityHubEventNotificationRule = new events.Rule(this, "SecurityHubEventNotification", {
      ruleName: `gc-std-archi1-${props.envName}-security-hub-notification`,
      eventPattern: {
        source: ["aws.securityhub"],
        detailType: ["Security Hub Findings - Imported"],
        account: props.accountId,
        region: props.region,
        detail: {
          "$or": [
            // SecurityHubの「統合」を無効に設定している場合のイベントパターン
            {
              "findings": {
                // 基礎セキュリティのベストプラクティスの範囲
                "ProductFields": {
                  "StandardsArn": [ "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0" ]
                },
                // コンプライアンス順守状況が"PASSED"以外
                "Compliance": {
                  "Status": [ "FAILED", "WARNING", "NOT_AVAILABLE" ]
                },
                // 重要度が"HIGH"(優先事項として要対処)以上
                "Severity": {
                  "Label": [ "CRITICAL", "HIGH" ]
                },
                // ステータスが"ACTIVE"なもの
                "RecordState": [ "ACTIVE" ],
                // フロー状態が"NEW"であるもの
                "Workflow": {
                  "Status": [ "NEW" ]
                }
              }
            },
            // SecurityHubの「統合」を有効に設定している場合のイベントパターン
            {
              "findings": {
                // コンプライアンス順守状況が"PASSED"以外
                // 基礎セキュリティのベストプラクティスの範囲
                "Compliance": {
                  "Status": [ "FAILED", "WARNING", "NOT_AVAILABLE" ],
                  "AssociatedStandards": {
                    "StandardsId": ["standards/aws-foundational-security-best-practices/v/1.0.0"]
                  }  
                },
                // 重要度が"HIGH"(優先事項として要対処)以上
                "Severity": {
                  "Label": [ "CRITICAL", "HIGH" ]
                },
                // ステータスが"ACTIVE"なもの
                "RecordState": [ "ACTIVE" ],
                // フロー状態が"NEW"であるもの
                "Workflow": {
                  "Status": [ "NEW" ]
                }
              }
            }
          ]
        }
      },
      targets: [ new targets.SnsTopic(this.alarmTopic) ]
    });

    /**
     * ===============================
     * アラーム通知用SNSTopic アラーム監視定義
     * ===============================
     */
    // NumberOfNotificationsFailed のアラーム作成
    const alarmTopicNumberOfNotificationsFailedAlarm = this.alarmTopic.metricNumberOfNotificationsFailed({
      period: cdk.Duration.minutes(1),
      statistic: cloudwatch.Stats.SUM,    
    })
    .createAlarm(this, "AlarmTopicNumberOfNotificationsFailedAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-AlarmTopic-NumberOfNotificationsFailed",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));

    // NumberOfNotificationsFailedToRedriveToDlq のアラーム作成
    const alarmTopicNumberOfNotificationsFailedToRedriveToDlqAlarm = this.alarmTopic.metric(
      "NumberOfNotificationsFailedToRedriveToDlq",
      {
        period: cdk.Duration.minutes(5),
        statistic: cloudwatch.Stats.SUM,    
      }
    )
    .createAlarm(this, "AlarmTopicNumberOfNotificationsFailedToRedriveToDlqAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-AlarmTopic-NumberOfNotificationsFailedToRedriveToDlq",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));    

    /**
     * ===============================
     * Temas通知用Lambda アラーム監視定義
     * ===============================
     */ 
    // Errors のアラーム作成
    const myNotificationToTeamsFunctionErrorsAlarm = myNotificationToTeamsFunction.metricErrors({
      period: cdk.Duration.minutes(5),
      statistic: cloudwatch.Stats.SUM,
    })
    .createAlarm(this, "NotificationToTeamsFunctionErrorsAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionErrors",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // DeadLetterErrors のアラーム作成
    const myNotificationToTeamsFunctionDeadLetterErrorsAlarm = myNotificationToTeamsFunction.metric(
      "DeadLetterErrors",
      {
        period: cdk.Duration.minutes(5),
        statistic: cloudwatch.Stats.SUM,
      }
    )
    .createAlarm(this, "NotificationToTeamsFunctionDeadLetterErrorsAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDeadLetterErrors",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // DestinationDeliveryFailures のアラーム作成
    const myNotificationToTeamsFunctionDestinationDeliveryFailuresAlarm = myNotificationToTeamsFunction.metric(
      "DestinationDeliveryFailures",
      {
        period: cdk.Duration.minutes(5),
        statistic: cloudwatch.Stats.SUM,
      }
    )
    .createAlarm(this, "NotificationToTeamsFunctionDestinationDeliveryFailuresAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDestinationDeliveryFailures",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // Throttles のアラーム作成
    const myNotificationToTeamsFunctionThrottlesAlarm = myNotificationToTeamsFunction.metricThrottles({
      period: cdk.Duration.minutes(5),
      statistic: cloudwatch.Stats.SUM,
    })
    .createAlarm(this, "NotificationToTeamsFunctionThrottlesAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionThrottles",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // Duration のアラーム作成
    const myNotificationToTeamsFunctionDurationAlarm = myNotificationToTeamsFunction.metricDuration({
      period: cdk.Duration.minutes(5),
      statistic: cloudwatch.Stats.AVERAGE,
    })
    .createAlarm(this, "NotificationToTeamsFunctionDurationAlarm", {
      evaluationPeriods: 1,
      threshold: props.notificationToTeamsFunctionDurationAlarmThreshold,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionDuration",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    // AsyncEventsDropped のアラーム作成
    const myNotificationToTeamsFunctionAsyncEventsDroppedAlarm = myNotificationToTeamsFunction.metric(
      "AsyncEventsDropped",
      {
        period: cdk.Duration.minutes(5),
        statistic: cloudwatch.Stats.SUM,
      }
    )
    .createAlarm(this, "NotificationToTeamsFunctionAsyncEventsDroppedAlarm", {
      evaluationPeriods: 1,
      threshold: 0,
      alarmName: "gc-std-archi1-" + props.envName + "-NotificationToTeamsFunctionAsyncEventsDropped",
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
    })
    .addAlarmAction(new cwactions.SnsAction(monitorFailedTopic)); 

    /**
     * ===============================
     * SecurityHubイベント監視Rule アラーム監視定義
     * ===============================
     */     
    // DeadLetterInvocationsの監視アラーム作成
    const mySecurityHubEventNotificationRuleDeadLetterInvocationAlarm = new cloudwatch.Metric({
      namespace: "AWS/Events",
      metricName: "DeadLetterInvocations",
      dimensionsMap: {
        RuleName: mySecurityHubEventNotificationRule.ruleName
      },
      statistic: cloudwatch.Stats.MAXIMUM,
      period: cdk.Duration.minutes(5)
    })
    .createAlarm(this, "SecurityHubEventNotificationRuleDeadLetterInvocationAlarm", {
      alarmName: `gc-std-archi1-${props.envName}-SecurityHubEventNotificationRule-DeadLetterInvocations`,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
      threshold: props.securityHubEventNotificationRuleDeadLetterInvocationAlarmThreshold,
      evaluationPeriods: 1,
    });
    // Alarmアクション追加
    mySecurityHubEventNotificationRuleDeadLetterInvocationAlarm.addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));  

    // FailedInvocationsの監視アラーム作成
    const mySecurityHubEventNotificationRuleFailedInvocationsAlarm = new cloudwatch.Metric({
      namespace: "AWS/Events",
      metricName: "FailedInvocations",
      dimensionsMap: {
        RuleName: mySecurityHubEventNotificationRule.ruleName
      },
      statistic: cloudwatch.Stats.SUM,
      period: cdk.Duration.minutes(5)
    })
    .createAlarm(this, "SecurityHubEventNotificationRuleFailedInvocationsAlarm", {
      alarmName: `gc-std-archi1-${props.envName}-SecurityHubEventNotificationRule-FailedInvocations`,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
      threshold: 0,
      evaluationPeriods: 1,
    });
    // Alarmアクション追加    
    mySecurityHubEventNotificationRuleFailedInvocationsAlarm.addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));

    // InvocationsFailedToBeSentToDlqの監視アラーム作成
    const mySecurityHubEventNotificationRuleInvocationsFailedToBeSentToDlqAlarm = new cloudwatch.Metric({
      namespace: "AWS/Events",
      metricName: "InvocationsFailedToBeSentToDlq",
      dimensionsMap: {
        RuleName: mySecurityHubEventNotificationRule.ruleName
      },
      statistic: cloudwatch.Stats.SUM,
      period: cdk.Duration.minutes(5)
    })
    .createAlarm(this, "SecurityHubEventNotificationRuleInvocationsFailedToBeSentToDlqAlarm", {
      alarmName: `gc-std-archi1-${props.envName}-SecurityHubEventNotificationRule-InvocationsFailedToBeSentToDlq`,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
      threshold: 0,
      evaluationPeriods: 1,
    });
    // Alarmアクション追加    
    mySecurityHubEventNotificationRuleInvocationsFailedToBeSentToDlqAlarm.addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));

    // ThrottledRulesの監視アラーム作成
    const mySecurityHubEventNotificationRuleThrottledRulesAlarm = new cloudwatch.Metric({
      namespace: "AWS/Events",
      metricName: "ThrottledRules",
      dimensionsMap: {
        RuleName: mySecurityHubEventNotificationRule.ruleName
      },
      statistic: cloudwatch.Stats.SUM,
      period: cdk.Duration.minutes(5)
    })
    .createAlarm(this, "SecurityHubEventNotificationRuleThrottledRulesAlarm", {
      alarmName: `gc-std-archi1-${props.envName}-SecurityHubEventNotificationRule-ThrottledRules`,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
      threshold: 0,
      evaluationPeriods: 3,
    });
    // Alarmアクション追加
    mySecurityHubEventNotificationRuleThrottledRulesAlarm.addAlarmAction(new cwactions.SnsAction(monitorFailedTopic));
  };
}
kirin-ri commented 3 days ago
import json

def lambda_handler(event, context):
    # 受け取ったパラメータを処理
    params = event.get("params", {})
    filter = params.get("filter", "default")
    limit = params.get("limit", 5)

    # ダミーデータを返却
    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps({
            "filter": filter,
            "limit": limit,
            "data": [{"value": i} for i in range(limit)]
        })
    }
kirin-ri commented 3 days ago
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";

export class CustomWidgetStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    // Custom Widget の作成
    const customWidget = new cloudwatch.CustomWidget({
      title: "Custom Lambda Widget",
      functionArn: "arn:aws:lambda:us-west-2:123456789012:function:MyCustomWidgetFunction", // Lambda 関数の ARN
      params: {
        filter: "errors",
        limit: 10,
      }, // Lambda 関数に渡すパラメータ
      height: 6, // ウィジェットの高さ
      width: 12, // ウィジェットの幅
      updateOnRefresh: true, // リフレッシュ時に更新
      updateOnResize: false, // リサイズ時に更新しない
      updateOnTimeRangeChange: true, // 時間範囲変更時に更新
    });

    // ダッシュボードの作成とカスタムウィジェットの追加
    const dashboard = new cloudwatch.Dashboard(this, "MyCustomDashboard", {
      dashboardName: "CustomDashboard",
      widgets: [[customWidget]], // カスタムウィジェットを追加
    });
  }
}
kirin-ri commented 3 days ago
import json

def lambda_handler(event, context):
    try:
        # パラメータの取得
        params = event.get("params", {})
        filter_value = params.get("filter", "default")
        limit = params.get("limit", 5)

        # ダミーデータの生成
        data = [{"value": i} for i in range(limit)]

        # 正しいレスポンス形式で返す
        return {
            "statusCode": 200,
            "headers": {
                "Content-Type": "application/json"
            },
            "body": json.dumps({
                "filter": filter_value,
                "limit": limit,
                "data": data
            })
        }
    except Exception as e:
        # エラーハンドリング
        return {
            "statusCode": 500,
            "headers": {
                "Content-Type": "application/json"
            },
            "body": json.dumps({"error": str(e)})
        }
kirin-ri commented 3 days ago
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
import * as cdk from "aws-cdk-lib";

const app = new cdk.App();
const stack = new cdk.Stack(app, "MyStack");

// サンプルウィジェット
const sampleTextWidget = new cloudwatch.TextWidget({
  markdown: "### Sample Widget",
  width: 12
});

// ColumnWidget の使用例
const columnWidget = new cloudwatch.ColumnWidget({
  width: 24, // カラム全体の幅
  widgets: [
    sampleTextWidget, // テキストウィジェット
    new cloudwatch.SpacerWidget({ width: 24, height: 3 }) // スペーサーウィジェット
  ]
});

// RowWidget の使用例
const rowWidget = new cloudwatch.RowWidget({
  widgets: [
    sampleTextWidget, // テキストウィジェット
    new cloudwatch.SpacerWidget({ width: 6, height: 3 }) // スペーサーウィジェット
  ]
});

// ダッシュボードに追加
new cloudwatch.Dashboard(stack, "MyDashboard", {
  dashboardName: "MyCustomDashboard",
  widgets: [
    [columnWidget], // カラムウィジェットを 1 行目に追加
    [rowWidget] // ロウウィジェットを 2 行目に追加
  ]
});
kirin-ri commented 2 days ago
    const tableWidget = new cloudwatch.TableWidget({
      title: "Full Table Example", // タイトル
      metrics: [cpuUtilizationMetric, networkInMetric], // 表示するメトリクス
      width: 12, // ウィジェットの幅
      height: 6, // ウィジェットの高さ
      fullPrecision: true, // 小数点以下を完全に表示
      period: cdk.Duration.minutes(10), // デフォルトの期間
      region: "us-west-2", // 表示するメトリクスのリージョン
      start: "2023-01-01T00:00:00Z", // 開始時刻
      end: "2023-12-31T23:59:59Z", // 終了時刻
      liveData: true, // ライブデータを表示
      showUnitsInLabel: true, // ラベルに単位を表示
      thresholds: [
        cloudwatch.TableThreshold.above(80, cloudwatch.Color.RED), // 80%以上を赤
        cloudwatch.TableThreshold.between(50, 80, cloudwatch.Color.ORANGE), // 50〜80%をオレンジ
        cloudwatch.TableThreshold.below(50, cloudwatch.Color.GREEN), // 50%未満を緑
      ],
      summary: {
        columns: [cloudwatch.TableSummaryColumn.AVERAGE, cloudwatch.TableSummaryColumn.MAXIMUM], // 平均値と最大値
        hideNonSummaryColumns: true, // サマリー列のみ表示
        sticky: true, // サマリー列を固定
      },
    });