LoginSignup
11
1

More than 1 year has passed since last update.

AWS CDKを使ってTrend Micro Cloud One Container SecurityをEKSにセットアップしてみた その2

Posted at

背景

前回記事の続きです。未読の方はそちらからお願いします。

前回のおさらい

前回記事でやったことを軽くまとめておきます。

  1. Cloud One Container Securityの監視対象となるAWS EKSクラスタの作成
    • AWS CDKを用いて以下のような環境を作成した
      1. VPC(public subnet × 2, private subnet × 2)
      2. EKSクラスタ
      3. RDS(EKSクラスタ上のPodやJobなどが利用する想定)
      4. RDSの接続情報が格納されたAWS Secrets Manager上のSecret
      5. AWS Secrets ManagerのSecretをk8s上のSecretとしてマウントできるExternal Secrets Controller
  2. 上記EKSクラスタをCloud One Container Securityの監視対象化
    • 公式ガイドラインに記載された手順をAWS CDKで実装して、作成したEKSクラスタにPolicy Based Deployment Controllerをインストールした
      1. Cloud Oneの管理コンソール上に表示されたAPI KeyをAWS Systems Managerのパラメータストアに格納(Systems Managerを選択できなかった理由は前回記事を参照)
      2. Helm Chart経由でPolicy Based Deployment Controllerをインストール
      3. Cloud Oneの管理コンソール上からクラスタが監視対象になっていることを確認

EKSクラスタの更新 - 作成したRDSにサンプルデータをインストールする

前回作ったクラスタがせっかくCloud One Container Securityの管理下に入ったので、動作確認を兼ねてクラスタ上にリソースを追加してみましょう。

EKSクラスタと同じVPCに作成したRDSに何か初期データを投入したいと思います。前回解説した通り、RDSのインスタンスへはEKSクラスタからしかアクセスできないよう制限をかけていますので、RDSにデータを投入するDocker Imageを作成してKubernetesのJobとしてEKSクラスタ上で実行することにします。

また、投入するデータに関してはGitHub上に公開されているaws-samplesのデータを使わせて頂きます。

rds-initialize.png

AWS CDKによるKubernetes Jobのapply

普通にkubectl経由でmanifestをapplyしても良いのですが、せっかくなのでこの辺りもAWS CDK経由でやりましょう。

kubectlでやる場合は以下のような手順になるかと思いますが、

  1. Dockerfileを書いてDocker Imageをビルドする
  2. Docker Registry(例えばAWS Elastic Container Registry)にpushする
  3. pushしたImageを参照するmanifestファイルを書く
  4. 作成したファイルをkubectl経由でapplyする

AWS CDKでやる場合は必要なソース群を一括で管理し、cdk deployコマンド一発で上記の手順を一括実行するよう設定できます。

DockerImageAssetの作成

CDKで自作のDocker Imageを管理するAssetを作成します。

とは言っても特に難しいことはなく、docker buildでビルドする時のディレクトリ構成、つまりDockerfileが直下に置かれ、そこから参照されるスクリプトなどが配置されたディレクトリを作成します。

スクリーンショット 2021-08-08 16.13.20.png

./docker/rds-initializer/scripts/import_data.sh
#!/bin/bash

# RDS(Postgres)の認証情報が以下にマウントされる
DBINFO_DIR=/etc/secret
DBINFO_JSON=$DBINFO_DIR/rds-db-info

# 認証情報はJSONで格納されているのでjqでパースして環境変数に設定する
export PGHOST=`cat $DBINFO_JSON | jq -r .host`
export PGPORT=`cat $DBINFO_JSON | jq -r .port`
export PGUSER=`cat $DBINFO_JSON | jq -r .username`
export PGPASSWORD=`cat $DBINFO_JSON | jq -r .password`
export PGDATABASE=postgres


# AWSの公式サンプルデータをCloneする
git clone https://github.com/aws-samples/aws-database-migration-samples/

# CloneしたSQLを実行する
cd aws-database-migration-samples/PostgreSQL/sampledb/v1/
psql -f ./install-postgresql.sql

./docker/rds-initializer/scripts/Dockerfile
FROM debian:stable-slim

RUN apt-get update && apt-get install -y jq && apt-get install -y git && apt-get install -y postgresql-client

RUN mkdir /tmp/workspace

COPY ./scripts/import_data.sh /tmp/workspace/import_data.sh

WORKDIR /tmp/workspace

ENTRYPOINT ["sh", "import_data.sh"]

ディレクトリができたら、@aws-cdk/aws-ecr-assetsDockerImageAssertにディレクトリを指定するだけでOKです。

import {DockerImageAsset} from '@aws-cdk/aws-ecr-assets';

//中略

const rdsInitializerImage = new DockerImageAsset(this, 'rds-initializer', {
    directory: path.join(__dirname, '../docker/rds-initializer')
});

これだけで、cdk deployコマンド実行時にdocker buildを実行しCDK用のECRレポジトリであるaws-cdk/assetspushしてくれます。

スクリーンショット 2021-08-08 16.23.46.png

CDKでKuberbetes JobのManifestを作成

前回記事でも特に何の前置きもなくhelm chartのインストールやexternal secretのマニフェストをAWS CDKのConstructとして定義してしまいましたが、改めて、AWS CDKの@aws-cdk/aws-eksではクラスタに適用するマニフェストをKubernetesManifestとして定義することができます。

今回は、先ほど例示したDockerAssetと作成されたImageを参照するKubernetes Jobのマニフェストを定義するカスタムConstructを作成しました。

./lib/manifest-rds-initialize-job.ts
import * as path from 'path';

import * as cdk from '@aws-cdk/core';
import * as eks from '@aws-cdk/aws-eks';
import {DockerImageAsset} from '@aws-cdk/aws-ecr-assets';

/**
 * RdsInitializeJobに指定するProperty
 */ 
export interface RdsInitializeJobProps{
    /** マニフェストを適用するEKSクラスタ*/
    cluster: eks.ICluster,
    /** RDSの認証情報が格納されているSecretの情報*/
    rdsCredentialSecret: {
        name: string,
        key: string
    }
}

/**
 * RDSの初期化Jobを作成するConstruct
 */ 
export class RdsInitializeJob extends cdk.Construct {
    /** 作成されるマニフェストのConstructNode(依存性の定義に使用)*/
    readonly manifestNode: cdk.ConstructNode

    /**
     * コンストラクタ
     */ 
    constructor(scope: cdk.Construct, id: string, props: RdsInitializeJobProps){
        super(scope, id);

        const {cluster, rdsCredentialSecret} = props;

        /**
         * jobで使用するDockerイメージのAsset
         */ 
        const rdsInitializerImage = new DockerImageAsset(this, 'rds-initializer', {
            directory: path.join(__dirname, '../docker/rds-initializer')
        });

        /**
         * RDS初期化ジョブのマニフェスト
         */ 
        const manifest = new eks.KubernetesManifest(this, 'rds-initialize-job', {
          cluster,
          manifest:[
            {
              apiVersion: "batch/v1",
              kind: "Job",
              metadata:{
                name: "rds-initialize-job"
              },
              spec:{
                template:{
                  spec:{
                    containers: [
                      {
                        name: "rds-initialize",
                        image: rdsInitializerImage.imageUri, //DockerAsset化Imageをこのように参照できる
                        volumeMounts:[
                          {
                            name: "rdsdbinfo",
                            mountPath: "/etc/secret"
                          }
                        ]
                      }
                    ],
                    volumes: [
                      { //external secretで連携したRDSの認証情報
                        name: "rdsdbinfo",
                        secret: {
                          secretName: rdsCredentialSecret.name,
                          items: [
                            {
                              key: rdsCredentialSecret.key,
                              path: 'rds-db-info'
                            }
                          ]
                        }
                      }
                    ],
                    restartPolicy: "Never"
                  }
                }
              }
            }
          ]
        });

        this.manifestNode = manifest.node;
    }
}

作成したConstructStack側で呼び出します。

./lib/cdk-container-security-sample-stack.ts
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as eks from '@aws-cdk/aws-eks';
import * as rds from '@aws-cdk/aws-rds';

import {PolicyBasedDeploymentController} from './helm-policybased-deployment-controller';
//追加
import {RdsInitializeJob} from './minifest-rds-initialize-job';

//中略

export class CdkContainerSecuritySampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    //だいぶ中略

    if(postgresRdsSecretInfo){
      //中略

      // rdsの初期化Jobの適用
      const rdsInitializeJob = new RdsInitializeJob(this, 'rds-initialize', {
        cluster,
        rdsCredentialSecret: {
          name: 'postgres-rds-secret',
          key: "dbcredential"
        }
      });
      // このマニフェストがpostgresRdsSecretManifestの後に適用されるよう依存関係を明示
      rdsInitializeJob.manifestNode.addDependency(postgresRdsSecretManifest);
    }
  }

  //中略
}

動作確認

この状態で再度、

$ yarn cdk deploy --parameters CloudOneApiKey=/cloudone/apikey/samplecluster

を実行します。

実行後しばらく待つと、RDSへのデータ投入が完了します。

スクリーンショット 2021-08-08 12.27.21.png
スクリーンショット 2021-08-08 12.32.33.png

そして肝心のCloud Consoleを覗くと...

スクリーンショット 2021-08-08 12.30.14.png

きちんとイベントとして記録されていることがわかります。

Deep Security Smart Checkのインストール

Deep Security Smart Checkとは、Trend Micro社が提供するContainer Image Scannerです。Container Securityと連動して、例えばスキャン未実施のImageや一定以上の脆弱性があるImageがクラスタにデプロイされることを阻止することができます。

スクリーンショット 2021-08-09 9.58.06.png
画像は公式の製品紹介ページより

前回のPolicy based deployment controllerと同様、公式ガイドの手順をAWS CDKで実施します。

API KeyをSystems Managerのパラメータストアに登録する

公式ガイドの手順に従って、Cloud OneのコンソールからContainer Security > Add Scannerを選択すると、前回同様API Keyが発行されます。

スクリーンショット 2021-08-09 9.10.01.png

前回解説した通り、このAPI KeyをCDKのソースや設定ファイルなどにハードコードするわけにはいきません。本来はSecrets Managerを使いたいところですが、これも前回解説した通り2021年8月現在でちょっと使いにくいので、代わりにSystems Managerのパラメータストアを利用します。

スクリーンショット 2021-08-09 10.12.20.png

今回は/cloudone/apikey/smartcheckという名前で登録しました。

SmartCheckをEKSクラスタにインストール

前回同様、AWS CDK経由で用意されているhelmチャートをインストールします。

Constructの作成

Stackの中に直接定義しても良いのですが、専用のConstructを作成して分離しておきます。

./lib/helm-smartcheck.ts
import * as cdk from '@aws-cdk/core';
import * as eks from '@aws-cdk/aws-eks';

/**
 * SmartCheckのプロパティ
 */ 
export interface SmartCheckProps{
    /** インストール対象のEKSクラスタ*/
    cluster: eks.ICluster,
    /** Cloud Oneが発行したAPI Key*/
    apiKey: cdk.SecretValue
}

/**
 * Trend Micro Cloud One Container SecurityのDeepSecurity SmartCheckをインストールするHelmを適用するConstruct
 * @see https://cloudone.trendmicro.com/docs/container-security/sc-integrate/
 * @see https://github.com/deep-security/smartcheck-helm
 */ 
export class SmartCheck extends cdk.Construct{
    constructor(scope: cdk.Construct, id: string, props: SmartCheckProps){
        super(scope, id);

        const {cluster, apiKey} = props;
        new eks.HelmChart(this, "smartcheck-deployment", {
            cluster,
            release: "deepsecurity-smartcheck",
            chart: "https://github.com/deep-security/smartcheck-helm/archive/master.tar.gz",
            values: {
                cloudOne: {
                    apiKey
                },
                auth: {
                    /**
                     * 管理コンソールのパスワードを作成する際のシード
                     * ここではStack IDを使用する
                     */ 
                    secretSeed: cdk.Stack.of(this).stackId
                }
            } 
        })
    }
}

前回のpolicy-based-deployment-controllerと同じといえば同じですが、一点だけ注意ください。

smartcheck-helmのGitHubレポジトリを確認すると以下のようなサンプルがあります。

smartcheck-helmのGitHubレポジトリより引用
## activationCode is the product activation code.
##
## Default value: (none)
# activationCode: YOUR-CODE-HERE

# While it's still possible to use activationCode to activate Deep Security Smart Check,
# we strongly encourage you to enroll your Deep Security Smart Check installation with a valid Cloud One Container Security API key.
# You can find more detail on how to get a API key in https://cloudone.trendmicro.com/docs/container-security/api-key-create/
cloudOne:
 apiKey: YOUR-CLOUD-ONE-CONTAINER-SECURITY-API-KEY

auth:
  ## secretSeed is used as part of the password generation process for
  ## all auto-generated internal passwords, ensuring that each installation of
  ## Deep Security Smart Check has different passwords.
  ##
  ## Default value: {must be provided by the installer}
  secretSeed: YOUR-SECRET-HERE

apiKeyの他にsecretSeedという、SmartCheckの管理画面のパスワード生成に使用するシードが必須となっています。今回はStackのIDを使用することにしました。

                auth: {
                    /**
                     * 管理コンソールのパスワードを作成する際のシード
                     * ここではStack IDを使用する
                     */ 
                    secretSeed: cdk.Stack.of(this).stackId
                }

Stack側から呼び出し

作成したConstructをStack側から呼び出します。前述の通り、パラメータストアにAPI Keyを保存しているので以下のようになります。

./lib/cdk-container-security-sample-stack.ts
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as eks from '@aws-cdk/aws-eks';
import * as rds from '@aws-cdk/aws-rds';

import {PolicyBasedDeploymentController} from './helm-policybased-deployment-controller';
//追加
import {SmartCheck} from './helm-smartcheck';

//中略

export class CdkContainerSecuritySampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    //だいぶ中略

    //Cloud One Container Securityの導入
    new PolicyBasedDeploymentController(this, "cloudone", {
      cluster,
      apiKey: cdk.SecretValue.cfnParameter(new cdk.CfnParameter(this, 'CloudOneApiKey', {type: "AWS::SSM::Parameter::Value<String>", noEcho: true}))
    });

    //DeepSecurity SmartCheckの導入
    new SmartCheck(this, "smartcheck", {
      cluster,
      apiKey: cdk.SecretValue.cfnParameter(new cdk.CfnParameter(this, 'SmartCheckApiKey', {type: "AWS::SSM::Parameter::Value<String>", noEcho: true}))
    });  }

  //中略
}

この変更によってパラメータが1つ増え、deployコマンドは以下のようになります。

$ yarn run cdk deploy --parameters CloudOneApiKey=/cloudone/apikey/samplecluster --parameters SmartCheckApiKey=/cloudone/apikey/smartcheck

動作確認

Smart Checkは自身のクラスタ上に作成されます。管理コンソールのUIと初期のID/Passwordの取得方法は公式ガイドを参照ください。

管理コンソールへのログインと設定

スクリーンショット 2021-08-09 11.06.00.png

スクリーンショット 2021-08-09 11.10.16.png

EKSと同じアカウント/リージョンにあるECRがスキャン対象であれば、Registry IDの設定は不要です。また、EKSのセットアップ時にECRを参照するロールもInstance Roleに付与されているので、Authentication MethodInstance RoleでOKです。

ECRのスキャン

設定後しばらく待つとECR内にあるImageのScanが開始されます。
以下は私のレジストリをScan中の結果ですが、雑に管理しているECRなので結構Critaicalが出ていますね。。

スクリーンショット 2021-08-09 11.25.47.png

Cloud Oneコンソールの確認

Cloud Oneコンソール > Container Security > Scannersを確認するとちゃんと認識されています。

スクリーンショット 2021-08-09 11.28.38.png

Scannerが認識されたので、Scan関連のPolicy(未スキャンのイメージはデプロイをブロックするなど)を設定できるようになりました。

スクリーンショット 2021-08-09 11.29.26.png

後片付け

前回も書きましたが、使い終わったら後片付けをしましょう。EKSを稼働しているとそれなりの利用料がかかります。

繰り返しになりますが、Infrastructure as Codeの良いところは作った環境を躊躇なく削除できるところです。もう一度動作検証したくなったらもう一度depolyすれば良いだけです。

$ yarn cdk destroy #作成した環境を削除する

まとめ

Infrastructure as Codeとセキュリティ

今回の記事ではAWS EKSに対するTrend Micro Cloud One Container SecurityをAWS CDKを用いたInfrastructure as Codeで実現する方法を紹介しました。

AWSもCloud Oneも素晴らしいサービスです。そういった大前提の中で、我々エンジニアにとってのセキュリティリスクは、実は我々エンジニアの人為ミスにあるのではないでしょうか。

  • 単純に知識がなく、適切な設定ができない
  • 納期や時間に追われて設定をミスしてしまう
  • PoCや技術スパイク中に「とりあえず」設定した雑な設定をうっかり本番環境に適用してしまう

などなどの問題をAWS CDKをはじめとしたInfrastructure as Codeツールが解決します。

自動化は効率化や納期短縮のためにやるのではなく、ガバナンスやセキュリティを含めた品質向上のためにやるのです。

AWS CDKとKubernetes

今回紹介した通り、AWS CDKを用いてAWS EKSのクラスタを構築するだけでなく、helm chartのインストールやmanifestの定義をすることができます。それだけでなく、@aws-cdk/aws-ecr-assetsなどを活用して、Docker Imageの管理までAWS CDKの中で管理することができます。

言い換えると、インフラ層からアプリケーション層まで1つのソースベースで一括管理できるということです。

こういった構造は、アジャイルやリーンスタートアップのような少数精鋭チームが自律的に判断し、臨機応変に活動する形態に於いて非常に有用です。

マインドや文化を支えるには技術も必要

アジャイルやリーンスタートアップ、CI/CDやDevOpsなどの近年流行の概念はツール論や技術論だけではなく文化やマインドセットが占める割合が多い概念です。しかし、文化やマインドセットを支えるのは技術であることも事実です。

「失敗を恐れずにチャレンジしよう」という号令をかけるには、その裏で失敗した時に最悪の事態を招かないようリスク削減を行うための技術投資が必要です。その中にはもちろんセキュリティも含まれます。

今回紹介したようにAWSやCloud Oneのような素晴らしいサービスの力を借りつつ、安全にチャレンジができるよう技術を磨いていきましょう。

Appendix

今回のソースコードについて

ソースコード全体をGitHubに公開しました。自由にお使いください。

11
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
1