Baseline Environment on AWS（BLEA）の詳細を構成図ベースで解説してみる

# Zennに移動しました
https://zenn.dev/wakaka23/articles/8a60cfb3d69032

# 概要
AWS BLEAのStandAlone版で作成されるリソース間の関係を構成図を用いて解説していきます。AWS BLEAってなに？という方は公式の記事をご確認いただければと思います。

https://aws.amazon.com/jp/blogs/news/announcing-baseline-environment-on-aws/

# 全体構成図

BLEAでは以下の構成図に示されたセキュリティサービスにより、監視・通知・ログ保管がなされます。（IAM、KMSは構成図から省略しています）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2746031/89774640-bc4f-5120-5091-7efec479a6a2.png)

詳細を解説するにあたって、説明の都合上、以下のような二つのパートに分けます。
緑の領域が各パートでの該当範囲となります。

**【CloudTrail, CloudWatch編】**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2746031/52608bf5-5607-cabf-39e7-96e9688aa0db.png)
            <br>
**【Config, Security Hub, GuardDuty, Health Dashboard編】**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2746031/44546e5d-b2f8-7a2b-0917-b3a135c323af.png)

# CloudTrail, CloudWatch編

AWS APIコールを監視する部分にフォーカスして詳細を確認していきます。
CloudTrailで管理しているAPIコールログを監視する方法として主に以下の二つがあります。
- CloudTrail→EventBridge→SNS
- CloudTrail→CloudWatch Logs→CloudWatch Alarm→SNS

後者を使用するとCloudWatch Logsを経由する分の余計な費用や遅延が発生してしまうため、BLEAでは基本的には前者を使用しています。しかし、IAMイベントはus-east-1で発生するため、BLEAをデプロイしたリージョンのEventBridgeではIAMイベントを拾ってくることができません。(us-east-1にBLEAをデプロイするのであれば問題はないですが...)
そこで、IAMイベントを監視するために、APIコールログをCloudWatch Logsに配信するという後者の設計も同時に採用されています。

## 構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2746031/0b7d91f9-9100-bde3-b36f-b3c36e6b38d3.png)

## CloudTrail

CloudTrail証跡が作成され、CloudWatch Logsへの配信も有効化されています。また、ログファイルのSSE-KMS暗号化も有効となっており、カスタマー管理CMKに適用するキーポリシーも定義されています。
```typescript
new trail.Trail(this, 'CloudTrail', {
  bucket: cloudTrailBucket,
  enableFileValidation: true,
  includeGlobalServiceEvents: true,
  cloudWatchLogGroup: cloudTrailLogGroup,
  encryptionKey: cloudTrailKey,
  sendToCloudWatchLogs: true,
});
```

## S3
APIコール監視の文脈では以下に示す二つのS3バケットが作成されます。また、これらのS3バケットに適用するバケットポリシーも定義されます。

- ```CloudTrailLogBucket```
証跡ログを格納する用のS3バケットです。

    ```typescript
    const cloudTrailBucket = new s3.Bucket(this, 'CloudTrailBucket', {
      accessControl: s3.BucketAccessControl.PRIVATE,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      versioned: true,
      serverAccessLogsBucket: cloudTrailAccessLogBucket,
      serverAccessLogsPrefix: 'cloudtraillogs',
      removalPolicy: cdk.RemovalPolicy.RETAIN,
      enforceSSL: true,
    });
    ```
    <br>

- ```CloudTrailAccessLogBucket```
```CloudTrailLogBucket```のアクセスログ取得用のS3バケットです。オブジェクトを90日後にS3 Glacier Flexible Retrievalに移行させ、2555日後に削除するライフサイクルルールが適用されています。

    ```typescript
    const cloudTrailAccessLogBucket = new s3.Bucket(this, 'CloudTrailAccessLogBucket', {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      versioned: true,
      encryption: s3.BucketEncryption.S3_MANAGED,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
      enforceSSL: true,
      lifecycleRules: [
        {
          enabled: true,
          expiration: cdk.Duration.days(2555),
          transitions: [
            {
              transitionAfter: cdk.Duration.days(90),
              storageClass: s3.StorageClass.GLACIER,
            },
          ],
        },
      ],
    });
    ```

## EventBridge

前述の通り、BLEAではIAMイベント以外はEventBridgeを用いて監視する設計となっています。そのために、以下に示す三つのEventBridgeルールが作成されます。全てのルールで同一のSNSトピックをターゲットとして指定し、メール通知・Slack通知を実現しています。

- ```SgChangeEventRule```
SGに対するインバウンドルールの追加・削除、アウトバウンドルールの追加・削除が発生した際に通知するルールです。

    ```typescript
    new cwe.Rule(this, 'SgChangedEventRule', {
      description: 'Notify to create, update or delete a Security Group.',
      enabled: true,
      eventPattern: {
        source: ['aws.ec2'],
        detailType: ['AWS API Call via CloudTrail'],
        detail: {
          eventSource: ['ec2.amazonaws.com'],
          eventName: [
            'AuthorizeSecurityGroupIngress',
            'AuthorizeSecurityGroupEgress',
            'RevokeSecurityGroupIngress',
            'RevokeSecurityGroupEgress',
          ],
        },
      },
      targets: [new cwet.SnsTopic(topic)],
    });
    ```
    <br>

- ```NetworkAclChangeEventRule```
NACLの作成・削除、NACLエントリの追加・削除・置換、NACLを関連付けるサブネットの変更が発生した際に通知するルールです。

    ```typescript
    new cwe.Rule(this, 'NetworkAclChangeEventRule', {
      description: 'Notify to create, update or delete a Network ACL.',
      enabled: true,
      eventPattern: {
        source: ['aws.ec2'],
        detailType: ['AWS API Call via CloudTrail'],
        detail: {
          eventSource: ['ec2.amazonaws.com'],
          eventName: [
            'CreateNetworkAcl',
            'CreateNetworkAclEntry',
            'DeleteNetworkAcl',
            'DeleteNetworkAclEntry',
            'ReplaceNetworkAclEntry',
            'ReplaceNetworkAclAssociation',
          ],
        },
      },
      targets: [new cwet.SnsTopic(topic)],
    });
    ```
    <br>

- ```CloudTrailChangeEventRule```
ログ記録の停止、CloudTrailの設定変更、CloudTrail証跡の削除が発生した際に通知するルールです。

    ```typescript
    new cwe.Rule(this, 'CloudTrailChangeEventRule', {
      description: 'Notify to change on CloudTrail log configuration',
      enabled: true,
      eventPattern: {
        detailType: ['AWS API Call via CloudTrail'],
        detail: {
          eventSource: ['cloudtrail.amazonaws.com'],
          eventName: ['StopLogging', 'DeleteTrail', 'UpdateTrail'],
        },
      },
      targets: [new cwet.SnsTopic(topic)],
    });
    ```

## CloudWatch Logs (Metrics Filter)

APIコールログをCloudWatch Logsロググループに配信し、メトリクスフィルタの適用・CloudWatch Alarmの設定を実施することでIAMイベントを監視します。具体的には、以下に示すような四つのメトリクスフィルタに合致するイベントを検出した場合に通知をおこないます。（CloudWatch Alarmは基本的に一回でもメトリクスフィルタに合致するイベントが発生した時点でアラーム発報するというシンプルな設定のため、詳細は割愛します）

- ```IAMPolicyChangeFilter```
インラインポリシの追加・削除、IAMポリシの作成・削除、IAMポリシのアタッチ・デタッチ、IAMポリシの新しいバージョンの作成・削除が発生した際に、該当のメトリクスに値を発行するメトリクスフィルタです。

    ```typescript
    const mfIAMPolicyChange = new cwl.MetricFilter(this, 'IAMPolicyChangeFilter', {
      logGroup: cloudTrailLogGroup,
      filterPattern: {
        logPatternString:
          '{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}',
      },
      metricNamespace: 'CloudTrailMetrics',
      metricName: 'IAMPolicyEventCount',
      metricValue: '1',
    });
    ```
    <br>

- ```UnauthorizedAttemptsFilter```
AWS環境でのアクセス拒否エラーが発生した際に、該当のメトリクスに値を発行するメトリクスフィルタです。

    ```typescript
    const unauthorizedAttemptsFilter = new cwl.MetricFilter(this, 'UnauthorizedAttemptsFilter', {
      logGroup: cloudTrailLogGroup,
      filterPattern: {
        // Exclude calls “Decrypt" event by config.amazonaws.com to ignore innocuous errors caused by AWS Config.
        // That error occurs if you have KMS (CMK) encrypted environment variables in Lambda function.
        logPatternString:
          '{($.errorCode = "*UnauthorizedOperation" || $.errorCode = "AccessDenied*") && ($.eventName != "Decrypt" || $.userIdentity.invokedBy != "config.amazonaws.com" )}',
      },
      metricNamespace: 'CloudTrailMetrics',
      metricName: 'UnauthorizedAttemptsEventCount',
      metricValue: '1',
    });
    ```
    <br>

- ```NewAccessKeyCreatedFilter```
IAMユーザのアクセスキーが作成された際に、該当のメトリクスに値を発行するメトリクスフィルタです。

    ```typescript
    const newAccessKeyCreatedFilter = new cwl.MetricFilter(this, 'NewAccessKeyCreatedFilter', {
      logGroup: cloudTrailLogGroup,
      filterPattern: {
        logPatternString: '{($.eventName=CreateAccessKey)}',
      },
      metricNamespace: 'CloudTrailMetrics',
      metricName: 'NewAccessKeyCreatedEventCount',
      metricValue: '1',
    });
    ```
    <br>

- ```RootUserActivityFilter```
Rootユーザによる操作が行われた際に、該当のメトリクスに値を発行するメトリクスフィルタです。

    ```typescript
    const rootUserActivityFilter = new cwl.MetricFilter(this, 'RootUserActivityFilter', {
      logGroup: cloudTrailLogGroup,
      filterPattern: {
        logPatternString:
          '{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}',
      },
      metricNamespace: 'CloudTrailMetrics',
      metricName: 'RootUserPolicyEventCount',
      metricValue: '1',
    });
    ```

# Config, Security Hub, GuardDuty, Health Dashboard編

Config、Securityhub、GuardDuty、Health Dashboardによる評価をもとに、AWS環境を監視する部分の詳細を確認していきます。

## 構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2746031/ca3917c8-c6ad-7465-327f-0ed3a6bc6581.png)

## Config

設定レコーダ（```ConfigRecorder```）とConfigに適用するIAMロールを定義することで、AWSリソースの設定を継続的に記録・評価します。Configにより管理されるリソース追跡ログは```ConfigBucket```に配信されます。

- ```ConfigRecorder```
このリージョンで現在および将来サポートされるすべてのリソースタイプに加えて、IAMなどのAWSグローバルサービスも対象としています。

    ```typescript
    new config.CfnConfigurationRecorder(this, 'ConfigRecorder', {
      roleArn: configRole.roleArn,
      recordingGroup: {
        allSupported: true,
        includeGlobalResourceTypes: true,
      },
    });
    ```
    <br>

- ```ConfigBucket```

    ```typescript
    const configBucket = new s3.Bucket(this, 'ConfigBucket', {
      accessControl: s3.BucketAccessControl.PRIVATE,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      versioned: true,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
      encryption: s3.BucketEncryption.S3_MANAGED,
      enforceSSL: true,
    });
    ```
    <br>

Configルールとして、デフォルトSGに一つ以上のインバウンドルール・アウトバウンドルールが存在する場合に非準拠となる```DefaultSgClosedRule```が作成されます。

- ```DefaultSgClosedRule```
```vpc-default-security-group-closed```というAWS マネージド型のConfigルールが選択されています。

    ```typescript
    const defaultSgClosedRule = new config.ManagedRule(this, 'DefaultSgClosedRule', {
      identifier: config.ManagedRuleIdentifiers.VPC_DEFAULT_SECURITY_GROUP_CLOSED,
      ruleScope: config.RuleScope.fromResources([config.ResourceType.EC2_SECURITY_GROUP]),
      configRuleName: 'bb-default-security-group-closed',
      description:
        'Checks that the default security group of any VPC does not allow inbound or outbound traffic. The rule is non-compliant if the default security group has one or more inbound or outbound traffic.',
    });
    ```
    <br>

上記Configルールが非準拠になると、以下に示す```DefaultSgClosedEventRule```というEventBridgeルールにより通知します。

- ```DefaultSgClosedEventRule```

    ```typescript
    new cwe.Rule(this, 'DefaultSgClosedEventRule', {
      description: 'CloudWatch Event Rule to send notification on Config Rule compliance changes.',
      enabled: true,
      eventPattern: {
        source: ['aws.config'],
        detailType: ['Config Rules Compliance Change'],
        detail: {
          configRuleName: ['bb-default-security-group-closed'],
          newEvaluationResult: {
            complianceType: ['NON_COMPLIANT'],
          },
        },
      },
      targets: [new cwet.SnsTopic(topic)],
    });
    ```
    <br>

また、非準拠となった場合の修復アクションとして```DefaultSgRemediation```が作成されています。この修復アクションにより、デフォルトSGに一つ以上のインバウンドルール・アウトバウンドルールが存在する場合に、デフォルトSGの全てのルールが削除されます。

- ```DefaultSgRemediation```
```AWSConfigRemediation-RemoveVPCDefaultSecurityGroupRules```というSSMドキュメントが選択されており、SSM Automationにより自動的に修復されます。

    ```typescript
    new config.CfnRemediationConfiguration(this, 'DefaultSgRemediation', {
      configRuleName: defaultSgClosedRule.configRuleName,
      targetType: 'SSM_DOCUMENT',
      targetId: 'AWSConfigRemediation-RemoveVPCDefaultSecurityGroupRules',
      targetVersion: '1',
      parameters: {
        AutomationAssumeRole: {
          StaticValue: {
            Values: [defaultSgRemediationRole.roleArn],
          },
        },
        GroupId: {
          ResourceValue: {
            Value: 'RESOURCE_ID',
          },
        },
      },
      automatic: true,
      maximumAutomaticAttempts: 5,
      retryAttemptSeconds: 60,
    });
    ```

## Security Hub
「AWS基礎セキュリティのベストプラクティス」と「CIS AWS Foundations Benchmark」という二つのセキュリティ基準からの逸脱検知をおこないます。以下のEventBridgeルールが定義されており、逸脱を検知した場合に通知します。

- ```SecurityHubEventRule```
「重要度」が 'CRITICAL' か 'HIGH' 、「コンプライアンスのステータス」が 'FAILED' 、「ワークフローのステータス」が 'NEW' か 'NOTIFIED'、「レコードの状態」が 'ACTIVE' の場合に通知します。つまり、基準に非準拠であり、重要度が高いもののうち、レビューが実施されていないものが存在する場合に通知します。

    ```typescript
    new cwe.Rule(this, 'SecurityHubEventRule', {
      description: 'CloudWatch Event Rule to send notification on SecurityHub all new findings and all updates.',
      enabled: true,
      eventPattern: {
        source: ['aws.securityhub'],
        detailType: ['Security Hub Findings - Imported'],
        detail: {
          findings: {
            Severity: {
              Label: ['CRITICAL', 'HIGH'],
            },
            Compliance: {
              Status: ['FAILED'],
            },
            Workflow: {
              Status: ['NEW', 'NOTIFIED'],
            },
            RecordState: ['ACTIVE'],
          },
        },
      },
      targets: [new cwet.SnsTopic(topic)],
    });
    ```

## GuardDuty

GuardDutyを有効化することで、脅威検出・異常な振る舞い検知をおこないます。以下のようなEventBridgeルールが定義されており、重要度が「高」「中」（Severityが4.0~8.9）の場合に通知します。

- ```GuardDutyEventRule```

    ```typescript
    new cwe.Rule(this, 'GuardDutyEventRule', {
      description: 'CloudWatch Event Rule to send notification on GuardDuty findings.',
      enabled: true,
      eventPattern: {
        source: ['aws.guardduty'],
        detailType: ['GuardDuty Finding'],
        detail: {
          severity: [
            4, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6,
            6.0, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7, 7.0, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8,
            8.0, 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9,
          ],
        },
      },
      targets: [new cwet.SnsTopic(topic)],
    });
    ```

## Health Dashboard

以下に示すEventBridgeルールにより、全てのAWS Healthイベントが通知されます。

- ```AwsHealthEventRule```

    ```typescript
    new cwe.Rule(this, 'AwsHealthEventRule', {
      description: 'Notify AWS Health event',
      enabled: true,
      eventPattern: {
        source: ['aws.health'],
        detailType: ['AWS Health Event'],
      },
      targets: [new cwet.SnsTopic(topic)],
    });
    ```

# 最後に
BLEA最高！