ネットワーク設定検証 は何をしますか?
このスキルは、変更作業の前や自動化が本番デバイスに影響を与える前に、ネットワーク設定をレビューするために使用します。
使用するタイミング
- Cisco IOS または IOS-XE スタイルのスニペットを導入前にレビューする場合。
- スクリプトやテンプレートから生成された設定を監査する場合。
- 危険なコマンド、重複IPアドレス、サブネットのオーバーラップを探す場合。
- ACL、ルートマップ、プレフィックスリスト、ラインポリシーが参照されているが定義されていないかを確認する場合。
- ネットワーク自動化のための軽量な事前チェックスクリプトを作成する場合。
仕組み
設定検証は、完全なパーサーではなく、レイヤー化されたエビデンスとして扱います。正規表現によるチェックは事前警告として有用ですが、最終的な承認には、ネットワークエンジニアによる意図、プラットフォームの構文、ロールバック手順のレビューが依然として必要です。
以下の順序で検証を行います:
- 破壊的なコマンド。
- 認証情報と管理プレーンの露出。
- 重複アドレスとサブネットのオーバーラップ。
- ACL、ルートマップ、プレフィックスリスト、インターフェースへの未定義参照。
- NTP、タイムスタンプ、リモートロギング、バナーなどの運用衛生状態。
危険なコマンドの検出
import re
DANGEROUS_PATTERNS: list[tuple[re.Pattern[str], str]] = [
(re.compile(r"\breload\b", re.I), "reload はダウンタイムを引き起こします"),
(re.compile(r"\berase\s+(startup|nvram|flash)", re.I), "永続ストレージを消去します"),
(re.compile(r"\bformat\b", re.I), "デバイスのファイルシステムをフォーマットします"),
(re.compile(r"\bno\s+router\s+(bgp|ospf|eigrp)\b", re.I), "ルーティングプロセスを削除します"),
(re.compile(r"\bno\s+interface\s+\S+", re.I), "インターフェース設定を削除します"),
(re.compile(r"\baaa\s+new-model\b", re.I), "認証動作を変更します"),
(re.compile(r"\bcrypto\s+key\s+(zeroize|generate)\b", re.I), "デバイスのSSHキーを変更します"),
]
def find_dangerous_commands(lines: list[str]) -> list[dict[str, str | int]]:
findings = []
for line_number, line in enumerate(lines, start=1):
stripped = line.strip()
for pattern, reason in DANGEROUS_PATTERNS:
if pattern.search(stripped):
findings.append({
"line": line_number,
"command": stripped,
"reason": reason,
})
return findings
重複IPとサブネットオーバーラップ
import ipaddress
import re
from collections import Counter
IP_ADDRESS_RE = re.compile(
r"^\s*ip address\s+"
r"(?P<ip>\d{1,3}(?:\.\d{1,3}){3})\s+"
r"(?P<mask>\d{1,3}(?:\.\d{1,3}){3})\b",
re.I | re.M,
)
def extract_interfaces(config: str) -> list[dict[str, str]]:
results = []
current = None
for line in config.splitlines():
if line.startswith("interface "):
current = line.split(maxsplit=1)[1]
continue
match = IP_ADDRESS_RE.match(line)
if current and match:
ip = match.group("ip")
mask = match.group("mask")
network = ipaddress.ip_interface(f"{ip}/{mask}").network
results.append({"interface": current, "ip": ip, "network": str(network)})
return results
def find_duplicate_ips(config: str) -> list[str]:
ips = [entry["ip"] for entry in extract_interfaces(config)]
counts = Counter(ips)
return sorted(ip for ip, count in counts.items() if count > 1)
def find_subnet_overlaps(config: str) -> list[tuple[str, str]]:
networks = [ipaddress.ip_network(entry["network"]) for entry in extract_interfaces(config)]
overlaps = []
for index, left in enumerate(networks):
for right in networks[index + 1:]:
if left.overlaps(right):
overlaps.append((str(left), str(right)))
return overlaps
管理プレーンチェック
VTYブロックをセクションごとに解析し、アクセスクラスのチェックが無関係な行に及ばないようにします。
import re
def iter_blocks(config: str, starts_with: str) -> list[str]:
blocks = []
current: list[str] = []
for line in config.splitlines():
if line.startswith(starts_with):
if current:
blocks.append("\n".join(current))
current = [line]
continue
if current:
if line and not line.startswith(" "):
blocks.append("\n".join(current))
current = []
else:
current.append(line)
if current:
blocks.append("\n".join(current))
return blocks
def check_vty_blocks(config: str) -> list[str]:
issues = []
for block in iter_blocks(config, "line vty"):
if re.search(r"transport\s+input\s+.*telnet", block, re.I):
issues.append("VTY が Telnet を許可しています。SSH のみを要求してください。")
if not re.search(r"\baccess-class\s+\S+\s+in\b", block, re.I):
issues.append("VTY ブロックにインバウンドアクセスクラスによる送信元制限がありません。")
if not re.search(r"\bexec-timeout\s+\d+\s+\d+\b", block, re.I):
issues.append("VTY ブロックに明示的な exec-timeout が設定されていません。")
return issues
セキュリティ衛生チェック
SECURITY_PATTERNS = [
(re.compile(r"\bsnmp-server community\s+(public|private)\b", re.I),
"デフォルトの SNMP コミュニティが設定されています"),
(re.compile(r"\bsnmp-server community\s+\S+", re.I),
"SNMPv2 コミュニティ文字列が設定されています。SNMPv3 authPriv を推奨します"),
(re.compile(r"\bip ssh version 1\b", re.I),
"SSH バージョン 1 が有効です"),
(re.compile(r"\benable password\b", re.I),
"enable password が存在します。enable secret を使用してください"),
(re.compile(r"\busername\s+\S+\s+password\b", re.I),
"ローカルユーザー名が secret ではなく password を使用しています"),
]
BEST_PRACTICE_PATTERNS = [
(re.compile(r"\bntp server\b", re.I), "NTP サーバー"),
(re.compile(r"\bservice timestamps\b", re.I), "ログタイムスタンプ"),
(re.compile(r"\blogging\s+\S+", re.I), "ロギングの宛先またはバッファー"),
(re.compile(r"\bsnmp-server group\s+\S+\s+v3\s+priv\b", re.I), "SNMPv3 authPriv グループ"),
(re.compile(r"\bbanner\s+(login|motd)\b", re.I), "ログインバナー"),
]
def check_security(config: str) -> list[str]:
return [message for pattern, message in SECURITY_PATTERNS if pattern.search(config)]
def check_missing_hygiene(config: str) -> list[str]:
return [
f"欠落: {description}"
for pattern, description in BEST_PRACTICE_PATTERNS
if not pattern.search(config)
]
使用例
変更作業事前チェック
- 貼り付ける正確なスニペットに対して危険なコマンドチェックを実行します。
- 完全な候補設定に対して重複IPとサブネットのオーバーラップチェックを実行します。
- 参照されているすべてのACL、ルートマップ、プレフィックスリストが存在することを確認します。
- 管理プレーンの変更前にロールバックコマンドと帯域外アクセスを確認します。
自動化事前チェック
Netmiko、NAPALM、Ansible、またはベンダーAPIの自動化が生成された設定をプッシュする前に、検証をブロッキングゲートとして使用します。危険なコマンドと認証情報についてはエラーで停止します。変更範囲外のベストプラクティスのギャップについては警告します。
アンチパターン
- 正規表現による検証をデバイスパーサーとして扱うこと。
- ドライランの差分なしで生成された設定を適用すること。
- 監視要件としてSNMPv2コミュニティ文字列を推奨すること。
- VTYブロックを、無関係なセクションに誤って及ぶ可能性のある正規表現でチェックすること。
- カウンターやログを読む代わりにACLを無効にしてファイアウォールの動作をテストすること。
関連情報
- エージェント:
network-config-reviewer - エージェント:
network-troubleshooter - スキル:
network-interface-health