Featured image of post 第7回-偽物Observerを排除 - Perlでハニーポット侵入レーダーを作ろう

第7回-偽物Observerを排除 - Perlでハニーポット侵入レーダーを作ろう

IntrusionHubにdoes制約を導入し、IntrusionObserverロールを実装していないオブジェクトの登録を拒否します。型制約による堅牢な設計を学びましょう。

@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の入り口で不正なオブジェクトを排除しています。

早期発見の価値

型チェックを登録時点で行うことの価値は大きいです。

チェックなしの場合

  1. attachで偽物を登録(エラーなし)
  2. しばらく運用…
  3. notifyupdateを呼び出し
  4. Can't locate object method "update"でクラッシュ

チェックありの場合

  1. attachで偽物を登録しようとする
  2. 即座にエラー
  3. 問題の原因が明確

バグの発見が早ければ早いほど、修正コストは下がります。

今回のまとめ

今回はIntrusionHubに型チェックを導入し、偽物Observerの登録を防ぎました。

  • doesメソッドでRoleの実装を確認
  • 登録時点で不正なオブジェクトを排除
  • 問題の早期発見で保守性向上

これで司令塔の堅牢性が向上しました。

次回予告

次回は「新しい脅威判定を追加しても既存コードを変更しない」という話です。開放閉鎖の原則(OCP)を実践し、RiskLevelObserverを追加します。

拡張に開き、修正に閉じる設計の真価をご覧ください。

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