OneloginのActivty EventログをAWS S3に投げ込む

## 条件 
* [Onelogin](https://www.onelogin.com/jp)をEnterprise以上で利用している

## 目的
* OneloginのActivty-Eventsを取りたい
    * Oneloginにログインした
    * SAMLで連携している先のアプリケーションにログインした
    * Oneloginの設定、構成を変更した
    * etc .....

### 監査ログの置き場
* [AWS S3](https://aws.amazon.com/jp/s3/)
    * [AWS ElasticsearchServiceに突っ込みたい人はこちらを参考に修正してね](https://qiita.com/kenji-toforone/items/ec82e7fad5c3da8429c5)
### 実行環境とフロー
* AWS
    * CloudWatch　ルールでLambda(Node.js)を定周期起動
    * OneloginAPIを利用して、取得した監査ログをS3に投げ込む
    * 後は好きに[Athena](https://aws.amazon.com/jp/athena/)で可視化するなりなんなりと
    
## Onelogin側の設定(Credentialの用意)
* OneloginAPIを利用するにはCredentialを作成する必要がある。
* 管理画面の`Developers` > `API Credentials` 
<img width="713" alt="Credential0.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164563/40499026-b5df-13f7-59f5-3dbfef9a516f.png">
* `New Credential`
<img width="648" alt="Credential1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164563/85076d27-47ec-9e4c-c559-3c6791d988a7.png">
* Credentialに適当な名前をつけて `Read all` を選択して `Save`
<img width="442" alt="Credential2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164563/4e295e9a-9a3d-95ef-b1c8-156251bbb8d2.png">
* 作成された `Client ID` と `Client Secret` をメモって `Done`
<img width="375" alt="Credential3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164563/73fdde2b-13d6-ab24-db6e-456c7bf29e80.png">
* 出来ました
<img width="695" alt="Credential4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164563/90cbbeb3-a1cc-7316-d168-7494ed11cc6c.png">


## AWS側の事前準備
* S3バケットの用意
    * Oneloginのイベントを格納するバケット。
* credentials の用意
    * IAMユーザのcredentialsを用意
        * :arrow_up: のS3バケットへのアクセス権

## ローカルで試してみよう
### npmインストール
* aws-sdk
* request
* request-promise
* dotenv
    * さくっとやりたいので`serverless`は使わない
    * [Nodeの環境設定モジュール](https://qiita.com/ueokande/items/80048f886082fea5b776)　これでローカルと本番(lambda上の環境変数)を管理する

```
$ mkdir onelogin-auditlogs-s3
$ cd onelogin-auditlogs-s3
$ npm install aws-sdk request request-promise dotenv
```

### ソース
* 下記構成でそれぞれ作成してください

#### tree
```
.
├── index.js
├── main.js
├── .env
└── package.json
```

#### index.js
```javascript:index.js
'use strict';

const aws = require('aws-sdk');
const request = require('request-promise');
require('dotenv').config();

// AWS Setting
aws.config.region = process.env.AWS_REGION;
if (process.env.LOCAL) {
    var credentials = new aws.SharedIniFileCredentials(
        {profile: process.env.LOCAL_PROFILE});
    aws.config.credentials = credentials;
}

// OneLogin Setting
const ONELOGIN_CLIENTID = process.env.ONELOGIN_CLIENTID;
const ONELOGIN_SECRET = process.env.ONELOGIN_SECRET;

// S3 Setting
var s3 = new aws.S3();
var bucketName = process.env.S3_BUCKETNAME;
var fileName = '';
var contentType = 'application/json';

exports.handler = (event, context, callback) => {
    main();
};

// get Onelogin AccessToken
async function getAccessToken() {
    var accessToken;
    let options = {
        method: 'POST',
        uri: 'https://api.us.onelogin.com/auth/oauth2/v2/token',
        auth: {
            user: ONELOGIN_CLIENTID,
            pass: ONELOGIN_SECRET
        },
        json: {
            grant_type: 'client_credentials'
        }
    }
    await request(options)
        .then(function (body) {
            accessToken = body.access_token;
        })
        .catch(function (err) {
            console.log(err);
        });
    return accessToken;
}

// get Onelogin event log recursively
async function getEventsRecursively(accessToken,eventGetUrl) {
    let options = {
        method: 'GET',
        uri: eventGetUrl,
        auth: {
            bearer: accessToken
        }
    }
    var fileIndex = 0;
    var next_link = 'not null';
    while (next_link!=null) {
        await request(options).then(function (body) {
            var jBody = JSON.parse(body);
            // Put this data somewhere like ElasticSearch,S3 blah blah blah.
            var body = '';
            jBody.data.forEach(function( value ) {
                body = body + JSON.stringify(value) + '\n';
            });
            console.log(body);
            // Case : Using S3
            // Upload the BodyData file to S3
            var params = {
                Key: fileName + ('0000000000' + fileIndex).slice(-10),
                Body: body,
                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);
                }
            });
            // Check next_link and get recursively
            next_link = jBody.pagination.next_link;
            fileIndex++;
            options = {
                method: 'GET',
                uri: next_link,
                auth: {
                    bearer: accessToken
                }
            }
        })
        .catch(function (err) {
            console.log(err);
            next_link = null;
        });
    }
}

async function main() {
    // create Since Until Date
    // this case, specify from 00:00 to 23:59 the previous day
    var untilDate = new Date();
    untilDate.setDate(untilDate.getDate() -1);
    var sinceDate = new Date(untilDate.setHours(0, 0, 0, 0));
    untilDate.setHours(23, 59, 59, 999);
    // set S3 FileName
    fileName = sinceDate.toISOString();
    // make Url
    var eventGetUrl = 'https://api.us.onelogin.com/api/1/events?since='+sinceDate.toISOString()+'&until=' + untilDate.toISOString();
    // get Onelogin AccessToken
    var accessToken = await getAccessToken();
    // Using an access token, get Onelogin event log recursively
    await getEventsRecursively(accessToken,eventGetUrl);
}
```

#### main.js
* ローカルでlambdaっぽく呼ぶやつ

```javascript: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/onelogin-auditlog-exec' ],
    detail: {}
};

var context = {
    invokeid: 'invokeid',
    done: function(err,message){
        return;
    }
};

var lambda = require("./index");
lambda.handler(event,context);
```

#### .env
* ローカル環境変数を記述するので、こちらに自分の環境を記載してください

```config:.env
ONELOGIN_CLIENTID=XXXXXXXXXXXXXXXXXXXXXXXX
ONELOGIN_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
AWS_REGION=ap-northeast-1
LOCAL=true
LOCAL_PROFILE=AWS_credentials_profile_name
S3_BUCKETNAME=mybucket
```

* ONELOGIN_CLIENTID:Credentialで作った`Client ID`
* ONELOGIN_SECRET:Credentialで作った`Client Secret`
* AWS_REGION:AWSのREGION
* `credentialを利用する場合` LOCAL:trueのままで大丈夫
* `credentialを利用する場合` LOCAL_PROFILE:credentialのprofile名
* S3_BUCKETNAME:AWS側の事前準備で作成したバケット名

### 実行
```
$ node main.js
```

## うまくいけばS3にこんな感じでアップされます
<img width="845" alt="S31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164563/50cacaa1-c3fc-9b42-d1ce-11424f3e84a5.png">


* Athenaで読むとこんな感じ
<img width="1024" alt="S32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164563/3d52a956-2788-f53f-febb-0074448fc776.png">

## AWSにアップして動かしてみよう
* [こちらを参考にパラメータを変えてやってくださいませ](https://qiita.com/kenji-toforone/items/ec82e7fad5c3da8429c5#aws%E3%81%AB%E3%82%A2%E3%83%83%E3%83%97%E3%81%97%E3%81%A6%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86)
* 今回のソースは起動時刻の`前日00:00:00-23:59:59`のEventを取得するので`00:01`に起動するようにCloudwatchルールに設定すると良いと思います。

## ソース置き場
[こちら](https://github.com/kenji-toforone/onelogin-auditlogs-node)にソース置いておきます。（Node.jpを書き慣れてないのでソースが汚かったらPullReqください）