SecurityHubでAWSリソースをセキュアにしてみた

# はじめに  
今回はSecurity Hubを使って、AWS環境のセキュリティ状態を可視化して、どんな感じでセキュアにできるのかを試したので、その内容を共有します。
## この記事でわかること  
- Security Hubがどんなサービスなのかがわかる
- Guard Dutyやinspectorも含めたセキュリティサービスの全体像が理解できる
- 実際にSecurity Hubを導入する際の手順がわかる 

# やったこと  
### 開発中のアプリにSecurity Hub,GuardDuty,Inspectorを実行し状態の可視化  
開発環境のAWSアカウントにおいて、以下のセキュリティサービスを有効化しました。
- **AWS Security Hub**: セキュリティ状態の統合管理 
- **Amazon GuardDuty**: 脅威検出サービス 
- **Amazon Inspector**: 脆弱性評価サービス

## SecurityHubとは
AWS Security Hubは、アカウント単位でAWS環境全体のセキュリティ状態を一元的に把握・管理するためのマネージドサービスです。複数のセキュリティサービスやツールからの情報を集約し、統一された形式で可視化することで、効率的な分析と迅速な対応を可能にします。

### 主な機能
1. セキュリティ標準の適用：CIS AWS FoundationsやAWSのベストプラクティスに基づくセキュリティチェックを自動で実行
1. 検出結果の集約：GuardDuty、Inspector、IAM Access AnalyzerなどのAWSサービス、さらにはCrowdStrike、Palo Alto Networksといった外部セキュリティ製品とも連携可能
1. 統合ビュー：すべての検出結果を統一された形式（AWS Security Finding Format：ASFF）で表示し、重要度に応じた優先対応が可能
1. 自動化：EventBridgeを使ったインシデント対応の自動化が容易。例えば、重大な脆弱性を検出したら自動でSNS通知を送信する、といったワークフローを構築可能

## GuardDutyとは
Amazon GuardDutyは、AWSが提供する脅威検出サービスで、AWSアカウントやワークロードに対する悪意のある活動や不正な挙動を継続的に監視・検出します。マネージド型のサービスで、エージェントのインストールは不要です。

### 主な機能
- 脅威インテリジェンスを活用した検出
AWSやサードパーティ（Proofpoint、CrowdStrikeなど）の脅威インテリジェンスを元に、異常な挙動をリアルタイムに分析します。

- データソースの自動分析
VPC Flow Logs、CloudTrail、DNSログなどを元に、自動で脅威を検出します。

- 検出例
    - ブルートフォース攻撃の兆候
    - C&Cサーバーとの通信
    - 通常と異なるリージョンからのAPI呼び出し
    - 暗号通貨のマイニング活動

## Inspectorとは
Amazon Inspectorは、EC2やコンテナイメージに対して脆弱性スキャンを実行するマネージドサービスです。ソフトウェアの依存ライブラリやOSレベルの設定に潜むCVE（共通脆弱性識別子）を自動検出します。

### 主な機能
- **EC2およびECRイメージのスキャン**
EC2インスタンスのOSやパッケージ、Amazon ECR内のコンテナイメージを対象に脆弱性評価を実施します。

- **継続的スキャン**
新たにインスタンスが起動されたり、ECRイメージがプッシュされるたびに、自動でスキャンが行われます。

- **CVEデータベースとの連携**
常に最新の脆弱性情報（CVEs）に基づいてスキャンを行い、深刻度（CVSSスコア）も併せて提示します。


## 有効化手順
有効化する方法は以下の2通り
- マネジメントコンソールで有効化
- CDKで有効化
### AWS SecurityHub
#### マネジメントコンソール
今回はCDKで実装したため、マネジメントコンソールで実装したい方は以下の記事を参考にしてください。
（マネジメントコンソール上の操作としては有効化のボタンをクリックするだけになると思います）
https://qiita.com/sugimount-a/items/69726f6600591f437f3f
#### CDK
SecurityHubはAWS Configの有効化が必須
AWS Security HubでAWS Configが必須となる理由は、Security Hubがリソースの状態や設定を正確に把握し、セキュリティ標準に基づく評価（セキュリティチェック）を行うために、Configの記録が必要不可欠だからです。

- Config
    ```
    import * as cdk from 'aws-cdk-lib';
    import { Construct } from 'constructs';
    import * as s3 from 'aws-cdk-lib/aws-s3';
    import * as iam from 'aws-cdk-lib/aws-iam';
    import * as config from 'aws-cdk-lib/aws-config';
    
    export class AwsConfigConstruct extends cdk.Stack {
      public readonly bucket: s3.Bucket;
      constructor(scope: Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
       
        const configBucket = new cdk.aws_s3.Bucket(this, "configBucket", {
          blockPublicAccess: cdk.aws_s3.BlockPublicAccess.BLOCK_ALL,
          encryption: cdk.aws_s3.BucketEncryption.S3_MANAGED,
          enforceSSL: true,
          versioned: false,
          removalPolicy: cdk.RemovalPolicy.DESTROY,
          autoDeleteObjects: true,
        });
    
        // Add bucket policy
        const bucketPolicy = new iam.PolicyStatement({
          sid: "AWSConfigBucketPermissionsCheck",
          effect: iam.Effect.ALLOW,
          principals: [new iam.ServicePrincipal("config.amazonaws.com")],
          actions: ["s3:GetBucketAcl"],
          resources: [configBucket.bucketArn],
          conditions: {
            StringEquals: {
              "AWS:SourceAccount": AWS_ACCOUNT_ID, // Using the account ID of the stack
            },
          },
        });
    
        const listBucketPolicy = new iam.PolicyStatement({
          sid: "AWSConfigBucketExistenceCheck",
          effect: iam.Effect.ALLOW,
          principals: [new iam.ServicePrincipal("config.amazonaws.com")],
          actions: ["s3:ListBucket"],
          resources: [configBucket.bucketArn],
          conditions: {
            StringEquals: {
              "AWS:SourceAccount": AWS_ACCOUNT_ID,
            },
          },
        });
    
        const putObjectPolicy = new iam.PolicyStatement({
          sid: "AWSConfigBucketDelivery",
          effect: iam.Effect.ALLOW,
          principals: [new iam.ServicePrincipal("config.amazonaws.com")],
          actions: ["s3:PutObject"],
          resources: [`${configBucket.bucketArn}/AWSLogs/${process.env.AWS_ACCOUNT_ID}/Config/*`],
          conditions: {
            StringEquals: {
              "s3:x-amz-acl": "bucket-owner-full-control",
              "AWS:SourceAccount": AWS_ACCOUNT_ID,
            },
          },
        });
    
        // Add the policy statements to the bucket
        configBucket.addToResourcePolicy(bucketPolicy);
        configBucket.addToResourcePolicy(listBucketPolicy);
        configBucket.addToResourcePolicy(putObjectPolicy);
    
        const configRole = new iam.CfnServiceLinkedRole(this, 'ConfigServiceLinkedRole', {
          awsServiceName: 'config.amazonaws.com',
        });
    
        const configRecorder = new config.CfnConfigurationRecorder(
          this,
          "ConfigRecorder",
          {
            name: "CloudFrontVpcOriginConfigRecorder",
            roleArn: `arn:aws:iam::${AWS_ACCOUNT_ID}:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig`,
            recordingGroup: {
              allSupported: true,
              includeGlobalResourceTypes: true,
            },
            recordingMode: {
              recordingFrequency: "CONTINUOUS",
            },
          }
        );
    
        const configDeliveryChannel = new config.CfnDeliveryChannel(
          this,
          "ConfigDeliveryChannel",
          {
            name: "CloudFrontVpcOriginConfigDeliveryChannel",
            s3BucketName: configBucket.bucketName, // S3バケット名を指定
            configSnapshotDeliveryProperties: {
              deliveryFrequency: "TwentyFour_Hours",
            },
          }
        );
        configDeliveryChannel.node.addDependency(configBucket);
        configDeliveryChannel.node.addDependency(configRole);
        configRecorder.node.addDependency(configRole);
        configRecorder.node.addDependency(configBucket);
      }
    }
    ```

- SecurityHub
    ```
    import * as cdk from 'aws-cdk-lib';
    import { Construct } from 'constructs';
    
    
    export class SecurityHubStack extends cdk.Stack {
      constructor(scope: Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
    
        // Security Hubの有効化
        const hub = new cdk.aws_securityhub.CfnHub(this, 'SecurityHub', {
            enableDefaultStandards: false, // デフォルトのセキュリティ基準を無効化
            autoEnableControls: true, // 自動的にコントロールを有効化しない
        });
    
        // 東京リージョンでAWS Foundational Security Best Practices標準の有効化
        const awsFsbp = new cdk.aws_securityhub.CfnStandard(this, 'AWSFoundationalSecurityBestPractices', {
            standardsArn: `arn:aws:securityhub:ap-northeast-1::standards/aws-foundational-security-best-practices/v/1.0.0`,
        });
    
        awsFsbp.addDependency(hub);
    
      }
    }
    ```

### AWS GuardDuty
#### マネジメントコンソール
GuardDutyにアクセスして有効化ボタンを押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3971030/f88d846e-1fc8-4035-a44d-fc1434c8dc13.png)

#### CDK
以下のコードをSecurityHubのCDKコードに追加する
```
// GuardDutyの有効化と各種機能の有効化
    const guardDuty = new cdk.aws_guardduty.CfnDetector(this, 'GuardDuty', {
      enable: true,
      features: [
      { name: 'S3_DATA_EVENTS', status: 'ENABLED' }, // S3 Protection
      { name: 'EKS_AUDIT_LOGS', status: 'DISABLED' }, // EKS 監査ログのモニタリング設定（無効）
      { name: 'RUNTIME_MONITORING', status: 'ENABLED', additionalConfiguration: [ { name: 'EC2_AGENT_MANAGEMENT', status: 'ENABLED' } ] }, // ランタイムモニタリング（EC2自動エージェント設定有効）
      { name: 'EBS_MALWARE_PROTECTION', status: 'ENABLED' }, // EC2 の Malware Protection
      { name: 'RDS_LOGIN_EVENTS', status: 'DISABLED' }, // RDS Protection（無効）
      { name: 'LAMBDA_NETWORK_LOGS', status: 'ENABLED' }, // Lambda 保護
      ],
    });
```

### AWS Inspector
#### マネジメントコンソール
Inspectorにアクセスしてアクティブ化ボタンを押す（タイミング悪くてアクティブ中のスクショになった）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3971030/3163d4df-add5-43af-bf58-01c59aaa93f4.png)

#### CDK
有効化するにあたって対応するCloudFormationリソースが無いためマネジメントコンソールでの実行を推奨
カスタムリソースを使ってCDKでの実装は可能
参考
https://dev.classmethod.jp/articles/aws-cdk-custom-resource-inspector-enabler/


## Security Hubの警告をSlackに通知する
警告が出たときにSlackやメールに通知し、重要度の高い警告を感知し対応検討できるようにしました
以下の記事を参考に作成しました
https://dev.classmethod.jp/articles/security-hub-findings-notification-eventbridge-rules-input-transformer-aws-cdk/

この記事の通りに作成した場合、後述する抑制設定された検知結果を通知してしまうので設定を追加しました。

```
    // EventBridge ルールを作成
    const eventRule = new events.Rule(this, 'EventRule', {
      eventPattern: {
        source: ['aws.securityhub'],
        /**
         * 全ての Security Hub Findings を対象にする場合は以下のように設定
         * @see https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-cwe-integration-types.html#securityhub-cwe-integration-types-all-findings
         */
        detailType: ['Security Hub Findings - Imported'],
        detail: {
          findings: {
            Severity: {
              Label: ['CRITICAL', 'HIGH'],
            },
            // 抑制対象を除外する設定を追加
            Workflow: {
              Status: ['NEW', 'NOTIFIED', 'RESOLVED'], // SUPPRESSED を除外
            },
          },
        },
      },
    });
```

Security Hubで検出された各種セキュリティ課題に対して、優先度に応じた対応を実施しました。 
## 難しかったこと  
### IaCコードを修正するのかしないのか振り分けが難しかった
AWS リソースをIaCで管理している場合、表示されるエラー内容が具体的でないためIaCのコードで対処可能かどうかの判断がつけにくかった
Security Hubで検出される課題の中には、以下のような判断が必要なものがありました：  
- **修正が必要な課題**: セキュリティリスクが高く、実運用に影響する可能性があるもの 
- **修正不要な課題**: 開発環境特有の設定や、ビジネス要件上必要な設定  特に、エラーメッセージが抽象的で、具体的な修正方法や影響範囲が分からないケースが多く、AWS公式ドキュメントとの照合が必要でした。

## 具体的な実施例 
### 修正必要な課題
- [EC2.18] セキュリティグループは承認されたポートのみ無制限インバウンドトラフィックを許可すべきです
- 対応例
    - トラフィックの制限をする
        - IPを制限する
        - ポートを制限する（80,443のみ有効）
CDK
修正前
```
    securityGroup.addIngressRule(
        ec2.Peer.anyIpv4(),
        ec2.Port.tcp(9000),
        'Allow HTTP access port 9000'
    );
```
修正後(CloudFrontのプレフィクスリストを使用)
```
    ec2securityGroup.addIngressRule(
        ec2.Peer.prefixList(cloudfrontPrefixList.prefixListId),
        ec2.Port.tcp(9000),
        'Allow CloudFront to connect to EC2 origin'
    )
```

参考
https://docs.aws.amazon.com/securityhub/latest/userguide/ec2-controls.html#ec2-18



### 修正不要な課題
// [GuardDuty.5] GuardDuty EKS監査ログモニタリングを有効にする必要があります
修正不要な理由
EKSを利用することがないため不要と判断しました
対処方法
SecurityHubの対象の警告で、抑制したいリソースに対してワークフローのステータスを「抑制済み」にすることで検知対象から除外することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3971030/3f14cc3a-995c-4b48-ba16-787e282ad664.png)


# わかったこと  
### 公式ドキュメントがわかりづらい
AWSの公式ドキュメントは情報量は多いものの、実際の運用に必要な具体的な手順や設定例が不足しており、雑な印象を受けました。特にSecurity Hubの詳細設定やトラブルシューティングに関する情報が断片的で、実装時に苦労することが多かったです。  
### 1日1回実行される  
Security Hubの評価は定期的に実行され、新しいリソースの追加や設定変更を自動的に検知してくれます。

# おわりに  
## 今回セキュリティ状態の可視化ができた  
Security Hubを導入することで、AWSアカウント全体のセキュリティ状態を一元的に把握できるようになりました。
## 
## アプリ開発の最後でセキュリティチェックするのではなく継続監視的なチェックするのがよい
従来の開発プロセスでは、アプリケーション開発の最終段階でセキュリティチェックを行うことが多かったですが、Security Hubを活用し対応することで以下が実現できセキュアな状態が維持できるようになりました。
- **継続的な監視**: リソースの作成・変更時にリアルタイムでセキュリティ状態をチェック 
- **早期発見**: 問題の早期発見により、修正コストを削減 
- **運用負荷軽減**: 自動化されたチェックにより、手動でのセキュリティ監査作業を削減  これにより、セキュリティを「後付け」ではなく、開発プロセスに組み込んだ「セキュリティ・バイ・デザイン」の実現が可能になります。 
## 参考資料  
- [AWS Security Hub ユーザーガイド](https://docs.aws.amazon.com/securityhub/)
- [Amazon GuardDuty ユーザーガイド](https://docs.aws.amazon.com/guardduty/) 
- [Amazon Inspector ユーザーガイド](https://docs.aws.amazon.com/inspector/)