Featured image of post 第8回-脅威スコアを拡張せよ(OCP) - Perlでハニーポット侵入レーダーを作ろう

第8回-脅威スコアを拡張せよ(OCP) - Perlでハニーポット侵入レーダーを作ろう

開放閉鎖の原則(OCP)を実践して、新しいRiskLevelObserverを追加します。既存コードを一切変更せずに機能拡張できることを確認しましょう。

@nqounetです。

前回はdoes制約で偽物Observerの登録を防ぎました。型チェックによって、登録時点で問題を検出できるようになりましたね。

今回は「新しい脅威判定機能を追加したい」という要望に応えます。ポイントは既存コードを変更しないこと。

開放閉鎖の原則(OCP)

SOLID原則の「O」、開放閉鎖の原則(Open-Closed Principle)を覚えていますか?

  • 拡張に対して開いている: 新しい機能を追加できる
  • 修正に対して閉じている: 既存コードを変更しない

今までの設計がこの原則を満たしているか、試してみましょう。

RiskLevelObserverを追加

「脅威レベルを3段階で表示したい」という要望が来ました。LOW / MEDIUM / HIGH の判定です。

  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
#!/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 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;
}

# 新しいObserver(追加)
package RiskLevelObserver;
use Moo;
with 'IntrusionObserver';

my %risk_levels = (
    'SSH Brute Force'       => 'HIGH',
    'Port Scan'             => 'LOW',
    'SQL Injection Attempt' => 'HIGH',
    'XSS Attack'            => 'MEDIUM',
);

sub update ($self, $event) {
    my $level = $risk_levels{$event->attack_type} // 'MEDIUM';
    my $indicator = $level eq 'HIGH'   ? '!!!'
                  : $level eq 'MEDIUM' ? '!!'
                  : '!';
    say "[$indicator リスクレベル: $level] " . $event->attack_type;
}

package IntrusionHub;
use Moo;

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

sub attach ($self, $observer) {
    unless ($observer->does('IntrusionObserver')) {
        die "Error: IntrusionObserverロールを実装していないオブジェクトは登録できません";
    }
    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
$hub->attach(RadarLogObserver->new);
$hub->attach(ThreatScoreObserver->new);

# 新しいObserverを追加
$hub->attach(RiskLevelObserver->new);

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

say "=== 侵入イベント処理 ===";
say "";
for my $event (@events) {
    $hub->notify($event);
    say "";
}

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

1
2
3
4
5
6
7
8
9
=== 侵入イベント処理 ===

[ログ] SSH Brute Force from 192.168.1.100
[スコア] 累計: 80
[!!! リスクレベル: HIGH] SSH Brute Force

[ログ] Port Scan from 10.0.0.55
[スコア] 累計: 110
[! リスクレベル: LOW] Port Scan

変更したコードを確認する

さて、ここで重要な質問です。

「RiskLevelObserverを追加するために、既存のコードをどこか変更しましたか?」

答えはNOです。変更したのは以下だけ。

  1. RiskLevelObserverクラスを新規作成
  2. main$hub->attach(RiskLevelObserver->new)を追加

既存のRadarLogObserverThreatScoreObserverIntrusionHubIntrusionEvent一切変更していません

これが**開放閉鎖の原則(OCP)**です。

OCPの効果

OCPを守ると、こんな良いことがあります。

  • 既存コードにバグを混入させるリスクがない
  • 既存機能のテストを再実行する必要が最小限
  • 新機能の追加が独立したタスクになる

逆にOCPを破ると、新機能を追加するたびに既存コードを修正することになり、思わぬ副作用を引き起こすリスクが高まります。

今回のまとめ

今回は開放閉鎖の原則(OCP)を実践しました。

  • RiskLevelObserverを新規作成
  • 既存コード(ObserverやHub)は変更なし
  • 拡張に開き、修正に閉じる設計

この設計の柔軟性を実感できたでしょうか。

次回予告

次回はいよいよ「完成版」です!これまで作ってきたすべてのObserverを統合し、ハニーポット侵入レーダーの司令室を完成させます。

すべてが連動する達成感をお楽しみに。

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