Featured image of post 第5回-侵入司令塔を作る - Perlでハニーポット侵入レーダーを作ろう

第5回-侵入司令塔を作る - Perlでハニーポット侵入レーダーを作ろう

複数のObserverを一元管理し、侵入イベント発生時に一斉通知するIntrusionHub(司令塔)を実装します。attach/detach/notifyの基本構造を作りましょう。

@nqounetです。

前回はMoo::Roleで通知担当の契約を定義しました。Observerはupdateメソッドを持つことが保証されています。

でも、現在のコードには面倒な点が残っています。

1
2
3
4
5
for my $event (@events) {
    $log_observer->update($event);
    $score_observer->update($event);
    # 通知先が増えるたびにここに追加...
}

通知先が増えるたびに、ループ内のコードを変更しなければなりません。今回は「通知を一元管理する司令塔」を作って、この問題を解決します。

司令塔の設計

司令塔(IntrusionHub)は以下の役割を持ちます。

  • Observerを登録する(attach)
  • Observerを解除する(detach)
  • 全Observerに通知する(notify)
	classDiagram
    class IntrusionHub {
        +observers[]
        +attach(observer)
        +detach(observer)
        +notify(event)
    }
    class IntrusionObserver {
        <<interface>>
        +update(event)
    }
    class RadarLogObserver {
        +update(event)
    }
    class ThreatScoreObserver {
        +update(event)
    }
    IntrusionHub --> IntrusionObserver : notifies
    RadarLogObserver ..|> IntrusionObserver
    ThreatScoreObserver ..|> IntrusionObserver

イベントが発生したら、司令塔が登録されているすべてのObserverに通知を送ります。

  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
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/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 IntrusionObserver;
use Moo::Role;

requires 'update';

package RadarLogObserver;
use Moo;
with 'IntrusionObserver';

sub update ($self, $event) {
    say "=== 侵入イベント検知 ===";
    say "時刻: " . $event->timestamp;
    say "発信元: " . $event->source_ip;
    say "攻撃種別: " . $event->attack_type;
    say "";
}

package ThreatScoreObserver;
use Moo;
with 'IntrusionObserver';

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

has total_score => (
    is      => 'rw',
    default => 0,
);

sub update ($self, $event) {
    my $score = $threat_scores{$event->attack_type} // 50;
    $self->total_score($self->total_score + $score);
    say "[脅威スコア] $score 加算 (累計: " . $self->total_score . ")";
}

# 司令塔クラス
package IntrusionHub;
use Moo;

has observers => (
    is      => 'ro',
    default => sub { [] },
);

sub attach ($self, $observer) {
    push $self->observers->@*, $observer;
    say "[司令塔] Observerを登録しました";
}

sub detach ($self, $observer) {
    $self->observers->@* = grep { $_ != $observer } $self->observers->@*;
    say "[司令塔] Observerを解除しました";
}

sub notify ($self, $event) {
    for my $observer ($self->observers->@*) {
        $observer->update($event);
    }
}

package main;

# 司令塔を作成
my $hub = IntrusionHub->new;

# Observerを登録
my $log_observer   = RadarLogObserver->new;
my $score_observer = ThreatScoreObserver->new;

$hub->attach($log_observer);
$hub->attach($score_observer);

# イベント発生!
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) {
    $hub->notify($event);
}

実行結果はこうなります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[司令塔] Observerを登録しました
[司令塔] Observerを登録しました
=== 侵入イベント検知 ===
時刻: 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
2
3
for my $event (@events) {
    $hub->notify($event);
}

たった1行です。どのObserverが登録されているかは、ループ側は知りません。「司令塔に通知を依頼する」だけで、司令塔が登録済みの全Observerに通知を届けてくれます。

attach/detach/notify

司令塔の3つのメソッドを確認しましょう。

1
2
3
sub attach ($self, $observer) {
    push $self->observers->@*, $observer;
}

attachはObserverをリストに追加します。

1
2
3
sub detach ($self, $observer) {
    $self->observers->@* = grep { $_ != $observer } $self->observers->@*;
}

detachはObserverをリストから削除します(今回は使っていませんが、次回活躍します)。

1
2
3
4
5
sub notify ($self, $event) {
    for my $observer ($self->observers->@*) {
        $observer->update($event);
    }
}

notifyは登録されているすべてのObserverのupdateを呼び出します。

今回のまとめ

今回は司令塔(IntrusionHub)を作成し、Observerを一元管理できるようにしました。

  • attach: Observerを登録
  • detach: Observerを解除
  • notify: 全Observerに通知

イベント処理のコードがシンプルになり、新しいObserverを追加するときもattachで登録するだけで済むようになりました。

次回予告

次回はdetachの出番です。「深夜帯だけアラートを有効にしたい」という要望に応えるため、Observerを動的に登録・解除する機能を活用します。

ランタイムでの切り替えがいかに便利か、体感してください。

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