ECRイメージスキャンを重要度でフィルターして通知（CDK）

# 概要
AWS Lambdaを使用せずにEventBridge + SNSでECRイメージスキャン結果のメール通知を行うCDKサンプルコード（TypeScript）を紹介します。

# フィルター範囲
severityはECRイメージスキャンで定義されている重要度レベルを示しており、重要度は以下の通りです。
* UNDEFINED
* CRITICAL
* HIGH
* MEDIUM
* LOW
* INFORMATIONAL

本記事の例では重要度HIGH以上を通知対象としています。

# ファイル構成
ファイル構成は以下です。
* bin/
    * main.ts
* lib/
    * ImageScan/
        * ImageScanStack.ts

# サンプルコード

## bin/main.ts

```typescript:bin/main.ts
#!/usr/bin/env node

import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { Environment } from 'aws-cdk-lib';
import { ImageScanStack } from '../lib/ImageScan/ImageScanStack';

if (process.env['NODE_ENV'] == null || process.env['NODE_ENV'] === '') {
    throw new Error('NODE_ENV is not set');
}
if (process.env['AWS_PROFILE'] == null || process.env['AWS_PROFILE'] === '') {
    throw new Error('AWS_PROFILE is not set');
}

const app = new cdk.App();

const env: Environment = {
    account: '<AWSアカウント番号>',
    region: '<AWSリージョン>',
};

new ImageScanStack(app, {
    env: env,
});
```

## lib/ImageScan/ImageScanStack.ts

```typescript:lib/ImageScan/ImageScanStack.ts
import * as cdk from 'aws-cdk-lib';
import { Rule } from 'aws-cdk-lib/aws-events';
import { Topic } from 'aws-cdk-lib/aws-sns';
import { EmailSubscription } from 'aws-cdk-lib/aws-sns-subscriptions';
import { SnsTopic } from 'aws-cdk-lib/aws-events-targets';
import { Construct } from 'constructs';

/**
 * 構築プロパティ。
 */
export interface ImageScanProps extends cdk.StackProps {}

export class ImageScanStack extends cdk.Stack {
    private readonly _props: ImageScanProps;

    constructor(scope: Construct, props: ImageScanProps) {
        super(scope);

        // SNSアラートトピック作成
        const snsTopic = new Topic(this, 'SNSTopic', {
            displayName: 'ImageScanResultAlert',
            topicName: 'ImageScanResultTopic',
        });
        snsTopic.addSubscription(new EmailSubscription('user@example.com'));

        // イメージスキャン結果通知イベント作成
        const imageScanResultEvent = new Rule(this, 'ImageScanResultEvent', {
            ruleName: 'ImageScanResultEvent',
            description: 'ECR Image Scan Result Alert for ExampleRepositoryName',
            eventPattern: {
                source: ['aws.ecr'],
                detailType: ['ECR Image Scan'],
                detail: {
                    'repository-name': ['ExampleRepositoryName'], // イメージスキャン対象のリポジトリ名
                    'scan-status': ['COMPLETE'],
                    'finding-severity-counts': {
                        $or: [
                            // 重要度HIGH以上のみ通知するようにフィルタリング
                            {
                                CRITICAL: [{ numeric: ['>', 0] }],
                            },
                            {
                                HIGH: [{ numeric: ['>', 0] }],
                            },
                        ],
                    },
                },
            },
        });

        // ターゲット追加
        imageScanResultEvent.addTarget(new SnsTopic(snsTopic));
    }
}
```

# メール通知の発砲
重要度HIGH以上のイメージがプッシュされた際に以下のような内容のメール通知が発砲されます。
```json
{"version":"0","id":"93d88051-2270-f8b7-ad0b-efea5b962e2b","detail-type":"ECR Image Scan","source":"aws.ecr","account":"<AWSアカウント番号>","time":"2023-06-24T02:00:00Z","region":"<AWSリージョン>","resources":["arn:aws:ecr:<AWSリージョン>:<AWSアカウント番号>:repository/ExampleRepositoryName"],"detail":{"scan-status":"COMPLETE","repository-name":"ExampleRepositoryName","image-digest":"sha256:b8a5bc211d11fa35c0074cfc64cb8f783356eeda0f242017e1e7efcd562a38d4","image-tags":["e7e685b","latest"],"finding-severity-counts":{"HIGH":9,"MEDIUM":0,"INFORMATIONAL":44,"LOW":0}}}
```

:::note warn
メールの本文は改行されていないJSONでそのまま出力されるため、必要に応じてEventBridgeの入力トランスフォーマー等で整形して下さい。
:::