ネットワーク管理者より、FortiGateが吐き出すファイアウォールのディスクログファイルを解析するに当たり、そのままではエクセルなどへインポートできないとの相談を受け、ログに含まれる膨大なフィールドの中から必要最小限の情報を抜き出し、タブ区切りのテキストファイルを生成するスクリプトをPythonで自作しました。
FortiGateファイアウォールのログの中身
膨大なログファイルの一部をサンプルとして受け取り、その中身を確認。その1行分を抜き出したのがこちら。
各項目毎に改行を入れて見やすくするとこんな感じ。これはそのままCSVとしてエクセルなどへインポートできないのも納得。
ログフォーマットは、 フィールド名=値 が半角スペース区切りで数珠つなぎになっており、値はString型の時にダブルクォーテーションで囲まれ、その中身には半角スペースが含まれることがあり、といったところ。また、FortiGateの高速ログはナノ秒単位で記録されている模様( eventtime )。
巷のLog Parserたち
当初はもうてっきり誰かが作っているだろうと検索してみるも意外と少なく、そんな中から見つけたこちらのLog Parserは、カンマ区切りされたログを想定しているようで、残念ながら受け付けてもらえず。
そんなわけで、FortiGateのファイアウォールログから必要な項目を抜き出し、CSV形式に整形するスクリプトの製作へ入ります。
項目毎に正確に区切る正規表現
このスクリプトの肝はなんと言っても、決められた区切り文字の無いログエントリの各項目を正確に切り分けること。空白区切りで表計算ソフトへインポートしようとすると罠に陥ります。
こちらの正規表現チェッカーのページで苦手な正規表現を駆使すること数時間、ようやく正規表現を確定。
|
1 |
/[a-z]+=[0-9]\S*|[a-z]+=["][a-zA-Z0-9+].[^"]+["]/g |
実際にテストデータを入れて結果を確認したり、その結果をURLで残しておけるのがとても便利でした(鳴謝!)。
ナノ秒換算
ログエントリにはナノ秒単位のUNIXタイプスタンプが含まれているので、これをヒューマンフレンドリな形式へ変換する関数を作りました。
|
1 2 3 4 5 6 7 8 9 10 |
$ python3 Python 3.8.0 (default, Dec 9 2021, 17:53:27) >>> from datetime import datetime >>> def nanoutm2str(nano): ... fmt='%Y-%m-%d %H:%M:%S.%f' ... nt=datetime.fromtimestamp(int(nano)/1000000000).strftime(fmt) ... return nt >>> nanoutm2str(1727950020698390194) '2024-10-03 18:07:00.698390' |
出来上がったLog Parser
以上を経て、Log Parserは完成しました。突貫工事なので、抜き出す項目名はスクリプト内に決め打ちです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Fortigate Log Parser # import re, argparse from datetime import datetime ## VARs fields=['eventtime','action','srccountry','srcip','srcport','dstport','dstip','dstcountry'] p='[a-z]+=[0-9]\S*|[a-z]+=["][a-zA-Z0-9+].[^"]+["]' ## FUNCs def nanoutm2str(nano): fmt='%Y-%m-%d %H:%M:%S.%f' nt=datetime.fromtimestamp(int(nano)/1000000000).strftime(fmt) return nt def onelinework(line): linedict={} m_iter=re.finditer(p, line) for m in m_iter: val=m.group().split('=') key=val[0] linedict[key]=val[-1].strip('"') #print(linedict) exp='' for fld in fields: if fld == 'eventtime': v=nanoutm2str(linedict.get(fld, '')) else: v=linedict.get(fld, '') exp+=v+'\t' exp=exp.rstrip() return exp+'\n' ## MAIN if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('arg1') args=parser.parse_args() parsedfile=open(args.arg1+'_parsed.txt', "w", encoding='utf-8') parsedfile.write('\t'.join(fields)+'\n') with open(args.arg1) as f: for line in f: parsedfile.write(onelinework(line)) |
実行結果は以下の通り。抜き出すフィールドを絞り込んだので、単純にテキストファイルを表示させただけでも、分かりやすくなったと思います。
こちら、恥ずかしながらGitHubに公開しておいたので、優しくそっと見守ってください。








