【BigQuery】Chrome拡張機能を用いてフォーム入力内容をログ記録するまで
【BigQuery】Chrome拡張機能を用いてフォーム入力内容をログ記録するまで
お世話になっております。
技術担当のskkと申します。
はじめに
今回はGoogleChromeの拡張機能とGoogleCloudPlatform(以下GCP)を用いて
入力フォームで入力された特定の文字列を検出して外部DBに記録させる
というものを試作しましたので備忘録も兼ねた技術共有メモとしてこちら記事にさせていただきます。
使用言語とサービス
・ JavaScript(jQuery、Node.js)
・ GoogleCloudPlatform(CloudFunctions、Bigquery)
・ chrome.storage
・ IndexedDB
概要
つまるところどういう処理なのかという話ではございますので、順を追ってご説明致します。
1. 拡張機能によってあらゆるページにJavaScriptを読み込ませる それによって入力フォーム内のテキスト入力欄での入力内容をチェックできるようにする
2. 設定した指定の文字列が入力された時その文字列をフォーム内で下線を引く形でハイライトさせる。(これもJavaScriptで)
3. 送信ボタンを押した際にajaxを用いてGCP内のDB登録処理に入力文字列データをPOST送信
4. GCP内のDB登録処理(CloudFunctionという内部の連携関数で作成 言語はNode.js)にて渡ってきた内容をGCP内のDB、Bigqueryにログデータとして登録
こちらが基本の処理の流れです。
加えてこのログデータをajaxによる取得、表示をするページも設けています。
また、ログ登録の際に現在日時をベースにユーザIDの作成
これらをログデータ行に含ませることで、指定したユーザIDのログを選出可能な状態にしています。
別途ログ登録の際に用いる暗号化キーを生成、前述のユーザIDと合わせてクライアント側に保存(今回はIndexedDBを使用)
暗号化したログデータをDB登録して万一の漏洩にも備えます。
言語選定の特長
・Chrome拡張機能を用いることであらゆる入力フォームに対応、及び導入を容易に
(ただし、この記事執筆時点では公開申請中の為、実際に導入させて各人が使用できるかは不確定…
こちら自PC内で公開前の非パッケージ状態で動作確認した記録になります…ご了承くださいorz)
・GCPを利用することでDBや内部処理の為にサーバを別途用意する必要がない
・基本はJavaScriptなので作りやすい(個人的には)
利用用途
基本的にフォーム入力のログ取得ということで
個人利用のメリットは特に考えられないので、
組織での利用を想定しています。
検知する指定の文字列は制作側で指定するとして
何を指定、検知させるかによってこのツールの詳細な用途が決まるかなと思います。
企業として不利益となるような用語を指定することでその用語を用いた社員を記録したり、
正規表現等で携帯番号、メールアドレスを引っ掛けるようにして、
不用意な個人情報の入力を記録したり、
攻撃的な単語、言い回しを指定して
誹謗中傷につながる書き込みを検知したり…etc
例示した内容以外にも
色々使えそうな可能性があるのかなとは思います。
機能ごとに解説
・入力フォーム内のテキスト入力欄での入力内容をチェックできるようにする
$('form').submit(function(){ $(this).find('input[type="text"],textarea').each(function(index, element){ // 検知 var submit_intxt = $(this).val(); //~~~~~~~中略~~~~~~~ }); });
この辺りはシンプルなjQueryで出来るかなと思います。
送信ボタン(submit)を押したときにそのフォーム内のテキスト関連の内容を取得して
各内容に対して指定の文字を含んでいるかチェックしてログ記録処理に渡していく形になります。
ちなみに
Chrome拡張機能でjQueryを使うには
jQueryライブラリファイル自体を拡張機能ディレクトリに保存
使用構成を表記するmanifest.jsonの中で
処理を行うjsファイルより前に、jQueryライブラリファイルを指定すると
jsファイルの中でjQueryが使用出来るようになります。
・設定した指定の文字列が入力された時その文字列をフォーム内で下線を引く形でハイライトさせる。
これもJavaScriptで実装可能なのですが、
ちょっと一癖ありまして…
というのも
inputタグやtextareaタグで書いた文字の一部に対して
下線を引いたり太字にしたりのstyle適用が直接行えない問題があります
その為に対応を考える必要があります。
// 各テキストフォームに入力内容をトレスするdiv枠を追加 var S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // div枠名用ランダム文字列使用文字セット var N=6;// ランダム文字列の桁数 $("input[type='text'],textarea").each(function(index, element){ var ps =Array.from(crypto.getRandomValues(new Uint8Array(N))).map((n)=>S[n%S.length]).join(''); //専用class名をランダム文字列で $(this).addClass('_pii_'+ps); var div_inner = { text: '', class: '_pii_'+ps, style: 'position:absolute; '+ 'top:'+(Number($(this).offset().top)+3.5)+'px; left:'+(Number($(this).offset().left)+3)+'px; '+ 'font-size:'+$(this).css('font-size')+'; '+ 'letter-spacing:'+$(this).css('letter-spacing')+'; '+ 'font-family:'+$(this).css('font-family')+'; '+ 'color: transparent; '+ 'text-align: left; ' } $(this).after($('<div>', div_inner)); }); $("input,textarea").on("input",function(e){ var classVal = $(this).attr('class'); // classの値を取得 var classVals = classVal.split(' '); // 取得した値を分割 var target_highlight = ''; for (var i = 0; i < classVals.length; i++) { if ( /^_pii_.*$/.test(classVals[i]) ) { target_highlight = classVals[i]; } } var intxt = $(this).val(); if(target_highlight != ''){ $("div."+target_highlight).html(intxt.replace(/\r?\n/g, '<br>')); $("div."+target_highlight).html(intxt.replace(/ /g, ' ')); } });
ということで上記のようにtextの各フォームと丁度重なるようにdivタグを置き、
入力に合わせて透明で同じ文字を重ね、検知内容が入力されたらその部分だけstyleを適用する
という形で下線を実現しています。
こちらの処理は今回の検出機能に対して必須なものではありませんが、
視覚的に表現したい部分でしたのでこちらも紹介させていただきました。
・送信ボタンを押した際にajaxを用いてGCP内のDB登録処理に入力文字列データをPOST送信
// Bigqueryの認証からinsertまで $.ajax({ type: 'POST', data: { /*「ここにログデータとかユーザIDとかBigqueryで登録する為のデータたちを入れる」*/ }, url: '/*「ここにGCP側で作成したBigquery登録用のプログラムURLを入れる」*/', success: function(data) { console.log(data); }, error: function(XMLHttpRequest, textStatus, errorThrown) { console.log("XMLHttpRequest : " + XMLHttpRequest.status); console.log("textStatus : " + textStatus); console.log("errorThrown : " + errorThrown.message); } });
ここはJavaScript側の処理は簡単なものになります。
データをjsonでGCP側のDB登録プログラムにPOSTで送信するだけです。
GCP側のDB登録プログラムの方で一手間かかります…
・GCP内のDB登録処理
(CloudFunctionという内部の連携関数で作成 言語はNode.js)にて渡ってきた内容をGCP内のDB、Bigqueryにログデータとして登録
GCP内のサービスで
DBの用意、DBへのデータ登録処理 を行います。
GCP利用に必要なもの
・gmailアドレス
・クレジットカード情報
GoogleCloudPlatformへアクセス
↓
新しいプロジェクトの作成
↓
請求先アカウントの作成
↓
[IAMと管理]画面にてサービスアカウントのキー発行
(この時、発行されるkeyfile.jsonは後で使用するため大切に保管すること。
後のCloudFunctionをajax実行する際の認証画面省略の為に使用するため必須)
↓
Bigqueryのデータセット、テーブルの作成
↓
CloudFunctionの作成
CloudFunctionでDB登録する処理はこのような感じです。
sample.js(Node.js)
exports.bigqueryInsert = (req, res) => { res.header("Access-Control-Allow-Origin", "*"); res.header('Access-Control-Allow-Headers', "Origin, X-Requested-With, Content-Type, Accept"); const {BigQuery} = require('@google-cloud/bigquery'); var iconvlite = require('iconv-lite'); var windows1252 = require('windows-1252'); var config = { projectId: '/*『ここにGCPのプロジェクトID』*/', keyFilename: './keyfile.json' }; const bigqueryclient = new BigQuery(config); console.log(req.body); const str_input_data = req.body['input_data']; const str_pii_data = req.body['pii_data']; const str_free_json_data = !req.body['json_data'] ? '' : req.body['json_data']; const rows= { wdate_id: req.body['wdate_id'], input_data: str_input_data, pii_data: str_pii_data, wdate: req.body['wdate'], free_json_fld: str_free_json_data }; const option_rows = { ignoreUnknownValues: true, skipInvalidRows: true }; bigqueryclient .dataset('pii_dataset') .table('pii_checklog') .insert(rows, option_rows) .then((data) => { const apiResponse = data[0]; console.log('insert finish'); res.status(200).send('fin'); return; }) .catch(err => { if (err && err.name === 'PartialFailureError') { if (err.errors && err.errors.length > 0) { console.log('Insert errors:'); err.errors.forEach(err => console.error(err)); } } else { console.error('ERROR:', err); } }); console.log(`Inserted ${rows.length} rows`); };
おわりに 雑感と今後の課題
入力内容(の一部)をサーバアップするという点で
いわゆるキーロガーの側面もある機能ですので、
第三者がデータを盗み見たとしても何かはわからないように
データ登録前に適切な暗号化を施すなどの、適切な対策をとり、
安全な運用をすることが必須と考えます。
また、利用用途の項でも説明した内容と重複はしますが、
検知する内容次第で、使い方が大きく変わってくるものだと思います。
この辺りは先々有効な使い方を模索していきます。
今回はこの入力データ検知・記録の仕組みを
ローカルのデータ・ストレージ機能と物理サーバを用いず、
サーバセットアップも無い、
クラウドサービス上のDB機能を使うことで、
プログラマーだけでも一連のDBシステムを作成可能であるという点が
一つの特長かと考えます。
そんな所で今回は以上です。
ここまでお読みいただきありがとうございました。