LanScope Catの必要なアラームをGASでSlackに送信する

# はじめに
こんにちは、コーポレートエンジニアの __まいまい__ です。
この記事は __LANSCOPE Advent Calendar 2021__ と __GAS Advent Calendar 2021__ 用に執筆したものです。

<https://adventar.org/calendars/6525>

<https://qiita.com/advent-calendar/2021/gas>

# 概要
LanScope Cat の「アラーム通知」を利用すると、アラームが検知された際にメール送信してくれます。
例えば、私の職場では業務でUSBメモリを使うスタッフがいるため、「外部デバイス書込アラーム」(要確認)を有効にしてUSBメモリなどへの書込を監視しています。

ただし、受信したメールでアラームを確認するのではなく、**必要なアラームのみ**を Google Apps Script (以下GAS) で Slack のチャンネルに送信し、Slack でアラームを確認して必要な対応をおこなっています。

理由は下記の通りです。

- 頻繁にメールチェックすることがなくなり、メールだとアラームに気付くのが遅れる
- LanScope Cat の設定で対象/対象外とするデバイスなどを指定しようとしたが、正常に機能していなかった
- いろいろなアラームが届くので、早急に対応が必要なアラームかどうか逐一メールを見て判別するのが大変
- Slack だと通知に気付きやすいのですぐ対応できる
- Slack だと対応者や対応内容・対応状況を他のメンバーに共有しやすい

今回の記事では、どのようにして必要なアラームを検索し、Slack に送信しているかを共有したいと思います。

# 構成
今回の記事における構成は以下の通りです。

- オンプレミス版 LanScope Cat 9.4.0
    - Azure 上に構成
- LanScope Cat アラーム通知
    - 外部デバイス書込アラーム
    - 他のアラームも有効にしています
- Google Workspace
    - LanScope Cat からのアラームを Gmail でメール受信
    - GAS で必要なアラームを絞り込み Slack にメール送信

大まかな流れは以下の構成図のようになります。

|![重要なアラームをSlackに通知.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234372/007bf27a-02e8-e670-61b2-33dd31311018.png "構成図")|
|:--|

1. MR がインストールされているデバイスでアラームが検知される (構成図の①)
1. LanScope Catが指定されたメールアドレス宛にアラーム通知を送信する (構成図の②)
1. タイムトリガーで設定した時間毎に GAS スクリプトが実行される

GAS スクリプトの大まかな処理は以下のようになります。

1. 受信メールの中に重要なアラームがあるか検索する (構成図の③)
1. ある場合はそれらを Slack の指定されたチャンネルに送信する (構成図の④)

# GAS の注意点
GAS には実行回数・時間・容量に関する制限・上限が設定されています。
この制限・上限は Google Workspace Accounts と通常の Gmail アカウントなどで違いがあります。
今回の設定で注意した方が良い制限をピックアップしておきます。

| Feature | Consumer (e.g., gmail.com) and G Suite free edition | Google Workspace accounts |
|:--|:-:|:-:|
| Email read/write (excluding send) | 20,000 / day | 50,000 / day |
| Email body size | 200 KB / msg | 400 KB / msg |
| Triggers total runtime | 90 min / day | 6 hr / day |
|Simultaneous executions|60|60|

詳細は公式ドキュメントを参照してください。

<https://developers.google.com/apps-script/guides/services/quotas>

# 設定方法

## LanScope Cat の設定
アラームの設定方法は LANSCOPE のポータルサイトにマニュアルがありますので、そちらを参照してください。
Azure や AWS 上でオンプレミス版 LanScope Cat を構成する方法もポータルサイトにマニュアルがあります。

## Slack チャンネルのメールアドレスの取得
今回の方法は Slack チャンネルのメールアドレスを取得し、そのメールアドレスにメール送信することで実現しています。
以下の記事を参考にして Slack チャンネルのメールアドレスを取得してください。

<https://slack.com/intl/ja-jp/help/articles/206819278-Slack-%E3%81%AB%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%92%E9%80%81%E4%BF%A1%E3%81%99%E3%82%8B#h_01F4WDZG8RTCTNAMR4KJ7D419V>

取得したメールアドレスは `x-bacadmfdqskgrif5gsgij714z2@hogehoge.slack.com` のような形式になります。

## GAS の構成

### GAS の作成

<https://script.google.com/> にアクセスします。
新しいプロジェクトを作成し、スクリプトを書いて保存します。

以下に GAS スクリプトのサンプルを紹介します。

```JavaScript:GAS&#x20;スクリプト&#x20;サンプル
function sendAlarm() {
  const to_adr = "hogehoge@hoge.slack.com"; // Slack のチャンネルのメールアドレスを設定
  const name = "LanScope Cat - 外部デバイス書込アラート"; // Slack で表示される送信者名
  const add_label = GmailApp.getUserLabelByName("Slack通知済み"); // Slack に送信したメールに付与するラベル

  // 検索クエリを設定する
  var query = "from:(LSCアラート通知(Azure)) subject:(書込) -{\\server01 \\server02} -(label:slack通知済み) newer_than:1d";
  
  // 受信メールを検索する 
  var threads = GmailApp.search(query);

  // 検索クエリに該当する受信メールの内容を Slack に送信する
  for(var i=0; i<threads.length; i++) {
    var k = threads[i].getMessageCount() - 1;
    var subject = threads[i].getMessages()[k].getSubject();
    var body = threads[i].getMessages()[k].getPlainBody();

    // body のサイズが 400KB 以上の場合の処理

    // Slack チャンネルにメールを送信する
    MailApp.sendEmail({to:to_adr, subject:subject, name:name, body:body});

    // 送信したらラベルを付与する
    threads[i].addLabel(add_label);
  }
}
```

スクリプトについて解説していきます。

#### 通知先 Slack チャンネルのメールアドレス設定

```javascript
const to_adr = "hogehoge@hoge.slack.com"; // Slack のチャンネルのメールアドレスを設定
```

先程取得した Slack チャンネルのメールアドレスを `to_adr` に設定します。

#### Gmail ラベルの設定

```javascript
const add_label = GmailApp.getUserLabelByName("Slack通知済み"); // Slack に送信したメールに付与するラベル
```

通知したメールにはラベルを付与して、次回の検索対象から除外するようにしています。
この例では Gmail で `Slack通知済み` というラベルを作成しておき、それを `add_label` に設定しています。

:::note warn
Gmail にラベルが存在しないとエラーになりますので、事前にラベルを作成しておいてください。
:::

#### 検索クエリの設定と検索

```javascript
// 検索クエリを設定する
var query = "from:(LSCアラート通知(Azure)) subject:(書込) -{\\server01 \\server02} -(label:slack通知済み) newer_than:1d";

// 受信メールを検索する 
var threads = GmailApp.search(query);
```

`query` に検索クエリを設定し、その内容で Gmail を検索し、引っかかったメールを `threads` に設定しています。

- LanScope Cat から届くメールの差出人名を `LSCアラート通知(Azure)` と設定してあるため、`from:(LSCアラート通知(Azure))` を検索クエリに追加しています。
- Slack 通知させたいメールの Subject には `書込` という文字列が含まれているため、`subject:(書込)` を検索クエリに追加しています。
- 特定のオンプレサーバー (例: `\\server01` `\\server02`) などへの書込アラームは Slack 通知対象から除外したいため、`-{\\server01 \\server02}` を検索クエリに追加しています。
- 既に Slack に通知したメールには `LSCアラート通知(Azure)` というラベルが付与されているので、除外するために `-(label:slack通知済み)` を検索クエリに追加しています。
- 1日以内に受信したメールのみを対象にするため、`newer_than:1d` を検索クエリに追加しています。

#### Slack にメール送信する

```javascript
var k = threads[i].getMessageCount() - 1;
var subject = threads[i].getMessages()[k].getSubject();
var body = threads[i].getMessages()[k].getPlainBody();

// body のサイズが 400KB 以上の場合の処理

// Slack チャンネルにメールを送信する
MailApp.sendEmail({to:to_adr, subject:subject, name:name, body:body});
```

検索に引っかかったメールを順番に Slack にメール送信します。
__GAS の注意点__ のところでも書きましたが、`MailApp.sendEmail` で送信できる `body` サイズには制限があります。
このため、自分の環境に適した処理を `// body のサイズが 400KB 以上の場合の処理` の下に追記してエラー回避してください。

#### Slack に通知したメールにラベルを付与する

```javascript
// 送信したらラベルを付与する
threads[i].addLabel(add_label);
```

Slack にメール送信したメールに `add_label` で指定したラベルを付与します。

### トリガーの設定

GAS が作成できたら、トリガーを設定します。
私の場合、GAS のマイトリガーで10分おきに `sendAlarm` が実行されるトリガーを設定しています。

![トリガー設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234372/825cc444-9f49-8bfe-c0af-009440f01554.png "GASのトリガー設定画面")

# Slack 通知された例

実際に Slack にメール送信されたアラームのスクリーンショットです。

![アラーム画面(Slack).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234372/6a6e2cc0-b352-6f2f-ce24-e8dec1b55933.png "Slackに通知された書込アラーム")

# まとめ

以上、GAS を使って必要なアラームを検索し、Slack に送信する方法を紹介させていただきました。
LanScope Cat の使い方というよりも GAS の紹介事例になりましたが、何かの参考になれば幸いです。
また、今回は GAS を利用しましたが、Gmail の「フィルター」を使って Slack にメール転送することも可能です。
今回の GAS の例だとトリガーが10分おきなので最長10分後の Slack 通知になりますが、Gmail のフィルターでメール転送する場合はほぼリアルタイムに近い形で転送できると思います。