条件
- BoxをBusiness以上で利用している
目的
- Boxの管理コンソールには出力されない詳細な監査ログを取りたい
- Downloadされた
- ファイルを更新した
- 外部コラボレーターを作成した
- etc .....
監査ログの置き場
-
AWS ElasticsearchService
- CloudWatchLogsでも良かったけど、可視化の観点で Kibanaを使ってみたかった
実行環境とフロー
- AWS
- CloudWatch ルールでLambda(Node.js)を定周期起動
- BoxApiを利用して、取得した監査ログをElasticsearchServiceに投げ込む
- Kibanaで簡単に可視化
Box側の設定
- BoxApiを利用するにはBoxアプリを作成する必要がある。
- 詳しい解説記事が既にQiitaにあるのでこちら参照して作成してください(ステップ4まで実行すれば大丈夫)
AWS側の事前準備
- ElasticsearchServiceの作成
- indexまで作っておきましょう
- S3バケットの用意
- BoxAPIで監査ログを取得する際の次回読み込みポジションを保存しておく領域です。
当然S3じゃなくても大丈夫ですが今回はS3で行きます!
- credentials の用意
- IAMユーザのcredentialsを用意
-
のS3バケットへのアクセス権
-
- IAMユーザのcredentialsを用意
ローカルで試してみよう
npmインストール
- aws-sdk
- box-node-sdk
- BoxのNode.jp用SDK
- elasticsearch
- elasticsearchのNode.jp用SDK
- dotenv
- さくっとやりたいので
serverless
は使わない - Nodeの環境設定モジュール これでローカルと本番(lambda上の環境変数)を管理する
- さくっとやりたいので
$ mkdir box-auditlogs-node-es
$ cd box-auditlogs-node-es
$ npm install aws-sdk box-node-sdk dotenv elasticsearch
ソース
- 下記構成でそれぞれ作成してください
tree
.
├── index.js
├── main.js
├── .env
└── package.json
index.js
index.js
'use strict';
const aws = require('aws-sdk');
const elasticsearch = require('elasticsearch');
const BoxSDK = require('box-node-sdk');
// AWS Setting
aws.config.region = process.env.AWS_REGION;
if (process.env.LOCAL) { // This part is unnecessary when not using profile
var credentials = new aws.SharedIniFileCredentials({profile: process.env.LOCAL_PROFILE});
aws.config.credentials = credentials;
}
// Box Setting
const boxConfig = JSON.parse(process.env.BOX_CONFIG);
boxConfig.boxAppSettings.appAuth.keyID = boxConfig.boxAppSettings.appAuth.publicKeyID;
const sdk = new BoxSDK(boxConfig.boxAppSettings);
const client = sdk.getAppAuthClient('enterprise', boxConfig.enterpriseID);
// Elasticsearch
var ES_INDEX = process.env.ES_INDEX; // Elasticsearch index name
var ES_TYPE = process.env.ES_TYPE; // Elsticsearch index type name
var ES_CLIENT = new elasticsearch.Client({
host: process.env.ES_ENDPOINT
});
// S3 Setting
var s3 = new aws.S3();
var bucketName = process.env.S3_NEXT_POSITION_BUCKETNAME;
var fileName = 'next_position.txt';
var contentType = 'text/plain';
exports.handler = (event, context, callback) => {
main();
};
async function main() {
// get initial value [next_position] from S3 and generate parameters of BoxApi
const s3_read_params = {
Key: fileName,
Bucket: bucketName,
};
var next_position = null;
var box_params = {
stream_type: 'admin_logs',
limit: 500
}
try {
const res = await s3.getObject(s3_read_params).promise();
next_position = res.Body.toString('ascii');
box_params.stream_position = next_position;
} catch(err) {
console.error(err);
box_params.created_after = process.env.BOX_START_DATE;
}
// Call BoxApi
client.events.get(box_params, function(err, stream) {
if (err) {
console.log("error");
}
}).then(event => {
var next_position = event.next_stream_position;
// Send to Elasticsearch
if (event.chunk_size != 0){
sendToES(event.entries);
}
// Upload the next_position file to S3
var params = {
Key: fileName,
Body: next_position,
Bucket: bucketName,
ContentType: contentType,
};
s3.putObject(params, function(err, data) {
if (err) {
console.log("Error uploading data: ", err);
} else {
console.log("Successfully uploaded data to " + bucketName + "/" + fileName);
}
});
});
}
// bulk send to Elasticsearch
function sendToES(records){
var searchRecords = [];
for(var i = 0; i < records.length; i++){
var record = records[i];
var header = {
"index":{
"_index": ES_INDEX,
"_type": ES_TYPE,
}
};
var searchRecord = {};
searchRecords.push(header);
searchRecords.push(record);
};
ES_CLIENT.bulk({
"body": searchRecords
}, function(err, resp){
if(err){
console.log(err);
}else{
console.log(resp);
};
});
};
main.js
- ローカルでlambdaっぽく呼ぶやつ
main.js
var event = {
version: '0',
id: 'XXXXXXXX-XXXXX-XXXX-XXXXX-XXXXXXXX',
'detail-type': 'Scheduled Event',
source: 'aws.events',
account: 'XXXXXXXXXX',
time: '2019-01-30T06:27:04Z',
region: 'ap-northeast-1',
resources:
[ 'arn:aws:events:ap-northeast-1:XXXXXXXXXX:rule/box-auditlog-exec' ],
detail: {}
};
var context = {
invokeid: 'invokeid',
done: function(err,message){
return;
}
};
var lambda = require("./index");
lambda.handler(event,context);
.env
- ローカル環境変数を記述するので、こちらに自分の環境を記載してください
.env
BOX_CONFIG={ "boxAppSettings": { "clientID": " } }
BOX_START_DATE=2019-01-01T00:00:00+09:00
AWS_REGION=ap-northeast-1
S3_NEXT_POSITION_BUCKETNAME=mybucket
LOCAL=true
LOCAL_PROFILE=AWS_credentials_profile_name
ES_INDEX=Elasticsearch_Index_name
ES_TYPE=Elasticsearch_Type_name
ES_ENDPOINT=https://xxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com/
- BOX_CONFIG:これ
- BOX_START_DATE:Boxを利用し始めた日時
- S3_NEXT_POSITION_BUCKETNAME:AWS側の事前準備で作成したバケット名
-
credentialを利用する場合
LOCAL:trueのままで大丈夫 -
credentialを利用する場合
LOCAL_PROFILE:credentialのprofile名 - ES_INDEX:Elasticsearchのインデックス名
- ES_TYPE:ElasticsearchのType名
- ES_ENDPOINT:Elasticsearchのエンドポイント
実行
$ node -r dotenv/config main.js
うまくいけばKibanaでこんな感じで見ることができる
- グラフとかは適当に自分で作ってください

AWSにアップして動かしてみよう
事前準備
AWSへデプロイ
- 今回はS3を経由しているが、S3を経由する必要は特に無い
$ zip -r box-auditlogs-node-es.zip index.js node_modules
$ aws s3 cp ./box-auditlogs-node-es.zip s3://[mybucket]/box-auditlogs-node-es.zip --profile [myprofile]
$ aws lambda update-function-code --function-name box-auditlogs-node-es --s3-bucket [mybucket] --s3-key box-auditlogs-node-es.zip --publish --profile [myprofile]
実行
ソース置き場
こちらにソース置いておきます。(Node.jpを書くの初めてなので、ソース汚かったらPullReqください)