EMRからS3にアクセス時のAPI発行回数を調べる

aws cloudtrail create-trail –name az-s3-event-selector –s3-bucket-name az-read-only-events { “IncludeGlobalServiceEvents”: true, “Name”: “az-s3-event-selector”, “TrailARN”: “arn:aws:cloudtrail:ap-northeast-1:********:trail/az-s3-event-selector”, “LogFileValidationEnabled”: false, “IsMultiRegionTrail”: … 続きを読む

[JAWSBigData#11]Cloudera on AWSと Amazon EMRを両方本番運用し 3つの観点から比較してみる …

BigData-JAWS 勉強会#11 発表資料https://jawsug-bigdata.connpass.com/event/77463/ ○ 概要Cloudera on AWSとして、Cloudera社の代表的ツールClouderaDirector/(ClouderaAltus)と、AmazonEMRの特徴を紹介します。Cloudera on AWS/AmazonEMR両方を本番環境で運用し、そこでのアーキテクチャ/エコ … 続きを読む

AWS Lambdaでkintoneアプリの簡易自動バックアップを作ってみた その1(フォーム設計情報とレコードの取得)

kintone自体はアプリケーションを非常に簡単に作成できるのですが、そのバックアップ、となると有償の製品を購入するか、あるいは手動で実施する、というのが現状です。

そこで、有償のバックアップとまでは行かなくても、簡易的なバックアップの仕組みを作れないか、試してみました。

ゴール

とりあえず以下の条件をゴールとします。

  1. バックアップ対象は「フォームの設計」と「データ一式」
  2. バックアップは自動で定期的に実施される
  3. 必要であればリストアの仕組みも作る

バックアップのための仕組み

・フォームの設計情報をバックアップ
 →フォーム設計情報取得
・データ一式のバックアップ
 →第10回 kintone REST APIを利用したレコード取得

これらをAWS Lambda(Python 3)上から呼び出せるように実装します。リストアもjson形式で対応できそうなので、上記のページの通りの結果が得られればバックアップとしては問題なさそうです。

処理対象

こんな感じの備品在庫管理(既成品)を利用します。

スクリーンショット 2018-01-28 16.28.42.png

実装

前処理

色々と準備が必要なので、最初に実施します。

KINTONE_BASE_URL = os.environ['KINTONE_URL']
KINTONE_FORM_BASE_URL = os.environ['KINTONE_FORM_BASE_URL']
URL = KINTONE_BASE_URL.format(
    kintone_domain=os.environ['KINTONE_DOMAIN'],
    kintone_app=os.environ['KINTONE_APP']
)
FORM_URL = KINTONE_FORM_BASE_URL.format(
    kintone_domain=os.environ['KINTONE_DOMAIN'],
    kintone_app=os.environ['KINTONE_APP']
)
HEADERS_KEY = os.environ['KINTONE_HEADERS_KEY']
API_KEY = os.environ['KINTONE_API_KEY']

S3_BUCKET = os.environ['S3_BUCKET']
S3_OBJECT_PREFIX = os.environ['S3_OBJECT_PREFIX']

s3_client = boto3.client('s3')

全レコードの取得

こんな感じでQueryに何も指定しない全件取得を行います。

def get_all_records(headers):
    query = u''

    response_record = requests.get(URL + query , headers=headers)
    return json.loads(response_record.text)

フォーム設計情報取得

フォームは前述のページを参考にこのような実装になります。

def get_form_info(headers):

    response_record = requests.get(FORM_URL, headers=headers)
    return json.loads(response_record.text)

取得したデータをS3にバックアップとして保持

botoライブラリを利用して、tmpフォルダに一時的に作成したファイルをS3にアップロードします。

def put_data_to_s3(contents):
    date = datetime.now()
    date_str = date.strftime("%Y%m%d%H%M%S")
    tmp_dir = "/tmp/"
    tmp_file = S3_OBJECT_PREFIX + "_" + date_str + ".json"

    with open(tmp_dir + tmp_file, 'w') as file:
        file.write(json.dumps(contents, ensure_ascii=False, indent=4, sort_keys=True, separators=(',', ': ')))

    s3_client.upload_file(tmp_dir + tmp_file, S3_BUCKET, tmp_file)

    return True

上記を呼び出し側メソッド

今まで作成したきた部品をつなぎ合わせます。それぞれレコード一覧とフォーム設計情報を一つのJSONファイルにまとめる様に実装しています。

def run(event, context):

    headers = {HEADERS_KEY: API_KEY}
    record_data = get_all_records(headers)
    records = record_data['records']

    form_data =get_form_info(headers)
    forms = form_data['properties']

    result = {
        "records": records,
        "properties": forms
    }

    return put_data_to_s3(result)

(おまけ)環境変数はステージごとに切り替えるように実装

クラメソさんの記事を参考に、ステージごとに環境変数を切り替えられるようにしています。

serverless.yml
service: aws-kintone-backup

provider:
  name: aws
  runtime: python3.6
  region: us-east-1
  stage: ${opt:stage, self:custom.defaultStage}
  environment:
    KINTONE_URL: https://{kintone_domain}/k/v1/records.json?app={kintone_app}
    KINTONE_FORM_BASE_URL: https://{kintone_domain}/k/v1/form.json?app={kintone_app}
    KINTONE_HEADERS_KEY: X-Cybozu-API-Token
custom:
  defaultStage: dev
  otherfile:
    environment:
      dev: ${file(./conf/dev.yml)}
      prd: ${file(./conf/prd.yml)}

functions:
  run:
    handler: handler.run
    environment:
      KINTONE_DOMAIN: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_DOMAIN}
      KINTONE_API_KEY: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_API_KEY}
      KINTONE_APP: ${self:custom.otherfile.environment.${self:provider.stage}.KINTONE_APP}
      S3_BUCKET: ${self:custom.otherfile.environment.${self:provider.stage}.S3_BUCKET}
      S3_OBJECT_PREFIX: ${self:custom.otherfile.environment.${self:provider.stage}.S3_OBJECT_PREFIX}
dev.yml
KINTONE_DOMAIN: xxxxxx.cybozu.com
KINTONE_API_KEY: XXXXXXXXX
KINTONE_APP: XXX
S3_BUCKET: XXXXXX
S3_OBJECT_PREFIX: XXXXXX

slsコマンドでアップロードをする際に--stageオプションを指定する必要があります。(デフォルトはdev

実行してみる

さて、それぞれ環境変数を指定して実行してみます。無事S3にファイルが出力されました。中身も無事出力されています。(コンテンツが長いからここには載せないけど)

スクリーンショット 2018-02-05 22.08.06.png

AWS Lambdaで開発しているので、あとはスケジューリングでもすれば自動起動は簡単ですね。

現状の課題

一応できましたが、いくつかすでにわかっている課題があります。

  1. レコードの一括取得は一度に500件までしかできない。(kintoneの仕様)
  2. レコードの一括登録は一度に100件までしかできない。(kintoneの仕様)
  3. S3へアップロードしたファイルはずっと残ってしまう。(手動で削除しに行かないと後々ゴミになる)
  4. リストアの仕組みがない

この辺を解消するような実装はまた後ほど。

成果物

ここまでの成果物は以下になります。

https://github.com/kojiisd/aws-kintone-backup/tree/v1.0

まとめ

よくあるkintoneアプリケーションのバックアップの一部を自動化することができました。とはいえAPIとしてはプロセス管理やアプリケーションの一般的な設定まで取得するものがあるので、この辺を盛り込むと、どんどんとリッチなバックアップ処理になりそうです。その辺りはまた次にでも。

続きを読む

ローカルでLambdaのテストをする環境を作ったメモ

何?

Lambdaをテストする際、いちいちUPしてCloudWatchを確認して・・・とテストするのは辛いのでローカルでテストする環境を作る。
作ったメモ

検証環境

Mac: macOS Sierra
awscli: aws-cli/1.14.32 Python/2.7.13 Darwin/16.7.0 botocore/1.8.36
nodejs: v9.4.0
npm: 5.6.0
docker: Version 17.06.2-ce-mac27

ディレクトリ構成

.
├── docker-compose.yml
├── event.json
├── index.js
├── package.json
└── template.yml

aws-sam-localのインストール

npm i aws-sam-local -g

私はこいつはグローバルインストールしている

手順

作業ディレクトリの作成と移動

コマンド
mkdir test
cd test

npm install

npm init -y
npm i aws-sam-local aws-sdk --save-dev

sam-localが使用するYAMLの作成

template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  lambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs6.10

localstack用のYAMLファイル作成

docker-compose.yml
version: '2.1'
services:
  localstack:
    image: localstack/localstack
    ports:
      - 4567-4583:4567-4583
      - 8080:8080

スクリプトの用意

index.js
'use strict';

const AWS = require('aws-sdk');
const S3 = new AWS.S3({endpoint: 'http://<ローカル端末のIP>:4572', s3ForcePathStyle: true});


exports.handler = (event, context, callback) => {
    console.log(`EVENT is ${event}`);
    uploads3().then(() => {
        callback()
    });
};

const uploads3 = () => {
    return new Promise((resolve, reject) => {
        let param = {
            Bucket: "xxxxxxxxxxxxxbbb",
            Key: "test2.txt",
            Body: "fugafuga"
        };
        console.log(param);

        S3.putObject(param, (err, data) => {
            if (err) {
                console.log(err, err.stack);
            } else {
                console.log(data);
            }
            resolve();
        });
    });
};

ダミーイベント作成

コマンド
sam local generate-event dynamodb > event.json

local-stackの起動(バックグラウンド起動)

コマンド
docker-compose up -d

lambdaのローカル実行

コマンド
sam local invoke lambdaFunction -e event.json 
  • アップロード用のS3バケットのダミーは最初に作っておくこと。
  • samが呼んでくるlambda動かすdockerからlocalstackへのネットワーク疎通が通らなかったからEndpointは端末のIP指定している。

ローカルでCLI使ってlocalstackは疎通出来るのに、docker上で動いてるLambdaスクリプトから接続ができなくてすっごいハマった。

参考

[新ツール]AWS SAMをローカル環境で実行できるSAM Localがベータリリース

AWS SAM Local と LocalStack を使って ローカルでAWS Lambdaのコードを動かす

続きを読む

Glacier Expressヤクの毛刈り(CloudFormation CLI tool) メモ (2018/02/03) (未整理)

  • Glacier Expressのヤクの毛刈りとしてCloudFormation 用のCLI toolの作成を行う
  • ただ、現状ではTool の作成どころか設計のための情報も足りず、検証段階と言える
  • 今日の検証はStackのイベントの追跡
    • いままでの検証で
    • describe_stack_eventsで追跡できそうなことはわかったため、更に検証を進めた
  • 結果としては順調に情報の取得ができた
  • イベントは時系列(降順)で取得できる
  • next_tokenで次の以降のイベントに限って取得できると考えたが、実際には”一定以上イベントが積み重なっている場合のPagerである”ことが判明したあまり使用することは想定されない
    • アウトプットが1MBを超えたときに使用する
    • カウント方法が分からないためなんとも言えないが1MBはレスポンスとしては非常に大きいため当面は対応する必要はないように感じる
  • ただ、イベントすべてを取得するため、今回適用分以外も表示されると考えた方が良い
  • client_request_tokenをeventの情報の要素に見つける、これなら今回適用分のみ判別することに使えるかもしれない
  • 再度試行してみるも、client_request_tokenの値が空白(null or 空文字列)であった
  • API Referenceを再度確認すると、どうもclient_request_token はcreate-stackを行う際に指定するようだ。
  • 指定してみたところその文字列が表示された。
  • さて、ではイベント取得の終了条件について考えてみることにする
  • create, update, delete、それぞれを確認してみたところ、どの変更であったとしても最初と最後のイベントは論理IDはstack_nameと同じものとなっている
  • そのため論理IDとステータスを見張れば終了がわかると考える
  • 試行してみたところ概ね成功
    • ただし、削除の場合は失敗
    • どうもDELETE_COMPLETEはstack_nameが論理IDの場合はない模様
    • 終了はスタックが存在するかどうかで判断する必要があるらしい
    • あと実際にはstack_nameだけじゃなくてリソースタイプがAWS::CloudFormation::Stackであるかも条件に加えた方がいいと思われる

続きを読む

AWS Lambda から ElastiCache に接続するメモ

やりたいこと

  • AWS Lambda から ElastiCache に接続する
  • boto3 は get/set はできないらしいので他の方法を使う

必要な設定

  • AWS Lambda の実行ロールに ElastiCache へのアクセス権限追加
  • AWS Lambda, ElastiCache を同じVPCにあわせる

redis (クラスターモードが無効) の場合

EC2 にて redis-py をダウンロード

$ pip3 install redis -t ./
  • 生成されるディレクトリを開発環境の index.py と同じディレクトリにコピーする

index.py 作成

index.py
import redis

def handler(event, context):

    r = redis.StrictRedis(host='****.****.0001.apne1.cache.amazonaws.com', port="6379", db=0)

    r.set("key1", "value1")
    print(r.get("key1"))

    return "OK"

以上。

redis (クラスターモードが有効) の場合

EC2 にて redis-py-cluster をダウンロード

$ pip3 install redis-py-cluster -t ./
  • redis-py も依存してるので一緒にダウンロードされる
  • 生成されるディレクトリを開発環境の index.py と同じディレクトリにコピーする

index.py 作成

index.py
from rediscluster import StrictRedisCluster

def handler(event, context):

    startup_nodes = [{"host": "****.****.clustercfg.apne1.cache.amazonaws.com", "port": "6379"}]
    rc = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True, skip_full_coverage_check=True)

    rc.set("key1", "value")
    print(rc.get("key1"))

    return "OK"

  • クラスターモードが無効の redis には繋がらないのでテスト環境などでは上記実装を切り替える

以上。

続きを読む

Cognitoユーザープールからの送信メールをカスタマイズする

サービスの認証にCognitoユーザープールを使用する際、確認コードや、初回ログインパスワードなどをメールでお知らせする場合があると思います。

Cognitoのデフォルト設定だとこんなメールが届きます。

Your confirmation code is 927173
Your username is xxxx and temporary password is xxxxxx.

このメールタイトル、本文をカスタマイズする方法です。

カスタムメッセージが設定できるイベント

公式ドキュメントからの抜粋です。
以下がカスタムメッセージが設定できるイベントの一覧です。

AWS Lambda トリガーのリクエストおよびレスポンスパラメータ

triggerSource 値 トリガーイベント
CustomMessage_AdminCreateUser カスタムメッセージ – 新規ユーザーに一時パスワードを送信するため.
CustomMessage_ResendCode カスタムメッセージ – 既存ユーザーに確認コードを再送するため.
CustomMessage_ForgotPassword カスタムメッセージ – 忘れたパスワードのリクエスト用の確認コードを送信するため.
CustomMessage_UpdateUserAttribute カスタムメッセージ – ユーザーの E メールまたは電話番号が変更されると、このトリガーは確認コードをそのユーザーに自動的に送信します。他の属性には使用できません。
CustomMessage_VerifyUserAttribute カスタムメッセージ – ユーザーが手動で新しい E メールや電話番号の認証コードをリクエストすると、このトリガーからユーザーに認証コードが送信されます。
CustomMessage_Authentication カスタムメッセージ – 認証時に MFA コードを送信するため.

今回はtriggerSource値がCustomMessage_AdminCreateUserの場合(Cognitoユーザープールのポリシーに「管理者のみにユーザーの作成を許可する」を設定していて、管理者がユーザーを作成した際にユーザーに送信されるメール)のカスタマイズを例にします。

手順

  1. Lambdaファンクションの作成
  2. Cognitoユーザープールに1.で作成したファンクションを登録

1. Lambdaファンクションの作成

Python3.6での例です。
ファンクションのIAMロールにはCloudwatchログ出力権限の付与だけでOKです。以下IAMポリシーの例です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "*"
            ],
            "Effect": "Allow"
        }
    ]
}

Lambdaファンクションで受け取るイベントをログ出力するとこのような感じ。

{
    "version": "1",
    "region": "ap-northeast-1",
    "userPoolId": "ap-northeast-1_xxxxxxxxx",
    "userName": "hogee",
    "callerContext": {
        "awsSdkVersion": "aws-sdk-js-2.176.0",
        "clientId": "CLIENT_ID_NOT_APPLICABLE"
    },
    "triggerSource": "CustomMessage_AdminCreateUser",
    "request": {
        "userAttributes": {
            "sub": "bf6e9c66-0a69-476e-bfd8-724d38e6555f",
            "cognito:email_alias": "hoge@example.com",
            "email_verified": "True",
            "cognito:user_status": "FORCE_CHANGE_PASSWORD",
            "name": "hoge",
            "email": "hoge@example.com"
        },
        "codeParameter": "{####}",
        "usernameParameter": "{username}"
    },
    "response": {
        "smsMessage": "None",
        "emailMessage": "None", # ここをカスタムした本文に変える
        "emailSubject": "None" # ここをカスタムしたタイトルに変える
    }
}

このイベントをそのままreturn eventとするとデフォルト設定が適用されたメールが送られます。カスタマイズするにはresponseのフィールド内をカスタマイズしたい内容に書き換えてリターンします。

cognito_custom_message.py
# -*- coding:utf-8 -*-

def handler(event, context):
    if event['triggerSource'] == 'CustomMessage_AdminCreateUser':
        customed_event = custom_message_admin_create_user(event)

    return customed_event


def custom_message_admin_create_user(event):

    email_message = '''
{username} 様
<br>
<br>
管理者から招待されました。
<br>
<br>
ログインメールアドレス:{mail}
<br>
初回ログインパスワード:{password}
'''.format(username='{username}',
           mail=event['request']['userAttributes']['email'],
           password='{####}')

    event['response']['emailSubject'] = '仮パスワード発行のお知らせ'
    event['response']['emailMessage'] = email_message

    return event

{username}にユーザーネーム、{####}にパスワードが入ってメールが送信されることになります。
triggerSourceによって本文に含めなくてはいけないコードパラメータは異るため先ほどの公式ドキュメントで確認してください。
triggerSourceがCustomMessage_AdminCreateUserの場合、メッセージ本文に{username}{####}が入っていないとエラーになります。

もし追加で他のカスタムメッセージイベントにも対応したい場合、handler()

    elif event['triggerSource'] == 'CustomMessage_ForgotPassword':
        customed_event = custom_message_forgot_password(event)

とやると対応できます。

2. Cognitoユーザープールに1.で作成したファンクションを登録

Lambdaファンクション作成後はCognitoユーザープールのコンソールにいき、トリガーカスタムメッセージから作成したLambdaファンクションを指定します。

スクリーンショット 2018-02-03 20.03.27.png

serverless frameworkではこのようになります。

serverless.yml
<略>
functions:
  cognitoCustomMessage:
    handler: functions/cognito_custom_message.handler
    role: CognitoCustomMessageRole
    events:
      - cognitoUserPool:
          pool: UserPool
          trigger: CustomMessage
resources:
  Resources:
    CognitoUserPoolUserPool:
      <以下略>

設定後、アプリケーションからユーザーを登録してみると、

スクリーンショット_2018-02-03_20_27_35.png

カスタマイズされたメールが届きました。

以上です。

続きを読む

Alexaのスキル開発中、Warning: Application ID is not set

alexa.APP_ID を alexa.appIdに修正すれば解決

結論から言うと仕様が変わったようで、文字列をほんの少し修正するとWarningが消えました。
以下経緯と解説です。

スキルのデバッグ中にWarningを発見

Amazon EchoのAlexaのスキルの処理をAWS Lambdaに書きながらデバッグしていたところ、AWS ClowdWatch Management Consoleにいつの間にか以下のエラーが出ており気になったので調査しました。

2018-01-28T03:58:29.297Z    xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx    Warning: Application ID is not set

アプリケーションIDがセットされていないとのことです。
今回AWS Lambdaの関数作成時に選択できる設計図であるalexa-skill-kit-sdk-factskillを選択して開発を始めましたが、どうも仕様に変更があったらしくこのままだとWarningになるようです。(スキルは一応ちゃんと動くので気づきにくい!)

どこがWarningの原因となっていたのか

alexa-skill-kit-sdk-factskillを選択して生成されるコードは以下のようになっていました。

index.js
// 一部不要なコメント等を省略しています
// 以下10行目〜15行目付近
'use strict';
const Alexa = require('alexa-sdk');
// 以下の行にスキルの`アプリケーション ID`を指定する必要があります(最初はundefinedですね)
const APP_ID = undefined;  // TODO replace with your app ID (OPTIONAL).

// 〜
// (中略)
// 〜

// 以下140目付近〜
exports.handler = function (event, context) {
    const alexa = Alexa.handler(event, context);
    // 以下の行に上の方で設定したアプリケーションIDが入るようですね
    alexa.APP_ID = APP_ID;
    // To enable string internationalization (i18n) features, set a resources object.
    alexa.resources = languageStrings;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

alexa.APP_IDが現在は使われていないようです。
以下のようにalexa.appIdに変更するとWarningが消えます。

- alexa.APP_ID = APP_ID; // こちらは現在Warningとなる
+ alexa.appId = APP_ID; // 正しくはこちら

以上でClowdWatch上でWarningが消えるのを確認しました。

補足:アプリケーションIDを環境変数で指定する

ファイル内にスキルのアプリケーションIDを直書きするのもあれなのでAWS Lambdaにて環境変数を指定して記述するほうが良いかなと思います。

スキルのアプリケーションIDは開発者コンソールのAlexaスキルを管理するページで確認できます。

スクリーンショット 2018-01-31 20.46.28.png

AWS Lambdaの環境変数部分に以下のように記述しました。
環境変数名をALEXA_APP_ID、値に先程のアプリケーションIDを指定します。

スクリーンショット 2018-01-31 21.08.24.png

(TZはタイムゾーンの設定です)

Alexa Skillのindex.jsのAPP_ID部分に環境変数で指定します。

index.js
const APP_ID = process.env.ALEXA_APP_ID;

これでアプリケーションIDを直書きしなくて済みました。

続きを読む

[AWS][Terraform] Terraform で Amazon Inspector を導入する

TerraformAmazon Inspector を導入して、CloudWatch Events で定期実行させるための手順。
Terraform は v0.11.2 を使っています。

Inspector の導入

Inspector を導入するには、Assessment targets (評価ターゲット) と Assessment templates (評価テンプレート) を設定する必要があります。

Assessment targets の設定

Terraform で Assessment targets を設定するには、aws_inspector_resource_group, aws_inspector_assessment_target リソースを使用します。
こんな感じです。

inspector_target.tf
variable "project" { default = "my-big-project" }
variable "stage"   { default = "production" }

resource "aws_inspector_resource_group" "inspector" {
    tags {
        project   = "${var.project}"
        stage     = "${var.stage}"
        inspector = "true"
    }
}

resource "aws_inspector_assessment_target" "inspector" {
    name               = "my-inspector-target"
    resource_group_arn = "${aws_inspector_resource_group.inspector.arn}"
}

aws_inspector_resource_group では、対象となるインスタンスを特定するための条件を記述します。
上記の例だと、以下のタグが設定されているインスタンスを対象にします。

Name Value
project my-big-project
stage production
inspector true

aws_inspector_assessment_target では、aws_inspector_resource_group で定義した条件を元に Assessment targets を作成します。

Assessment templates の設定

Terraform で Assessment templates を設定するには、aws_inspector_assessment_template リソースを使用します。
こんな感じです。

inspector_template.tf
variable "inspector-rule" = {
    type = "list"
    default = [
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-bBUQnxMq",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-gHP9oWNT",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-knGBhqEu"
    ]
}

resource "aws_inspector_assessment_template" "inspector" {
    name       = "my-inspector-template"
    target_arn = "${aws_inspector_assessment_target.inspector.arn}"
    duration   = 3600

    rules_package_arns = [ "${var.inspector-rule}" ]
}

output "assessment_template_arn" {
    value = "${aws_inspector_assessment_template.inspector.arn}"
}

rules_package_arns では、利用可能な Inspector rule package の ARN を設定します。
variable にしておくと、後で rule package を変更したい時に楽ですね。
こんな感じで、使用する rule package を変更できます。

terraform.tfvars
"inspector-rule" = [
    "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu",
    "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-bBUQnxMq"
]

使用できる rule package は、aws-cli で取得してください。

# パッケージ一覧の表示
$ aws --region ap-northeast-1 inspector list-rules-packages
{
    "rulesPackageArns": [
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-bBUQnxMq",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-gHP9oWNT",
        "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-knGBhqEu"
    ]
}
# 詳細を確認
$ aws --region ap-northeast-1 inspector describe-rules-packages 
  --rules-package-arns "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu"
{
    "rulesPackages": [
        {
            "description": "The CIS Security Benchmarks program provides well-defined, un-biased and consensus-based industry best practicesto help organizations assess and improve their security.nnThe rules in this package help establish a secure configuration posture for the following operating systems:nn  -   Amazon Linux version 2015.03 (CIS benchmark v1.1.0)n  n    ",
            "version": "1.0",
            "name": "CIS Operating System Security Configuration Benchmarks",
            "arn": "arn:aws:inspector:ap-northeast-1:406045910587:rulespackage/0-7WNjqgGu",
            "provider": "Amazon Web Services, Inc."
        }
    ],
    "failedItems": {}
}

参考URL: Terraform v0.8.5でAWS Inspectorに対応します

これで terraform apply すれば Assessment targets, templates が作成されます。

動作確認

実際に Inspector が実施されるか確認して見ましょう。

$ aws inspector start-assessment-run 
  --assessment-template-arn arn:aws:inspector:ap-northeast-1:************:target/0-xxxxxxxx/template/0-xxxxxxxx
{
    "assessmentRunArn": "arn:aws:inspector:ap-northeast-1:************:target/0-xxxxxxxx/template/0-xxxxxxxx/run/0-7WNjqgGu"
}

実行状況の確認は aws inspector describe-assessment-runs

$ aws inspector describe-assessment-runs 
  --assessment-run-arns arn:aws:inspector:ap-northeast-1:************:target/0-QOvPswHA/template/0-uCIUy636/run/0-n9nnWOem

CloudWatch Events Schedule による定期実行

当初は CloudWatch Events で定期実行するには Lambda から呼び出すようにしなければいけませんでした。
しかし、CloudWatch Event から直接 Inspector を実行できるようになったため、Lambda を使用しなくても aws_cloudwatch_event_targetaws_cloudwatch_event_rule だけで定期実行設定が可能です。

CloudWatch Events で使用する IAM ロールの作成

まずは、CloudWatch Events で使用する IAM ロールを作ります。

cloudwatch-events-iam-role.tf
esource "aws_iam_role" "run_inspector_role" {
    name               = "cloudwatch-events-run-inspector-role"
    assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

resource "aws_iam_policy" "run_inspector_policy" {
    name        = "cloudwatch-events-run-inspector-policy"
    description = ""
    policy      = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "inspector:StartAssessmentRun"
            ],
            "Resource": "*"
        }
    ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "run_inspector_role" {
    role       = "${aws_iam_role.run_inspector_role.name}"
    policy_arn = "${aws_iam_policy.run_inspector_policy.arn}"
}

CloudWatch Events への登録

CloudWatch Events に登録するために aws_cloudwatch_event_target リソースと aws_cloudwatch_event_rule を作りましょう。

cloudwatch-events.tf
variable "schedule"    { default = "cron(00 19 ? * Sun *)" }

resource "aws_cloudwatch_event_target" "inspector" {
  target_id = "inspector"
  rule      = "${aws_cloudwatch_event_rule.inspector.name}"
  arn       = "${aws_inspector_assessment_template.inspector.arn}"
  role_arn  = "${aws_iam_role.run_inspector_role.arn}"
}

resource "aws_cloudwatch_event_rule" "inspector" {
  name        = "run-inspector-event-rule"
  description = "Run Inspector"
  schedule_expression = "${var.schedule}"
}

schedule_expression の cron() は UTC で設定する必要があるので注意してください。
記述方法は、以下を参考に
参考URL: Rate または Cron を使用したスケジュール式

EC2 への IAM Role の設定と、ユーザーデータによる Inspector エージェントのインストール

評価ターゲットとなる EC2 には、Inspector エージェントがインストールされていて、適切なインスタンスロールが設定されている必要があります。
導入するには、こんな感じ

インスタンスロール

inspectora エージェントを使用するために必要なポリシーをアタッチしたインスタンスロールの作成はこんな感じです。

ec2-instance-role.tf
resource "aws_iam_role" "instance_role" {
    name               = "my-ec2-role"
    path               = "/"
    assume_role_policy = <<POLICY
{
"Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

resource "aws_iam_instance_profile" "instance_role" {
    name = "my-ec2-role"
    role = "${aws_iam_role.instance_role.name}"
}

resource "aws_iam_policy" "inspector" {
    name        = "my-ec2-iam-policy-inspector"
    description = ""
    policy      = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeAvailabilityZones",
                "ec2:DescribeCustomerGateways",
                "ec2:DescribeInstances",
                "ec2:DescribeTags",
                "ec2:DescribeInternetGateways",
                "ec2:DescribeNatGateways",
                "ec2:DescribeNetworkAcls",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DescribePrefixLists",
                "ec2:DescribeRegions",
                "ec2:DescribeRouteTables",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcEndpoints",
                "ec2:DescribeVpcPeeringConnections",
                "ec2:DescribeVpcs",
                "ec2:DescribeVpn",
                "ec2:DescribeVpnGateways",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeLoadBalancerAttributes",
                "elasticloadbalancing:DescribeRules",
                "elasticloadbalancing:DescribeTags",
                "elasticloadbalancing:DescribeTargetGroups",
                "elasticloadbalancing:DescribeTargetHealth"
            ],
            "Resource": "*"
        }
    ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "inspector" {
    role       = "${aws_iam_role.instance_role.name}"
    policy_arn = "${aws_iam_policy.inspector.arn}"
}

ユーザーデータによる inspector エージェントのインストール

OS は Amazon Linux を想定してます。
ユーザーデータに書いておけば、インスタンス起動直後に inspector エージェントインストールできますね。
参考URL: Amazon Inspector エージェントをインストールする

ssh-key.pemssh-key.pem.pubssh-keygen で適当に作っておきましょう。

ec2.tf
## AMI
##
data "aws_ami" "amazonlinux" {
    most_recent = true
    owners      = ["amazon"]

    filter {
        name   = "architecture"
        values = ["x86_64"]
    }

    filter {
        name   = "root-device-type"
        values = ["ebs"]
    }

    filter {
        name   = "name"
        values = ["amzn-ami-hvm-*"]
    }

    filter {
        name   = "virtualization-type"
        values = ["hvm"]
    }

    filter {
        name   = "block-device-mapping.volume-type"
        values = ["gp2"]
    }
}

## SSH Key Pair
##
resource "aws_key_pair" "deployer" {
    key_name   = "ssh-key-name"
    public_key = "${file(ssh-key.pem.pub)}"
}

## EC2
##
resource "aws_instance" "ec2" {
    ami                         = "${data.aws_ami.amazonlinux.id}"
    instance_type               = "t2.micro"
    key_name                    = "${aws_key_pair.deployer.key_name}"
    iam_instance_profile        = "${aws_iam_instance_profile.instance_role.name}"

    user_data                   = <<USERDATA
#!/bin/bash
# install inspector agent
cd /tmp
/usr/bin/curl -O https://d1wk0tztpsntt1.cloudfront.net/linux/latest/install
/bin/bash install -u false
/bin/rm -f install
USERDATA

    tags {
        project   = "${var.project}"
        stage     = "${var.stage}"
        inspector = "true"
    }
}

現場からは以上です。

続きを読む