Featured image of post 第6回-監視ON/OFFを切り替える - Perlでハニーポット侵入レーダーを作ろう

第6回-監視ON/OFFを切り替える - Perlでハニーポット侵入レーダーを作ろう

深夜帯だけアラートを有効にするなど、Observerを動的に登録・解除する機能を実装します。attach/detachを活用したランタイム制御で柔軟な運用を実現しましょう。

@nqounetです。

前回は司令塔(IntrusionHub)を作成し、Observerを一元管理できるようにしました。attachで登録、detachで解除、notifyで一斉通知という構造です。

今回は「深夜帯だけアラートを有効にしたい」という運用要件に応えます。detachの出番です。

運用シナリオ

ハニーポットの運用では、こんな要望が出てきます。

  • 日中はログとスコア計算だけでいい
  • 深夜(侵入が多い時間帯)はアラート通知も欲しい
  • 状況に応じて通知先を切り替えたい

攻撃者は夜行性なのでしょうか?深夜帯のほうが侵入試行が増える傾向にあります(まあ、どこかのタイムゾーンでは昼間ですが)。

これを実現するには、Observerを動的に登録・解除できる必要があります。

AlertObserverを追加する

まず、アラート通知を担当する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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#!/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 "[ログ] " . $event->timestamp . " / " . $event->source_ip . " / " . $event->attack_type;
}

package ThreatScoreObserver;
use Moo;
with 'IntrusionObserver';

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

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 "[スコア] 累計: " . $self->total_score;
}

# アラート担当(新規追加)
package AlertObserver;
use Moo;
with 'IntrusionObserver';

sub update ($self, $event) {
    say "!!! [警報] 侵入検知: " . $event->attack_type . " from " . $event->source_ip . " !!!";
}

package IntrusionHub;
use Moo;

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

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

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

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);

# 深夜帯専用のObserver
my $alert_observer = AlertObserver->new;

# イベントを用意
my @events = (
    IntrusionEvent->new(
        timestamp   => '2026-01-18T14:00:00+09:00',
        source_ip   => '192.168.1.100',
        attack_type => 'Port Scan',
    ),
    IntrusionEvent->new(
        timestamp   => '2026-01-18T02:30:00+09:00',
        source_ip   => '10.0.0.55',
        attack_type => 'SSH Brute Force',
    ),
    IntrusionEvent->new(
        timestamp   => '2026-01-18T03:15:00+09:00',
        source_ip   => '172.16.0.22',
        attack_type => 'SQL Injection Attempt',
    ),
);

say "=== 日中モード ===";
$hub->notify($events[0]);

say "";
say "=== 深夜モード(アラートON)===";
$hub->attach($alert_observer);  # 深夜になったのでアラートを有効化
$hub->notify($events[1]);

say "";
say "=== 引き続き深夜モード ===";
$hub->notify($events[2]);

say "";
say "=== 朝になったのでアラートOFF ===";
$hub->detach($alert_observer);  # 朝になったのでアラートを無効化
$hub->notify($events[0]);  # 再度同じイベントで確認

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
=== 日中モード ===
[ログ] 2026-01-18T14:00:00+09:00 / 192.168.1.100 / Port Scan
[スコア] 累計: 30

=== 深夜モード(アラートON)===
[ログ] 2026-01-18T02:30:00+09:00 / 10.0.0.55 / SSH Brute Force
[スコア] 累計: 110
!!! [警報] 侵入検知: SSH Brute Force from 10.0.0.55 !!!

=== 引き続き深夜モード ===
[ログ] 2026-01-18T03:15:00+09:00 / 172.16.0.22 / SQL Injection Attempt
[スコア] 累計: 200
!!! [警報] 侵入検知: SQL Injection Attempt from 172.16.0.22 !!!

=== 朝になったのでアラートOFF ===
[ログ] 2026-01-18T14:00:00+09:00 / 192.168.1.100 / Port Scan
[スコア] 累計: 230

動的登録・解除の威力

注目すべきは、コードの変更なしに通知先を切り替えられる点です。

1
2
3
$hub->attach($alert_observer);  # アラートを追加
# ...しばらく運用...
$hub->detach($alert_observer);  # アラートを解除

既存のコード(RadarLogObserverThreatScoreObserver)には一切手を加えていません。これが**開放閉鎖の原則(OCP)**の効果です。

現実的な使い方

実際の運用では、時刻判定を組み合わせることになるでしょう。

1
2
3
4
5
6
7
# 疑似コード
my $hour = (localtime)[2];
if ($hour >= 22 || $hour < 6) {
    $hub->attach($alert_observer);  # 深夜帯
} else {
    $hub->detach($alert_observer);  # 日中
}

外部の条件(時刻、負荷状況、設定ファイルなど)に応じて、柔軟に通知先を制御できます。

今回のまとめ

今回はObserverの動的登録・解除を実装しました。

  • AlertObserverを新規作成(アラート通知担当)
  • attachで深夜モードに切り替え
  • detachで日中モードに切り替え
  • 既存コードへの変更なしで機能追加

ランタイムでの柔軟な制御が可能になりました。

次回予告

次回は「偽物のObserverを排除する」話です。現在のattachは何でも受け入れてしまいますが、IntrusionObserverロールを実装していないものを弾けるようにしましょう。

Mooの型制約機能を使った堅牢化です。

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