Featured image of post 第2回-通知先が増えたら地獄 - Perlでハニーポット侵入レーダーを作ろう

第2回-通知先が増えたら地獄 - Perlでハニーポット侵入レーダーを作ろう

ハニーポット侵入レーダーに脅威スコア機能を追加しようとしたら、コードがスパゲッティ化していく様子を体験します。if/elseの増殖と単一責任の原則違反が引き起こす破綻を理解しましょう。

@nqounetです。

前回は侵入イベントをコンソールに表示する最小構成を作りました。シンプルで良いですね。

でも、現実の運用では「ログを表示するだけ」では物足りません。今回は「脅威スコアも計算したい」という要望に応えようとして、コードが崩壊していく様子を見ていきましょう(意図的に破綻させるので安心してください)。

脅威スコアを追加したい

侵入イベントには危険度があります。SSHブルートフォースは要注意だし、ポートスキャンは「様子見」程度。この違いを数値化した「脅威スコア」を計算したくなりました。

素直に機能追加してみましょう。

 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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo(cpanmでインストール)

use v5.36;

package IntrusionEvent;
use Moo;

has timestamp => (
    is       => 'ro',
    required => 1,
);

has source_ip => (
    is       => 'ro',
    required => 1,
);

has attack_type => (
    is       => 'ro',
    required => 1,
);

package main;

# 脅威スコア計算用のハッシュ
my %threat_scores = (
    'SSH Brute Force'       => 80,
    'Port Scan'             => 30,
    'SQL Injection Attempt' => 90,
    'XSS Attack'            => 70,
);

my $total_score = 0;

my @events = (
    IntrusionEvent->new(
        timestamp   => '2026-01-18T06:00:00+09:00',
        source_ip   => '192.168.1.100',
        attack_type => 'SSH Brute Force',
    ),
    IntrusionEvent->new(
        timestamp   => '2026-01-18T06:01:15+09:00',
        source_ip   => '10.0.0.55',
        attack_type => 'Port Scan',
    ),
);

for my $event (@events) {
    # ログ出力
    say "=== 侵入イベント検知 ===";
    say "時刻: " . $event->timestamp;
    say "発信元: " . $event->source_ip;
    say "攻撃種別: " . $event->attack_type;

    # 脅威スコア計算
    my $score = $threat_scores{$event->attack_type} // 50;
    $total_score += $score;
    say "脅威スコア: $score (累計: $total_score)";
    say "";
}

動きますね。でも、このコード、イヤな予感がしませんか?

要望はさらに増える

「アラート機能も追加してほしい」と言われました。脅威スコアが一定以上になったら警告を出す機能です。

 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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo(cpanmでインストール)

use v5.36;

package IntrusionEvent;
use Moo;

has timestamp => (
    is       => 'ro',
    required => 1,
);

has source_ip => (
    is       => 'ro',
    required => 1,
);

has attack_type => (
    is       => 'ro',
    required => 1,
);

package main;

my %threat_scores = (
    'SSH Brute Force'       => 80,
    'Port Scan'             => 30,
    'SQL Injection Attempt' => 90,
    'XSS Attack'            => 70,
);

my $total_score     = 0;
my $alert_threshold = 100;

my @events = (
    IntrusionEvent->new(
        timestamp   => '2026-01-18T06:00:00+09:00',
        source_ip   => '192.168.1.100',
        attack_type => 'SSH Brute Force',
    ),
    IntrusionEvent->new(
        timestamp   => '2026-01-18T06:01:15+09:00',
        source_ip   => '10.0.0.55',
        attack_type => 'Port Scan',
    ),
);

for my $event (@events) {
    # ログ出力
    say "=== 侵入イベント検知 ===";
    say "時刻: " . $event->timestamp;
    say "発信元: " . $event->source_ip;
    say "攻撃種別: " . $event->attack_type;

    # 脅威スコア計算
    my $score = $threat_scores{$event->attack_type} // 50;
    $total_score += $score;
    say "脅威スコア: $score (累計: $total_score)";

    # アラートチェック
    if ($total_score >= $alert_threshold) {
        say "!!! 警告: 脅威レベルが閾値を超えました !!!";
    }

    say "";
}

動作結果はこうなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
=== 侵入イベント検知 ===
時刻: 2026-01-18T06:00:00+09:00
発信元: 192.168.1.100
攻撃種別: SSH Brute Force
脅威スコア: 80 (累計: 80)

=== 侵入イベント検知 ===
時刻: 2026-01-18T06:01:15+09:00
発信元: 10.0.0.55
攻撃種別: Port Scan
脅威スコア: 30 (累計: 110)
!!! 警告: 脅威レベルが閾値を超えました !!!

機能的には動いています。でも、コードをよく見てください。

何が問題なのか

このコードには致命的な設計上の問題があります。

問題1: 1つのループで全部やりすぎ

イベント処理のループの中で以下のことをすべてやっています。

  • ログ出力
  • 脅威スコア計算
  • 累計スコア管理
  • アラートチェック

これは「単一責任の原則(SRP)」に違反しています。1つの場所で複数の責務を担当すると、変更が難しくなります。

問題2: 通知先を追加するたびにコードを変更

もし「メール通知も追加して」と言われたら?ループの中にメール送信処理を追加することになります。

もし「Slack通知も追加して」と言われたら?さらにSlack送信処理を追加…

 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
# こうなる未来が見える
for my $event (@events) {
    # ログ出力
    say "...";

    # 脅威スコア計算
    my $score = ...;

    # アラートチェック
    if ($total_score >= $alert_threshold) {
        say "!!!警告!!!";
    }

    # メール通知(追加)
    if ($should_send_email) {
        send_email($event);
    }

    # Slack通知(追加)
    if ($should_send_slack) {
        send_slack($event);
    }

    # LINE通知(追加)...もう勘弁して
}

これは「開放閉鎖の原則(OCP)」に違反しています。新しい機能を追加するたびに既存コードを変更しなければなりません。

今回のまとめ

今回は「通知先を増やしたい」という素直な要望に応えた結果、コードが破綻していく様子を見ました。

  • 1つのループで複数の責務を担当(SRP違反)
  • 通知先追加のたびに既存コードを変更(OCP違反)

このままでは保守性が悪く、バグも混入しやすくなります。

次回予告

次回はこの問題を解決するために「通知係を分離」します。ログ担当、スコア担当をそれぞれ別のクラスにすることで、責務を明確に分けていきます。

設計がスッキリしていく過程をお楽しみに。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。