うさラボ

お勉強と備忘録

ntc-template自作入門

ntc-templateにほしいパーサーがなかったので自作入門してみました。

やりたかったこと

show ip nat translationsを実行したときに想定通りのStaticNATのエントリーが見たい

準備

環境
python

ライブラリ

  • textfsm==1.1.2
  • ntc-templates==2.1.0

公式ドキュメントを参考に準備を開始 github.com

自作のテンプレートを格納するディレクトを作成 /templates

テンプレートの名前は下記の規則で作ってねと説明ありました。
{{ vendor_os }}_{{ command_with_underscores }}.textfsm

今回はiosのshow ip nat translationsの結果をパースするテンプレートのため、 ほかのテンプレートを参考にcisco_ios_show_ip_nat_translations.textfsmとしました。

実装

テンプレートの作成を始めます。

今回想定しているshow ip nat translationsの結果が以下になります。
StaticNATのInside globalとInside locaが想定通りであるか?の確認をするためにパースします。
StaticNATのエントリとしては最後の行が該当します。

  Pro Inside global      Inside local       Outside local      Outside global
  tcp 10.9.0.0:51776     10.1.0.2:51776     10.2.0.2:21        10.2.0.2:21
  tcp 10.9.0.0:51778     10.1.0.2:51778     10.2.0.2:21        10.2.0.2:21
  tcp 10.9.0.0:56384     10.1.0.2:56384     10.2.0.2:22        10.2.0.2:22
  icmp 10.9.0.0:56111     10.1.0.2:56384     10.2.0.2:23        10.2.0.2:23
  --- 10.9.0.0     10.1.0.2     ---        ---

ntc-template(textfsm)で対象の文字列を取得するのは正規表現でマッチさせる必要があります。
正規表現の書き方を確認するには下記サイトなどを利用しています。(機微な情報は載せないように気を付けてください) regex101.com

正規表現やほかのテンプレートを参考に下記テンプレートを作成しました

Value PROTOCOL (tcp|udp|icmp|---)
Value INSIDE_GLOBAL (\S+)
Value INSIDE_LOCAL (\S+)
Value OUTSIDE_LOCAL (\S+)
Value OUTSIDE_GLOBAL (\S+)

Start
  ^${PROTOCOL}\s+${INSIDE_GLOBAL}\s+${INSIDE_LOCAL}\s+${OUTSIDE_LOCAL}\s+${OUTSIDE_GLOBAL} -> Record

Valueにはパース後のキーを定義します。
key名と(ヒットさせる条件)の間にはスペースが必要です。 Value key名 (ヒットさせる条件)

テキストの解析処理はStartから始まります。
今回は^${PROTOCOL}\s・・・をキーに解析を始めます。
PROTOCOLはtcpudpかicmpか---が入る想定なのでカラムを除いたステータスの部分にマッチします。 f:id:usage_automate:20211111205641p:plain

そのほかのINSIDE_GLOBALなどは(\S+)のルールで空白以外の文字列をマッチさせています。
Pro Inside global Inside local Outside local Outside globalのカラムもマッチしていますがパース対象の文字列として含まれないように調整しています。 f:id:usage_automate:20211111212050p:plain

本来であれば、PATしている情報(IP:Portなどを値)も扱いやすいようにIPとPort分けて保存したほうがいいかもしれませんが、StaticNATの確認はこのテンプレートで十分できるのでここで終了します。

indexに今回追加したテンプレートを呼び出すルールを記載します。
これが必要なことに気づかず時間を結構食ってしまいました。。 templates/index

Template, Hostname, Platform, Command

cisco_ios_show_ip_nat_translations.textfsm, .*, cisco_ios, sh[[ow]] ip nat translations

確認

作成したテンプレートを動かすPythonスクリプトを作成します。

import os
import pprint
from ntc_templates.parse import parse_output # (1)

os.environ["NTC_TEMPLATES_DIR"] = "./templates" # (2)

output1 = (
  "Pro Inside global      Inside local       Outside local      Outside global\n"
  "tcp 10.9.0.0:51776     10.1.0.2:51776     10.2.0.2:21        10.2.0.2:21\n"
  "tcp 10.9.0.0:51778     10.1.0.2:51778     10.2.0.2:21        10.2.0.2:21\n"
  "tcp 10.9.0.0:56384     10.1.0.2:56384     10.2.0.2:22        10.2.0.2:22\n"
  "icmp 10.9.0.0:56111     10.1.0.2:56384     10.2.0.2:23        10.2.0.2:23\n"
  "--- 10.9.0.0     10.1.0.2     ---        ---\n"
) # (3)

parsed = parse_output(platform="cisco_ios",
                      command="show ip nat translations",
                      data=output1) # (4)

pprint.pprint(parsed) # (5)

1) ntc_templates.parseにあるparse_outputを読み込みます。
2) テンプレートを格納しているディレクトを指定します。
3) 想定のアウトプットを格納します。
4) パース処理を実行します、platform,command,dataを定義します。
5) 実行結果を表示します。

スクリプトを実行します。

sandbox# python nat_parser.py 
[{'inside_global': '10.9.0.0:51776',
  'inside_local': '10.1.0.2:51776',
  'outside_global': '10.2.0.2:21',
  'outside_local': '10.2.0.2:21',
  'protocol': 'tcp'},
 {'inside_global': '10.9.0.0:51778',
  'inside_local': '10.1.0.2:51778',
  'outside_global': '10.2.0.2:21',
  'outside_local': '10.2.0.2:21',
  'protocol': 'tcp'},
 {'inside_global': '10.9.0.0:56384',
  'inside_local': '10.1.0.2:56384',
  'outside_global': '10.2.0.2:22',
  'outside_local': '10.2.0.2:22',
  'protocol': 'tcp'},
 {'inside_global': '10.9.0.0:56111',
  'inside_local': '10.1.0.2:56384',
  'outside_global': '10.2.0.2:23',
  'outside_local': '10.2.0.2:23',
  'protocol': 'icmp'},
 {'inside_global': '10.9.0.0',
  'inside_local': '10.1.0.2',
  'outside_global': '---',
  'outside_local': '---',
  'protocol': '---'}]

無事にパースができました。
StaticNATのエントリはprotocl/outside_global/outside_localが---になるので下記の要素を確認します。

 {'inside_global': '10.9.0.0',
  'inside_local': '10.1.0.2',
  'outside_global': '---',
  'outside_local': '---',
  'protocol': '---'}]

パースがうまくできているので後はassertなどで結果の判断が簡単にできます。

まとめ

簡単なテンプレートでしたが、ntc-templateを自作することができました。
今回やったことは本当に入門レベルでntc-templateは複雑なテキストをパースすることも可能です。

自作したntc-templateをansibleに読み込ませてわちゃわちゃしたいと思います。

また、TTPパーサーも自作をしたのでいつか記事にします。