@nqounetです。
前回はObserverを動的に登録・解除する機能を実装しました。深夜だけアラートをONにする、といった柔軟な運用が可能になりましたね。
でも、今のIntrusionHubには危険な穴があります。
問題: 何でも登録できてしまう
現在のattachメソッドを見てみましょう。
1
2
3
| sub attach ($self, $observer) {
push $self->observers->@*, $observer;
}
|
引数の$observerに対して、何のチェックもしていません。つまり、IntrusionObserverロールを実装していないオブジェクトでも登録できてしまいます。
1
2
3
4
5
6
7
8
9
10
11
12
| # こんな偽物でも登録できてしまう
package FakeObserver;
use Moo;
# updateメソッドを持っていない!
package main;
my $fake = FakeObserver->new;
$hub->attach($fake); # エラーにならない...
$hub->notify($event); # ←ここで爆発
|
notifyで全Observerのupdateを呼び出すときに初めてエラーになります。これでは遅い。登録時点で弾きたいのです。
does制約で型チェック
Mooにはdoesという型制約があります。これは「指定したRoleを実装しているか」をチェックします。
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
| #!/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->attack_type . " from " . $event->source_ip;
}
package FakeObserver;
use Moo;
# updateメソッドを持たない偽物
package IntrusionHub;
use Moo;
has observers => (
is => 'ro',
default => sub { [] },
);
sub attach ($self, $observer) {
# does制約でチェック
unless ($observer->does('IntrusionObserver')) {
die "Error: IntrusionObserverロールを実装していないオブジェクトは登録できません";
}
push $self->observers->@*, $observer;
say "[司令塔] Observerを登録しました: " . ref($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;
$hub->attach($log_observer); # OK
# 偽物を登録しようとすると...
my $fake = FakeObserver->new;
eval { $hub->attach($fake) }; # エラー!
if ($@) {
say "登録拒否: $@";
}
|
実行結果はこうなります。
1
2
| [司令塔] Observerを登録しました: RadarLogObserver
登録拒否: Error: IntrusionObserverロールを実装していないオブジェクトは登録できません at ...
|
doesメソッドの仕組み
Mooでは、クラスがwithでRoleを取り込むと、そのクラスのインスタンスはdoesメソッドで確認できるようになります。
1
2
| $log_observer->does('IntrusionObserver'); # true
$fake->does('IntrusionObserver'); # false
|
これを利用して、attachの入り口で不正なオブジェクトを排除しています。
早期発見の価値
型チェックを登録時点で行うことの価値は大きいです。
チェックなしの場合
attachで偽物を登録(エラーなし)- しばらく運用…
notifyでupdateを呼び出しCan't locate object method "update"でクラッシュ
チェックありの場合
attachで偽物を登録しようとする- 即座にエラー
- 問題の原因が明確
バグの発見が早ければ早いほど、修正コストは下がります。
今回のまとめ
今回はIntrusionHubに型チェックを導入し、偽物Observerの登録を防ぎました。
doesメソッドでRoleの実装を確認- 登録時点で不正なオブジェクトを排除
- 問題の早期発見で保守性向上
これで司令塔の堅牢性が向上しました。
次回予告
次回は「新しい脅威判定を追加しても既存コードを変更しない」という話です。開放閉鎖の原則(OCP)を実践し、RiskLevelObserverを追加します。
拡張に開き、修正に閉じる設計の真価をご覧ください。