Skip to content

Instantly share code, notes, and snippets.

@dzeyelid
Last active January 23, 2018 12:22
Show Gist options
  • Save dzeyelid/c4aa81690cd767e93f5251a67f5697f9 to your computer and use it in GitHub Desktop.
Save dzeyelid/c4aa81690cd767e93f5251a67f5697f9 to your computer and use it in GitHub Desktop.
Node-RED で sshd のログを監視するフローのサンプルです。

概要

Node-RED で sshd のログを監視するフローのサンプルです。

このフローは、Ubuntu の /var/log/auth.log を監視し、不正なSSHログインを検知します。検知すると Slack へ通知し、不正ログイン状況を集計してダッシュボードに表示します。

動作環境

使い方

準備

インストール

Node-RED および、記録に使う SQLite をインストールします。

# Node.js をインストールする
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs

# Node-RED をインストールする
sudo npm install -g --unsafe-perm node-red

# SQLite コマンドをインストールする
sudo apt-get install sqlite

データベースの準備

monitor-logs.db を作成し、その中に invalid_users テーブルを作成します。

# データベースファイルを新規作成し、CREATE TABLE を実行する
sqlite3 ~/monitor-logs.db

sqlite> CREATE TABLE invalid_users (timestamp TIMESTAMP NOT NULL PRIMARY KEY, user VARCHAR(32) NOT NULL, address_from VARCHAR(15) NOT NULL);
sqlite> .exit

Node-RED の準備

Node-RED を起動します。

# Node-RED を起動する
node-red

http://<サーバーIP>:1880 にアクセスし、Node-RED の右上のメニューから「パレット管理」を開き、下記のノードを追加します。

  • node-red-node-sqlite
  • node-red-dashboard

Node-RED の右上メニューの「読み込み」>「クリップボード」を開き、上記 flow.json の JSON を貼り付けて読み込みます。

Slack 通知の準備

下記を参考に Incomming WebHooks を追加します。

  1. 「Slack に通知」ノードWのプロパティを開き、「URL」に Slack の WebHook URL を入力して完了ボタンをクリックしてください。
  2. 「通知メッセージを作成する」ノードのプロパティを開き、適宜テンプレートを編集してください。(http://localhost:1880/ui/ は Node-RED ダッシュボードのURLです。アドレスを適宜設定してください。)
{
    "text": "Attacked from `{{ payload.address_from }}` as `{{ payload.user }}` !",
    "attachments": [
        {
            "fallback": "Click <http://localhost:1880/ui/|here> to see detail.",
            "actions": [
                {
                    "type": "button",
                    "text": "See all :thinking_face:",
                    "url": "http://localhost:1880/ui/"
                }
            ]
        }
    ]
}

デプロイして、様子をみましょう。

[
{
"id": "987bb09c.ac58c",
"type": "tab",
"label": "auth.log を監視",
"disabled": false,
"info": ""
},
{
"id": "15620535.c0744b",
"type": "tail",
"z": "987bb09c.ac58c",
"name": "/var/log/auth.log を監視する",
"filetype": "text",
"split": true,
"filename": "/var/log/auth.log",
"x": 160,
"y": 120,
"wires": [
[
"5e1daef9.74fe5"
]
]
},
{
"id": "ce5f25c4.096c38",
"type": "http request",
"z": "987bb09c.ac58c",
"name": "Slack に通知",
"method": "POST",
"ret": "txt",
"url": "https://hooks.slack.com/services/<your web hook url of slack>",
"tls": "",
"x": 410,
"y": 360,
"wires": [
[]
]
},
{
"id": "a87de39a.9f43a",
"type": "comment",
"z": "987bb09c.ac58c",
"name": "auth.log を監視",
"info": "",
"x": 120,
"y": 60,
"wires": []
},
{
"id": "5e1daef9.74fe5",
"type": "switch",
"z": "987bb09c.ac58c",
"name": "Invalid user のログを抽出する",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "regex",
"v": "Invalid user (.+) from (.+)$",
"vt": "str",
"case": false
}
],
"checkall": "true",
"outputs": 1,
"x": 210,
"y": 180,
"wires": [
[
"73b4c11f.ade44"
]
]
},
{
"id": "ad708f47.ac2f8",
"type": "function",
"z": "987bb09c.ac58c",
"name": "SQL クエリを作成する",
"func": "var items = msg.payload;\n\nmsg = {\n \"topic\": \"INSERT INTO invalid_users (timestamp, user, address_from) VALUES(?,?,?);\",\n \"payload\" : [items.timestamp, items.user, items.address_from]\n};\n\nreturn msg;\n",
"outputs": 1,
"noerr": 0,
"x": 190,
"y": 300,
"wires": [
[
"2294027a.dc791e"
]
]
},
{
"id": "2294027a.dc791e",
"type": "sqlite",
"z": "987bb09c.ac58c",
"mydb": "e3c4fac5.921408",
"name": "SQLite へ書き込む",
"x": 430,
"y": 300,
"wires": [
[
"a019e6c6.25b418"
]
]
},
{
"id": "ce817f74.fcbfa",
"type": "template",
"z": "987bb09c.ac58c",
"name": "通知メッセージを作成する",
"field": "payload",
"fieldType": "msg",
"format": "json",
"syntax": "mustache",
"template": "{\n \"text\": \"Attacked from `{{ payload.address_from }}` as `{{ payload.user }}` !\",\n \"attachments\": [\n {\n \"fallback\": \"Click <http://localhost:1880/ui/|here> to see detail.\",\n \"actions\": [\n {\n \"type\": \"button\",\n \"text\": \"See all :thinking_face:\",\n \"url\": \"http://localhost:1880/ui/\"\n }\n ]\n }\n ]\n}",
"output": "json",
"x": 200,
"y": 360,
"wires": [
[
"ce5f25c4.096c38"
]
]
},
{
"id": "73b4c11f.ade44",
"type": "function",
"z": "987bb09c.ac58c",
"name": "ログを成型する",
"func": "var log = msg.payload;\nvar re = /^(\\w{3} \\d+) (\\d+:\\d+:\\d+) .+: Invalid user (.+) from (.+)$/;\nvar values = re.exec(log);\n\nvar tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\nvar datetime = values[1] + \" \" + new Date().getFullYear() + \" \" + values[2] + \" \" + tz;\n\nmsg.payload = {\n // \"timestamp\": values[1] + \" \" + values[2],\n \"timestamp\": datetime,\n // \"timestamp\": Date.parse(datetime),\n \"user\": values[3],\n \"address_from\": values[4]\n};\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 160,
"y": 240,
"wires": [
[
"ad708f47.ac2f8",
"ce817f74.fcbfa"
]
]
},
{
"id": "bceec386.fd9e",
"type": "comment",
"z": "987bb09c.ac58c",
"name": "SSH 不正アクセス状況を表示",
"info": "",
"x": 800,
"y": 340,
"wires": []
},
{
"id": "a019e6c6.25b418",
"type": "link out",
"z": "987bb09c.ac58c",
"name": "update-graph",
"links": [
"a3610115.91eea",
"a6b1ba01.ca99e8"
],
"x": 575,
"y": 300,
"wires": []
},
{
"id": "a6b1ba01.ca99e8",
"type": "link in",
"z": "987bb09c.ac58c",
"name": "update-list",
"links": [
"a019e6c6.25b418"
],
"x": 695,
"y": 400,
"wires": [
[
"c126ddd0.72e43"
]
]
},
{
"id": "98561757.5e8b78",
"type": "sqlite",
"z": "987bb09c.ac58c",
"mydb": "e3c4fac5.921408",
"name": "SQLite から読み込む",
"x": 820,
"y": 460,
"wires": [
[
"83c3a40f.4098a8"
]
]
},
{
"id": "c126ddd0.72e43",
"type": "function",
"z": "987bb09c.ac58c",
"name": "SQL クエリを作成する (SELECT)",
"func": "\nvar msg = {\n \"topic\": \"SELECT timestamp, address_from, user FROM invalid_users ORDER BY timestamp DESC LIMIT 20;\"\n};\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 880,
"y": 400,
"wires": [
[
"98561757.5e8b78"
]
]
},
{
"id": "83c3a40f.4098a8",
"type": "ui_template",
"z": "987bb09c.ac58c",
"group": "d7716715.ea91d8",
"name": "不正アクセスリスト (最新20件)",
"order": 0,
"width": "12",
"height": "12",
"format": "<div>\n <div layout=\"row\" layout-align=\"center\">\n <span flex style=\"font-weight: bold;\">時刻</span>\n <span flex style=\"font-weight: bold;\">アクセス元</span>\n <span flex style=\"font-weight: bold;\">ログインユーザー</span>\n </div>\n <div layout=\"row\" layout-align=\"center\" ng-repeat=\"log in msg.payload\">\n <span flex>{{ log.timestamp }}</span>\n <span flex>{{ log.address_from }}</span>\n <span flex>{{ log.user }}</span>\n </div>\n</div>",
"storeOutMessages": true,
"fwdInMessages": true,
"templateScope": "local",
"x": 1090,
"y": 460,
"wires": [
[]
]
},
{
"id": "9545923e.ebc87",
"type": "comment",
"z": "987bb09c.ac58c",
"name": "SSH 不正アクセス件数ランキングを表示",
"info": "",
"x": 840,
"y": 60,
"wires": []
},
{
"id": "a3610115.91eea",
"type": "link in",
"z": "987bb09c.ac58c",
"name": "update-ranking",
"links": [
"a019e6c6.25b418"
],
"x": 695,
"y": 120,
"wires": [
[
"949cf9fc.6dfd48"
]
]
},
{
"id": "39e8e434.7088ac",
"type": "sqlite",
"z": "987bb09c.ac58c",
"mydb": "e3c4fac5.921408",
"name": "SQLite から読み込む",
"x": 820,
"y": 180,
"wires": [
[
"9abe8580.30f598",
"a9ffe3bc.2b8a6"
]
]
},
{
"id": "949cf9fc.6dfd48",
"type": "function",
"z": "987bb09c.ac58c",
"name": "SQL クエリを作成する (SELECT)",
"func": "\nvar msg = {\n \"topic\": \"SELECT user, count(timestamp) as count FROM invalid_users GROUP BY user ORDER BY count DESC LIMIT 10;\"\n};\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 880,
"y": 120,
"wires": [
[
"39e8e434.7088ac"
]
]
},
{
"id": "9abe8580.30f598",
"type": "ui_template",
"z": "987bb09c.ac58c",
"group": "dbc78ebc.2a39f",
"name": "SSH 不正アクセスユーザー名ランキング",
"order": 2,
"width": "12",
"height": "5",
"format": "<div layout=\"row\" layout-align=\"center\">\n <span flex style=\"font-weight: bold;\">ログインユーザー名</span>\n <span flex style=\"font-weight: bold;\">件数</span>\n</div>\n<div layout=\"row\" layout-align=\"center\" ng-repeat=\"log in msg.payload\">\n <span flex>{{ log.user }}</span>\n <span flex>{{ log.count }}</span>\n</div>",
"storeOutMessages": true,
"fwdInMessages": true,
"templateScope": "local",
"x": 1120,
"y": 180,
"wires": [
[]
]
},
{
"id": "7786099f.499a68",
"type": "ui_chart",
"z": "987bb09c.ac58c",
"name": "",
"group": "dbc78ebc.2a39f",
"order": 1,
"width": "12",
"height": "7",
"label": "不正アクセスユーザー名分布",
"chartType": "pie",
"legend": "true",
"xformat": "HH:mm:ss",
"interpolate": "linear",
"nodata": "",
"dot": false,
"ymin": "",
"ymax": "",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "3600",
"cutout": 0,
"useOneColor": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"useOldStyle": false,
"x": 1080,
"y": 240,
"wires": [
[],
[]
]
},
{
"id": "a9ffe3bc.2b8a6",
"type": "function",
"z": "987bb09c.ac58c",
"name": "チャート用に成型する",
"func": "var payload = {};\nvar labels = [];\nvar data = [];\n\nfor (var key in msg.payload) {\n labels.push(msg.payload[key].user);\n data.push(msg.payload[key].count);\n}\n\npayload.labels = labels;\npayload.data = [data];\npayload.series = [\"Users\"];\n\nreturn {payload:[payload]};",
"outputs": 1,
"noerr": 0,
"x": 820,
"y": 240,
"wires": [
[
"7786099f.499a68"
]
]
},
{
"id": "4af1b90c.8ba9d8",
"type": "inject",
"z": "987bb09c.ac58c",
"name": "Node-RED 更新時トリガー",
"topic": "",
"payload": "",
"payloadType": "date",
"repeat": "",
"crontab": "",
"once": true,
"x": 440,
"y": 240,
"wires": [
[
"a019e6c6.25b418"
]
]
},
{
"id": "e3c4fac5.921408",
"type": "sqlitedb",
"z": "",
"db": "/home/nodered/monitor-logs.db"
},
{
"id": "d7716715.ea91d8",
"type": "ui_group",
"z": "",
"name": "不正アクセスリスト (最新20件)",
"tab": "7388e324.29331c",
"order": 2,
"disp": true,
"width": "12"
},
{
"id": "dbc78ebc.2a39f",
"type": "ui_group",
"z": "",
"name": "SSH 不正アクセスユーザー名ランキング",
"tab": "7388e324.29331c",
"order": 1,
"disp": true,
"width": "12"
},
{
"id": "7388e324.29331c",
"type": "ui_tab",
"z": "",
"name": "SSH 不正アクセス監視",
"icon": "dashboard"
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment