背景
前回記事の続きです。未読の方はそちらからお願いします。
前回のおさらい
前回記事でやったことを軽くまとめておきます。
-
Cloud One Container Securityの監視対象となるAWS EKSクラスタの作成
- AWS CDKを用いて以下のような環境を作成した
- VPC(public subnet × 2, private subnet × 2)
- EKSクラスタ
- RDS(EKSクラスタ上のPodやJobなどが利用する想定)
- RDSの接続情報が格納されたAWS Secrets Manager上のSecret
- AWS Secrets ManagerのSecretをk8s上のSecretとしてマウントできるExternal Secrets Controller
- AWS CDKを用いて以下のような環境を作成した
- 上記EKSクラスタをCloud One Container Securityの監視対象化
-
公式ガイドラインに記載された手順をAWS CDKで実装して、作成したEKSクラスタにPolicy Based Deployment Controllerをインストールした
- Cloud Oneの管理コンソール上に表示されたAPI KeyをAWS Systems Managerのパラメータストアに格納(Systems Managerを選択できなかった理由は前回記事を参照)
- Helm Chart経由でPolicy Based Deployment Controllerをインストール
- Cloud Oneの管理コンソール上からクラスタが監視対象になっていることを確認
-
公式ガイドラインに記載された手順をAWS CDKで実装して、作成したEKSクラスタにPolicy Based Deployment Controllerをインストールした
EKSクラスタの更新 - 作成したRDSにサンプルデータをインストールする
前回作ったクラスタがせっかくCloud One Container Securityの管理下に入ったので、動作確認を兼ねてクラスタ上にリソースを追加してみましょう。
EKSクラスタと同じVPCに作成したRDSに何か初期データを投入したいと思います。前回解説した通り、RDSのインスタンスへはEKSクラスタからしかアクセスできないよう制限をかけていますので、RDSにデータを投入するDocker Imageを作成してKubernetesのJobとしてEKSクラスタ上で実行することにします。
また、投入するデータに関してはGitHub上に公開されているaws-samplesのデータを使わせて頂きます。
AWS CDKによるKubernetes Jobのapply
普通にkubectl
経由でmanifestをapplyしても良いのですが、せっかくなのでこの辺りもAWS CDK経由でやりましょう。
kubectl
でやる場合は以下のような手順になるかと思いますが、
- Dockerfileを書いてDocker Imageをビルドする
- Docker Registry(例えばAWS Elastic Container Registry)にpushする
- pushしたImageを参照するmanifestファイルを書く
- 作成したファイルを
kubectl
経由でapplyする
AWS CDKでやる場合は必要なソース群を一括で管理し、cdk deploy
コマンド一発で上記の手順を一括実行するよう設定できます。
DockerImageAssetの作成
CDKで自作のDocker Imageを管理するAssetを作成します。
とは言っても特に難しいことはなく、docker build
でビルドする時のディレクトリ構成、つまりDockerfile
が直下に置かれ、そこから参照されるスクリプトなどが配置されたディレクトリを作成します。
#!/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
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-assets
のDockerImageAssert
にディレクトリを指定するだけで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/assets
にpush
してくれます。
CDKでKuberbetes JobのManifestを作成
前回記事でも特に何の前置きもなくhelm chartのインストールやexternal secretのマニフェストをAWS CDKのConstruct
として定義してしまいましたが、改めて、AWS CDKの@aws-cdk/aws-eks
ではクラスタに適用するマニフェストをKubernetesManifest
として定義することができます。
今回は、先ほど例示したDockerAsset
と作成されたImageを参照するKubernetes Jobのマニフェストを定義するカスタムConstruct
を作成しました。
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;
}
}
作成したConstruct
をStack
側で呼び出します。
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へのデータ投入が完了します。
そして肝心のCloud Consoleを覗くと...
きちんとイベントとして記録されていることがわかります。
Deep Security Smart Checkのインストール
Deep Security Smart Checkとは、Trend Micro社が提供するContainer Image Scannerです。Container Securityと連動して、例えばスキャン未実施のImageや一定以上の脆弱性があるImageがクラスタにデプロイされることを阻止することができます。
画像は公式の製品紹介ページより
前回のPolicy based deployment controllerと同様、公式ガイドの手順をAWS CDKで実施します。
API KeyをSystems Managerのパラメータストアに登録する
公式ガイドの手順に従って、Cloud OneのコンソールからContainer Security > Add Scannerを選択すると、前回同様API Keyが発行されます。
前回解説した通り、このAPI KeyをCDKのソースや設定ファイルなどにハードコードするわけにはいきません。本来はSecrets Managerを使いたいところですが、これも前回解説した通り2021年8月現在でちょっと使いにくいので、代わりにSystems Managerのパラメータストアを利用します。
今回は/cloudone/apikey/smartcheck
という名前で登録しました。
SmartCheckをEKSクラスタにインストール
前回同様、AWS CDK経由で用意されているhelm
チャートをインストールします。
Constructの作成
Stack
の中に直接定義しても良いのですが、専用のConstructを作成して分離しておきます。
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レポジトリを確認すると以下のようなサンプルがあります。
## 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を保存しているので以下のようになります。
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の取得方法は公式ガイドを参照ください。
管理コンソールへのログインと設定
EKSと同じアカウント/リージョンにあるECRがスキャン対象であれば、Registry ID
の設定は不要です。また、EKSのセットアップ時にECRを参照するロールもInstance Roleに付与されているので、Authentication Method
はInstance Role
でOKです。
ECRのスキャン
設定後しばらく待つとECR内にあるImageのScanが開始されます。
以下は私のレジストリをScan中の結果ですが、雑に管理しているECRなので結構Critaicalが出ていますね。。
Cloud Oneコンソールの確認
Cloud Oneコンソール > Container Security > Scannersを確認するとちゃんと認識されています。
Scannerが認識されたので、Scan関連のPolicy(未スキャンのイメージはデプロイをブロックするなど)を設定できるようになりました。
後片付け
前回も書きましたが、使い終わったら後片付けをしましょう。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に公開しました。自由にお使いください。