APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

【AWS】GitHubへのPushをトリガーにBLEA(CDKリソース)を複数アカウントに一括デプロイする

はじめに

こんにちは、エーピーコミュニケーションズ クラウド事業部の髙野です。
AWSのセキュリティベースラインを素早く構築できるCDKテンプレートであるBaseline Environment on AWS(BLEA)を複数のアカウントやリージョンに効率的にデプロイする方法を検討している際に、CDK PipelinesというCDKのコンストラクトライブラリを知りました。

このCDK Pipelinesを使用してCI/CDパイプラインを作成することで、GitHubなどにソースコードの修正をPushしたことをトリガーにBLEAを複数の環境に自動でデプロイできるようになります。

この記事では、CDK Pipelinesがどんな仕組みなのか、どうやってパイプラインを組むのかを、私が実際に試してみた手順も交えながらご紹介します。少しでも興味がある方はぜひ読んでみてください!

目次

とくに読んでほしい人

  • 複数アカウントまたはリージョンへのBLEA(CDKリソース)のデプロイを効率的に実施する方法を検討している
  • CDKによるCI/CDの実装方法のサンプルを見てみたい
  • CDK Pipelinesの動作イメージをつかみたい

本記事に記載しないこと

本記事ではBLEAの細かい仕様や設定内容、手動でのデプロイ方法については記載しません。BLEAについては以下のブログで解説しているので、気になる方は見てみてください。

techblog.ap-com.co.jp

CDK Pipelinesとは

CDK Pipelinesは、CDKアプリケーションの継続的デリバリー (CI/CD) パイプラインを構築するための高レベルコンストラクトライブラリです。CodeCommit、CodeBuild、CodePipelineなどのAWSサービスを統合し、CDKアプリケーションのビルド、テスト、デプロイを自動化できます。

詳細については、以下のドキュメントを参照してください。

docs.aws.amazon.com

パイプライン構成

BLEAはスタンドアロン版とマルチアカウント版が提供されていますが、本記事ではマルチアカウント版のBLEAをデプロイします。

  • スタンドアロン版:baseline-environment-on-aws/usecases/blea-gov-base-standalone
  • マルチアカウント版:baseline-environment-on-aws/usecases/blea-gov-base-ct

BLEA自体のデプロイはパイプラインが行い、自分でデプロイするのはパイプラインです。以下が今回構築するパイプラインの構成です。

構築手順

構築する環境の概要

  • マルチアカウント版BLEAをパイプラインでデプロイする
  • パイプラインはアカウント1us-east-1にデプロイする
  • BLEAはパイプラインから以下の環境にデプロイする
    • アカウント1(111111111111)us-east-1
    • アカウント2(222222222222)us-east-2
  • パイプラインのパラメーターはdevPipelineParameter、BLEAのパラメーターはAccount1US1parameterおよびAccount2US2parameterとする

前提条件

  • デプロイ対象の環境にすでにBLEAがデプロイされていないこと。

  • 以下の手順を参考に開発環境を用意できていること。
    BLEAデプロイ手順 > 前提条件

  • 以下の手順を参考にリポジトリの取得とプロジェクトの初期化が完了していること。
    BLEAデプロイ手順 > 1. リポジトリの取得とプロジェクトの初期化

  • アカウント1およびアカウント2にAdministrator権限でSSOできること。

  • BLEAをデプロイする対象のアカウントおよびリージョンでConfigが有効になっていること。
    ※ControlTowerで管理しているアカウント、リージョンでは自動で有効化されている。

  • AWS Chatbotの事前準備として、以下の手順を参考にSlack workspacesの設定が完了していること。
    BLEAデプロイ手順 > AWS Chatbot 用に Slack を設定する

  • BLEAのコードを登録するためのGitHubリポジトリを用意できていること。
    ※パイプラインがデプロイする際に必要となるデプロイ先アカウントへの接続情報を、パイプラインのパラメータファイルに記載する必要があります。当該情報を管理するGitHubリポジトリはセキュリティのためプライベートリポジトリとすることを推奨します。aws-samples/baseline-environment-on-awsのリポジトリを直接Forkするとプライベートリポジトリとして管理することができないので、注意してください。公開されている本リポジトリをCloneしてPushすることでPrivateなリポジトリを作成できます。

作業の流れ

  1. GitHubリポジトリにBLEAのコードを登録する
  2. AWS CodeConnectionsを使用してGitHubを接続する
  3. パラメーターを設定する
  4. アカウント2のブートストラップを実施する
  5. アカウント1のブートストラップを実施する
  6. アカウント1にパイプラインをデプロイする
  7. コンソール画面で実行結果を確認する
  8. BLEAコードを更新し変更をPushして、変更デプロイを実行する
  9. コンソール画面で実行結果を確認する

1.GitHubリポジトリにBLEAのコードを登録する

任意の方法でGitHubリポジトリにBLEAのコードを登録してください。
パイプラインはGitHubリポジトリを監視し、対象ブランチが更新されたらパイプラインをキックします。

2.AWS CodeConnectionsを使用してGitHubを接続する

AWS CodeConnections(旧称 AWS CodeStar Connections)を使用して、パイプラインが対象GitHubリポジトリにアクセスするためのConnectionを作成します。

AWS CodeConnectionsは、GitHub、GitLab、BitbucketなどのサードパーティのGitベースのソースプロバイダーと統合して、AWS CodePipelineなどのAWSサービスがリポジトリイベントに関する通知を受け取ったり、ソースコードをダウンロードしてコードをビルド、テスト、デプロイしたりできるようにするサービスです。

2-1. アカウント1のAWSマネジメントコンソールにログインします。

2-2. [CodePipeline] サービスを開きます。

2-3. ナビゲーションペインの左下にある [設定]=>[接続] をクリックし、[接続を作成] をクリックします。

2-4. [GitHub] を選択して、[接続名] を指定し、 [GitHubに接続する] をクリックします。

"2-5. AWS Connector for GitHub"をインストールするため、[新しいアプリをインストールする] をクリックします。

2-6. [Install & Authorize AWS Connector for GitHub] の画面で、自身のリポジトリを選択し、[Install & Authorize] をクリックします。この後画面がマネジメントコンソールに戻ります。

2-7. [GitHubに接続する] のページで、 [接続] をクリックします。

2-8. 以上で Connection の ARN が画面に表示されます。形式は次のとおりです。 arn:aws:codestar-connections:us-east-1:xxxxxxxxxxxx:connection/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 後に使用するため、これをコピーします

3. パラメーターを設定する

複数の環境へのデプロイを実施するために、各種コードを修正して設定を追加します。

  • usecase/blea-gov-base-ct/parameter.tsを修正

devPipelineParameterを修正して以下のように設定します

export const devPipelineParameter: PipelineParameter = {
  env: { account: '111111111111', region: 'us-east-1' }, // アカウント1のID
  envName: 'DevPipeline', // 任意の環境名
  sourceRepository: 'apc-s-takano/takano-baseline-environment-on-aws', // GitHubリポジトリの名前
  sourceBranch: 'main', // パイプラインが参照するブランチ名
  sourceConnectionArn: 'arn:aws:codestar-connections:us-east-1:xxxxxxxxxxxx:connection/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', // 先のセクションで取得したConnectionのARN
};

今回デプロイする環境のパラメーターを追記します

// アカウント1のus-east-1にBLEAをデプロイするためのパラメーター
export const Account1US1parameter: AppParameter = {
  envName: 'Account1US1', // 任意の環境名
  securityNotifyEmail: 'notify-security@example.com', // 通知メール送信先のアドレス
  securitySlackWorkspaceId: 'XXXXXXXXX', // 事前に設定したSlackワークスペースのID
  securitySlackChannelId: 'XXXXXXXXXXX', // 事前に設定したSlackチャンネルのID
  env: { account: 'XXXXXXXXXXXX', region: 'us-east-1' }, // デプロイ対象のアカウントおよびリージョン
};

// アカウント2のus-east-2にBLEAをデプロイするためのパラメーター
export const Account2US2parameter: AppParameter = {
  envName: 'Account2US2',
  securityNotifyEmail: 'notify-security@example.com',
  securitySlackWorkspaceId: 'XXXXXXXXX',
  securitySlackChannelId: 'XXXXXXXXXXX',
  env: { account: 'XXXXXXXXXXXX', region: 'us-east-2' },
};
  • usecases\blea-gov-base-ct\bin\blea-gov-base-ct-via-cdk-pipelines.tsを修正
import * as cdk from 'aws-cdk-lib';
import { BLEAGovBaseCtPipelineStack as BLEAGovBaseCtPipelineStack } from '../lib/stack/blea-gov-base-ct-via-cdk-pipelines-stack';

// Import parameters for each enviroment
// ★parameter.tsで追加したパラメーターをインポート
import { devPipelineParameter, Account1US1parameter, Account2US2parameter } from '../parameter'; 

const app = new cdk.App();

// Create stack for "Dev" environment.
// If you have multiple environments, instantiate stacks with its parameters.
new BLEAGovBaseCtPipelineStack(app, 'Dev-BLEAGovBaseCtPipeilne', {
  description:
    'Pipeline stack for BLEA Governance Base for multi-accounts (uksb-1tupboc58) (tag:blea-gov-base-ct-via-cdk-pipelines)',
  env: {
    account: devPipelineParameter.env.account || process.env.CDK_DEFAULT_ACCOUNT,
    region: devPipelineParameter.env.region || process.env.CDK_DEFAULT_REGION,
  },
  tags: {
    Repository: 'aws-samples/baseline-environment-on-aws',
    Environment: devPipelineParameter.envName,
  },

  targetParameters: [Account1US1parameter, Account2US2parameter], // ★追加したパラメーターを指定
  sourceRepository: devPipelineParameter.sourceRepository,
  sourceBranch: devPipelineParameter.sourceBranch,
  sourceConnectionArn: devPipelineParameter.sourceConnectionArn,
});
  • usecases\blea-gov-base-ct\lib\stack\blea-gov-base-ct-via-cdk-pipelines-stack.tsを修正
import * as cdk from 'aws-cdk-lib';
import { pipelines, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { BLEAGovBaseCtStage } from '../stage/blea-gov-base-ct-stage';
import { AppParameter } from '../../parameter';

export interface BLEAGovBaseCtPipelineStackProps extends cdk.StackProps {
  targetParameters: AppParameter[];
  sourceRepository: string;
  sourceBranch: string;
  sourceConnectionArn: string;
}

export class BLEAGovBaseCtPipelineStack extends Stack {
  constructor(scope: Construct, id: string, props: BLEAGovBaseCtPipelineStackProps) {
    super(scope, id, props);

    const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
      crossAccountKeys: true, // ★クロスアカウントでデプロイする場合はtrueにする必要がある
      synth: new pipelines.ShellStep('Synth', {
        input: pipelines.CodePipelineSource.connection(props.sourceRepository, props.sourceBranch, {
          connectionArn: props.sourceConnectionArn,
        }),
        installCommands: ['n stable', 'node --version', 'npm i -g npm', 'npm --version'],
        commands: [
          'npm ci --workspaces',
          'cd usecases/blea-gov-base-ct',
          'npx cdk synth --app "npx ts-node --prefer-ts-exts bin/blea-gov-base-ct-via-cdk-pipelines.ts" --all',
        ],
        primaryOutputDirectory: './usecases/blea-gov-base-ct/cdk.out',
      }),
    });

    props.targetParameters.forEach((params) => {
      pipeline.addStage(new BLEAGovBaseCtStage(this, params.envName, params)); // ★リソース名の重複を避けるためにenvNameを指定
    });
  }
}
  • usecases\blea-gov-base-ct\lib\stage\blea-gov-base-ct-stage.tsを修正
import { Stack, Stage } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Detection } from '../construct/detection';
import { Iam } from '../construct/iam';
import { Logging } from '../construct/logging';
import { Notification } from '../construct/notification'; // ★Notificationコンストラクトをインポート
import { BLEAGovBaseCtStackProps } from '../stack/blea-gov-base-ct-stack';

export class BLEAGovBaseCtStage extends Stage {
  constructor(scope: Construct, id: string, props: BLEAGovBaseCtStackProps) {
    super(scope, id, props);

    // Define a stack and associate same constructs as normal to this.
    const stack = new Stack(this, 'BLEAGovBaseCt', {
      description: 'BLEA Governance Base for multi-accounts (uksb-1tupboc58) (tag:blea-gov-base-ct)',
      tags: {
        Repository: 'aws-samples/baseline-environment-on-aws',
      },
    });

    new Iam(stack, 'Iam');

    // AWS CloudTrail configuration in Control Tower Landing Zone v3.0 will not create CloudWatch Logs LogGroup in each Guest Accounts.
    // And it will delete these LogGroups when AWS CloudTrial Configuration is disabled in case of updating Landing Zone version from older one.
    // BLEA should notify their alarms continuously. So, if there is no CloudTrail and CloudWatch Logs in Guest Account, BLEA creates them to notify the Alarms.
    const logging = new Logging(stack, 'Logging');

    // Security Alarms
    // !!! Need to setup SecurityHub, GuardDuty manually on Organizations Management account
    // AWS Config and CloudTrail are set up by Control Tower
    const detection = new Detection(stack, 'Detection', {  // ★Detectionを変数化
      notifyEmail: props.securityNotifyEmail,
      cloudTrailLogGroupName: logging.trailLogGroup.logGroupName,
    });

    // ★securitySlackWorkspaceIdとsecuritySlackChannelIdの存在チェック
    if (!props.securitySlackWorkspaceId || !props.securitySlackChannelId) {
      throw new Error('securitySlackWorkspaceId and securitySlackChannelId are required');
    }

    // ★Notificationコンストラクトを呼び出し
    new Notification(stack, 'Notification', {
      topicArn: detection.topic.topicArn,
      workspaceId: props.securitySlackWorkspaceId,
      channelId: props.securitySlackChannelId,
    });
  }
}

4. アカウント2のブートストラップを実施する

4-1. [AWS access portal]画面から、アカウント2のAdministrator権限のある許可セットを選択します。

4-2. 作業端末のOSに合わせて、一時クレデンシャル情報をコピーし、作業端末のターミナルに貼り付けて実行します。

4-3. アカウント2のブートストラップを実施します。

cd usecases/blea-gov-base-ct
npx aws-cdk bootstrap aws://<アカウント2のアカウントID>/us-east-2 --trust <アカウント1のアカウントID> --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess

※1 --trustにアカウント1のIDを指定することで、アカウント1がアカウント2にデプロイする権限を与えることができます。

※2 本手順では一時クレデンシャルを使用していますが、AWS認証情報ファイルにプロファイルを追加する方法でも代替できます。

5. アカウント1のブートストラップを実施する

5-1. 「4. アカウント2のブートストラップを実施する」と同様の手順でアカウント1の一時クレデンシャル情報をコピーし、ターミナルに貼り付けて実行します。

5-2. アカウント1のブートストラップを実施します。

npx aws-cdk bootstrap aws://<アカウント1のアカウントID>/us-east-1 --cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess

6. アカウント1にパイプラインをデプロイする

アカウント1に対してパイプラインをデプロイします。

npx aws-cdk deploy Dev-BLEAGovBaseCtPipeilne --app "npx ts-node --prefer-ts-exts bin/blea-gov-base-ct-via-cdk-pipelines.ts"

※パイプラインのデプロイが成功すると、パイプラインが自動でキックされて以下の環境へのBLEAのデプロイが始まります。

  • アカウント1のus-east-1
  • アカウント2のus-east-2

7. コンソール画面で実行結果を確認する

7-1. アカウント1のAWSマネジメントコンソールにログインします。

7-2. [CodePipeline] サービスを開きます。

7-3. ナビゲーションペインの[パイプライン]をクリックします。

7-4. Dev-BLEAGovBaseCtPipeilne-Pipeline<識別子>の名前のパイプラインをクリックします。

7-5. [パイプライン]タブで各ステージのアクションが成功していることを確認します。

赤枠の部分のステージのアクションで各環境へのBLEAのデプロイを行っています。

8. BLEAコードを更新し変更をPushして、変更デプロイを実行する

BLEAのコードを変更して、commit/pushします。これによってパイプラインが稼働し、各環境のBLEAに対して変更デプロイが行われます。

今回はusecases\blea-gov-base-ct\lib\construct\detection.tsの内容を変更して、SecurityHubの通知設定を変えてみます。

    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'], // ★もともとCRITICALとHIGHが設定されていましたが、CRITICALのみに変更。
            },
            Compliance: {
              Status: ['FAILED'],
            },
            Workflow: {
              Status: ['NEW', 'NOTIFIED'],
            },
            RecordState: ['ACTIVE'],
          },
        },
      },
      targets: [new cwet.SnsTopic(topic)],
    });

上記の変更内容をcommit/pushします。

git add .
git commit -m "SecurityHubでHIGHを検知しても通知しないように設定"
git push origin main

9. コンソール画面で実行結果を確認する

[実行]タブを確認すると、直近の実行IDのステータスが進行中になっており、GitHubリポジトリへのpushをトリガーにパイプラインがキックされたことが確認できます。

ソースリビジョンにはさきほどのコミットで設定したコミットメッセージが表示されています。

ステータスが成功になることを確認します。

[パイプライン]タブでもすべてのステージのアクションが成功していることを確認できます。

まとめ

今までBLEAのデプロイは手動で実施していたため、アカウントの数が増えるにつれて運用の負荷が大きくなっていましたが、CDK Pipelinesを使用してパイプラインを実装することでかなり作業が楽になると感じました。

今回はBLEAをデプロイする手順として紹介していますが、BLEA以外でもCDKテンプレートであれば基本的な手順は大きく変わらないと思うので色々な場面で活用できそうです。

お知らせ

APCはAWS Advanced Tier Services(アドバンストティアサービスパートナー)認定を受けております。


その中で私達クラウド事業部はAWSなどのクラウド技術を活用したSI/SESのご支援をしております。
www.ap-com.co.jp

https://www.ap-com.co.jp/service/utilize-aws/

また、一緒に働いていただける仲間も募集中です!
今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。

www.ap-com.co.jp

本記事の投稿者: sk07103
AWSをメインにインフラ系のご支援を担当しています。