
内容に関しましては基本的に筆者が慎重にレビューや動作確認、ファクトチェックを行なっておりますが、万が一問題と思われる点ございましたら、コメント欄にてご指摘くださいませ。
- 1. イントロダクション:なぜ今更、自前で環境を構築するのか
- 2. アーキテクチャ設計論:防御と可用性の融合
- 3. インフラストラクチャ構築
- 4. アプリケーションのセットアップ
- 5. テスト・検証
- 6. 実運用のイメージ
- 7. まとめ
1. イントロダクション:なぜ今更、自前で環境を構築するのか
1.1 プライベートクラウド回帰の潮流とセキュリティの課題
パブリック クラウド サービス、みなさん使っていますか?
勉強のために色々と検証用の VM 沢山立てたりしたいですよね?
でも一方でリソースを作りすぎ、使いすぎでコストが爆発したりするのも怖いですよね....?
本記事ではそんな慎重派の方でも簡単にクラウド環境構築の学習を、 Proxmox VE (Virtual Environment) という OSS のサーバー仮想化プラットフォームを利用上で Nginx を動かし、さらに Suricata を導入して頑健性のある環境に仕立てあげてみましょう。
(余談ですが、AWS サービスをローカル環境で再現する LocalStack という OSS もあります 。今回はよりベアメタルサーバーに近い環境の検証構築をするために Proxmox VE を選択しています。)
実は近年コストの圧縮、データの主権性、そしてレイテンシの制御といった観点から、オンプレミス環境やコロケーション環境における「プライベートクラウド」の価値が見直されており、海外では移行した事例を紹介する記事がちらほら出てたりします*1 *2 。
一方で、プライベートクラウドではセキュリティに関してパブリッククラウドより慎重にならなければなりません。
特に、K8s のようなコンテナオーケストレーションシステムを導入する場合、接続パターンが膨大かつ動的に変化するため、従来の境界防御モデル (FirewallによるIP/Port制限) を適用しただけでは適切なアクセルコントロールを実現することは困難で、マイクロサービス間の通信、外部からのIngressトラフィック、そして暗号化されたHTTPS通信の中に潜む脅威を検知・遮断する必要があります。
そのため自前で IDPS を用意して、よりセキュアな環境を構築して(自前でそのような環境を用意するのがどれぐらい大変か体感して)みましょう。
1.2 本記事の目的
本稿は、Proxmox VE上に構築されたNginxに対し、商用アプライアンスに匹敵する「エンタープライズレベル」の可用性とセキュリティを目指したものになります。
具体的には、以下の技術スタックを統合し、単一障害点(SPOF)のない、かつ多層防御を実現するアーキテクチャを設計・構築します。
Keepalived: VRRPを用いた高可用性(HA)の確保。
HAProxy: 高度なレイヤー4/7ロードバランシングとSSLオフロード。
Suricata: 次世代IDS/IPSエンジンによる、復号済みトラフィックのインライン検査。
Nginx: アプリケーションへのルーティングとProxy Protocolの処理。
2. アーキテクチャ設計論:防御と可用性の融合
2.1 全体像
┌─────────────────────────────────────────────────────────────────────────────┐ │ Proxmox VE ホスト │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ vmbr0 (Linux Bridge) │ │ │ │ 192.168.1.0/24 ネットワーク │ │ │ └──────┬──────────────┬──────────────┬──────────────┬─────────────────┘ │ │ │ │ │ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ │ │ lb-1 │ │ lb-2 │ │ web-1 │ │ web-2 │ │ │ │ VM 101 │ │ VM 102 │ │ VM 201 │ │ VM 202 │ │ │ │ .11 │ │ .12 │ │ .21 │ │ .22 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ VIP: 192.168.1.100 │ └─────────────────────────────────────────────────────────────────────────────┘
環境
- Proxmox VE: 8.x
- ゲストOS: Ubuntu 24.04 LTS (Cloud-Init イメージ使用)
- ストレージ: local-lvm
| VM ID | hostname | IP | role | vCPU | RAM |
|---|---|---|---|---|---|
| 101 | lb-1 | 192.168.1.11 | ロードバランサー1(MASTER) | 2 | 2GB |
| 102 | lb-2 | 192.168.1.12 | ロードバランサー2(BACKUP) | 2 | 2GB |
| 201 | web-1 | 192.168.1.21 | Webサーバー1 | 2 | 2GB |
| 202 | web-2 | 192.168.1.22 | Webサーバー2 | 2 | 2GB |
| - | VIP | 192.168.1.100 | 仮想IP(VRRP) | - | - |
また、構築するシステムの論理的なトラフィックフローは以下です。
| ステップ | コンポーネント | アクション | プロトコル/状態 | 詳細解説 |
|---|---|---|---|---|
| 1 | Client | リクエスト送信 | HTTPS (Encrypted) | インターネットからのアクセス。 |
| 2 | Keepalived | VIP受付 | VRRP | 稼働中のGatewayノードへトラフィックを誘導。 |
| 3 | HAProxy (Frontend) | 受信 | HTTP | 今回はただのLBとして扱いますが、HTTPSの通信を扱う場合には、ここでSSL/TLSを解除し、パケットの中身(ヘッダ、ボディ)を露出させます。 |
| 4 | Netfilter (iptables) | キューイング | NFQUEUE | HAProxyからバックエンドへ向かうパケットをカーネルレベルで捕捉。 |
| 5 | Suricata | インライン検査 | IPS Mode | ユーザー空間でパケットを検査。シグネチャマッチでDrop/Passを判定。 |
| 6 | HAProxy (Backend) | 転送 | HTTP + Proxy Protocol | 検査済みパケットにクライアントIP情報を付与して送信。 |
| 7 | Nginx | ルーティング | HTTP | Proxy Protocolを解釈し、正しい送信元IPでログ記録・転送。 |
2.2 コンポーネント選定の技術的根拠
Proxmox VE:インフラの基盤
ベアメタルハイパーバイザーとしての効率性と、Debianベースの運用しやすさが選定理由です。特に、virtioドライバによるネットワークI/Oの準ネイティブなパフォーマンスは、パケット処理を主とするGatewayノードでも比較的高い性能を発揮してくれるでしょう。
Keepalived:シンプルかつ堅牢なHA
Pacemaker/Corosyncのような重量級クラスタリングソフトウェアと比較し、KeepalivedはVRRP(Virtual Router Redundancy Protocol)に特化しており、設定がシンプルで誤動作のリスクが低い点が強みです。HAProxyのプロセス監視機能(vrrp_script)と組み合わせることで、OSが生きていてもサービスが死んでいる場合に即座にフェイルオーバーを実現できます。
HAProxy:高性能ロードバランサ
Nginxもロードバランサとして優秀ですが、HAProxyは「ロードバランシング専業」として設計されており、キューイングの制御、詳細なヘルスチェック、管理画面(Stats Page)の視認性において優位性があります。
Suricata:マルチスレッドIPS
Snort(特に2.x系)がシングルスレッドであったのに対し、Suricataは当初からマルチスレッドアーキテクチャを採用しており、現代のマルチコアCPUの性能を最大限に引き出せます。また、NFQUEUE モードにおける安定性と、L7プロトコル解析(HTTP, DNS, TLS等)の精度の高さから、インラインIPSとして利用されるのを目にします。また、AWSのNetwork FirewallにはSuricata互換のIPSルールを設定できたりしますので、ネットワークを構築する方は一度眺めてみておくと良いかもしれません。
3. インフラストラクチャ構築
3.0 物理サーバー確保
街を歩いていると、なぜか生えてきます。
私は以下の構成の物理サーバーが気がついたら生えてきていました。
3.1 Proxmoxネットワーク構成
Proxmoxでは標準のLinux Bridge(vmbr)またはOpen vSwitch(OVS)が利用可能ですが、複雑なSDN(Software Defined Networking)を構築しない限り、カーネルネイティブなLinux Bridgeの方がオーバーヘッドが少なく、構築やトラブルシューティングも比較的容易なので、今回は1つのLinux Bridgeで配下で全てのVMを繋げます。
3.1.1 Linux Bridgeの確認・設定
Proxmox VEのWebUI → ノード → System → Network で vmbr0 が存在することを確認します。
存在しない場合、または追加のブリッジが必要な場合は、SSHでProxmoxホストに接続して設定します。
/etc/network/interfaces の例:
# Proxmoxホストで実行
cat << 'EOF' > /etc/network/interfaces
auto lo
iface lo inet loopback
# 物理NIC
iface enp0s3 inet manual
# VMネットワーク用ブリッジ
auto vmbr0
iface vmbr0 inet static
address 192.168.1.1/24
gateway 192.168.1.254
bridge-ports enp0s3
bridge-stp off
bridge-fd 0
EOF
# ネットワーク設定を適用
ifreload -a
注意:
enp0s3は実際のNIC名に置き換えてください。ip linkで確認できます。
3.2 VMの作成
VM を効率的に作成するため、Ubuntu Cloud-Init テンプレートを作成します。
3.2.1 Cloud-Initイメージのダウンロード
# Proxmoxホストで実行 cd /var/lib/vz/template/iso/ # Ubuntu 24.04 Cloud イメージをダウンロード wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img # QEMUのディスクイメージサイズを拡張(7GB) qemu-img resize noble-server-cloudimg-amd64.img 7G
3.2.2 テンプレートVMの作成
# テンプレートVM作成(VM ID: 9000)
qm create 9000 --name "ubuntu-2404-cloudinit-template" \
--ostype l26 \
--memory 2048 \
--cores 2 \
--cpu host \
--net0 virtio,bridge=vmbr0 \
--scsihw virtio-scsi-pci \
--serial0 socket \
--vga serial0 \
--agent enabled=1
# ディスクをインポート
qm set 9000 --scsi0 local-lvm:0,import-from=/var/lib/vz/template/iso/noble-server-cloudimg-amd64.img
# Cloud-Initドライブを追加
qm set 9000 --ide2 local-lvm:cloudinit
# ブートデバイスを設定
qm set 9000 --boot order=scsi0
# Cloud-Initの基本設定
qm set 9000 --ipconfig0 ip=dhcp
qm set 9000 --ciuser ubuntu
qm set 9000 --cipassword "ChangeMe123!" # 変更してください
# テンプレートに変換
qm template 9000
3.2.3 テンプレートの確認
qm list | grep template
これで、テンプレートが完成しました!
これをクローンしてどしどしVMを作っていきましょう。
3.3: VMのクローンと作成
3.3.1 ロードバランサーVM の作成
# lb-1 (VM ID: 101) qm clone 9000 101 --name lb-1 --full qm set 101 --memory 2048 --cores 2 qm set 101 --ipconfig0 ip=192.168.1.11/24,gw=192.168.1.254 qm set 101 --ciuser ubuntu qm set 101 --cipassword "LB1Pass123!" # lb-2 (VM ID: 102) qm clone 9000 102 --name lb-2 --full qm set 102 --memory 2048 --cores 2 qm set 102 --ipconfig0 ip=192.168.1.12/24,gw=192.168.1.254 qm set 102 --ciuser ubuntu qm set 102 --cipassword "LB2Pass123!"
3.3.2 WebサーバーVM の作成
# web-1 (VM ID: 201) qm clone 9000 201 --name web-1 --full qm set 201 --memory 2048 --cores 2 qm set 201 --ipconfig0 ip=192.168.1.21/24,gw=192.168.1.254 qm set 201 --ciuser ubuntu qm set 201 --cipassword "Web1Pass123!" # web-2 (VM ID: 202) qm clone 9000 202 --name web-2 --full qm set 202 --memory 2048 --cores 2 qm set 202 --ipconfig0 ip=192.168.1.22/24,gw=192.168.1.254 qm set 202 --ciuser ubuntu qm set 202 --cipassword "Web2Pass123!"
3.3.3 全VMの起動
# 全VMを起動 qm start 101 qm start 102 qm start 201 qm start 202 # 起動確認 qm list
4. アプリケーションのセットアップ
4.1 Webサーバー(Nginx)のセットアップ
4.1.1 web-1, web-2 へ SSH 接続
# Proxmoxホストまたは同一ネットワークから接続 ssh ubuntu@192.168.1.21 # web-1 ssh ubuntu@192.168.1.22 # web-2
4.1.2 Nginx のインストール(両方のWebサーバーで実行)
# rootに昇格 sudo -i # パッケージ更新とNginxインストール apt update && apt upgrade -y apt install nginx -y # サービス有効化 systemctl enable nginx systemctl start nginx
4.1.3 テストページの作成
web-1 で実行:
cat << 'EOF' > /var/www/html/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Server 1</title>
<style>
body {
font-family: 'Segoe UI', Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
text-align: center;
padding: 50px;
background: rgba(255,255,255,0.15);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
h1 { font-size: 2.5em; margin-bottom: 20px; }
.server-info {
background: rgba(0,0,0,0.2);
padding: 15px;
border-radius: 10px;
margin-top: 20px;
}
.status { color: #00ff88; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h1>🖥️ Web Server 1</h1>
<div class="server-info">
<p><strong>ホスト名:</strong> web-1</p>
<p><strong>IP:</strong> 192.168.1.21</p>
<p><strong>ステータス:</strong> <span class="status">● Active</span></p>
</div>
<p style="margin-top:20px; font-size:0.9em;">
HAProxy + Keepalived + Suricata + Nginx
</p>
</div>
</body>
</html>
EOF
web-2 で実行:
cat << 'EOF' > /var/www/html/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Server 2</title>
<style>
body {
font-family: 'Segoe UI', Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
}
.container {
text-align: center;
padding: 50px;
background: rgba(255,255,255,0.15);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
h1 { font-size: 2.5em; margin-bottom: 20px; }
.server-info {
background: rgba(0,0,0,0.2);
padding: 15px;
border-radius: 10px;
margin-top: 20px;
}
.status { color: #00ff88; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h1>🖥️ Web Server 2</h1>
<div class="server-info">
<p><strong>ホスト名:</strong> web-2</p>
<p><strong>IP:</strong> 192.168.1.22</p>
<p><strong>ステータス:</strong> <span class="status">● Active</span></p>
</div>
<p style="margin-top:20px; font-size:0.9em;">
HAProxy + Keepalived + Suricata + Nginx
</p>
</div>
</body>
</html>
EOF
4.1.4 動作確認
# 各Webサーバーで実行 curl http://localhost systemctl status nginx
4.2: ロードバランサー(HAProxy + Keepalived + Suricata)のセットアップ
4.2.1 lb-1, lb-2 へ SSH 接続
ssh ubuntu@192.168.1.11 # lb-1 ssh ubuntu@192.168.1.12 # lb-2
4.2.2 パッケージのインストール(両方のLBで実行)
sudo -i apt update && apt upgrade -y apt install haproxy keepalived suricata -y
4.3 HAProxy の設定
4.3.1 HAProxy設定(lb-1, lb-2 共通)
cat << 'EOF' > /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
#---------------------------------------------------------------------
# Defaults
#---------------------------------------------------------------------
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
#---------------------------------------------------------------------
# Statistics page
#---------------------------------------------------------------------
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:HaProxyAdmin123 # 本番環境では変更
#---------------------------------------------------------------------
# Frontend: HTTP (port 80)
#---------------------------------------------------------------------
frontend http_front
bind *:80
option tcplog
default_backend http_back
#---------------------------------------------------------------------
# Backend: Nginx Web Servers
#---------------------------------------------------------------------
backend http_back
balance roundrobin
option httpchk GET /
http-check expect status 200
server web-1 192.168.1.21:80 check inter 2000 rise 2 fall 3
server web-2 192.168.1.22:80 check inter 2000 rise 2 fall 3
EOF
# 設定確認
haproxy -c -f /etc/haproxy/haproxy.cfg
# サービス有効化・起動
systemctl enable haproxy
systemctl restart haproxy
systemctl status haproxy
4.4 Keepalived の設定
4.4.1 カーネルパラメータの設定(lb-1, lb-2 両方)
# VIPバインド用の設定 cat << 'EOF' >> /etc/sysctl.conf # Allow binding to non-local IP addresses net.ipv4.ip_nonlocal_bind = 1 EOF sysctl -p
4.4.2 lb-1(MASTER)の設定
cat << 'EOF' > /etc/keepalived/keepalived.conf
# lb-1 keepalived config (MASTER)
global_defs {
router_id LB1
script_user root
enable_script_security
}
vrrp_script check_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight 2
fall 2
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0 # ここでエラーが発生する場合には、 `ip a` を実行して正しい名前確認してください
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass K33p@l1v3d! # 適宜変更してください
}
unicast_src_ip 192.168.1.11
unicast_peer {
192.168.1.12
}
virtual_ipaddress {
192.168.1.100/24
}
track_script {
check_haproxy
}
}
EOF
systemctl enable keepalived
systemctl restart keepalived
4.4.3 lb-2(BACKUP)の設定
cat << 'EOF' > /etc/keepalived/keepalived.conf
# lb-2 keepalived config (BACKUP)
global_defs {
router_id LB2
script_user root
enable_script_security
}
vrrp_script check_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight 2
fall 2
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface eth0 # ここでエラーが発生する場合には、 `ip a` を実行して正しい名前確認してください
virtual_router_id 51
priority 99
advert_int 1
authentication {
auth_type PASS
auth_pass K33p@l1v3d! # 適宜変更してください
}
unicast_src_ip 192.168.1.12
unicast_peer {
192.168.1.11
}
virtual_ipaddress {
192.168.1.100/24
}
track_script {
check_haproxy
}
}
EOF
systemctl enable keepalived
systemctl restart keepalived
4.4.4 ネットワークインターフェース名の確認
# 実際のインターフェース名を確認 ip link show # 通常、Proxmox Cloud-Init VMでは ens18 または eth0 # 必要に応じて keepalived.conf の interface を修正
4.5 Suricata(IDS)の設定
4.5.1 基本設定(lb-1, lb-2 両方)
# バックアップ
cp /etc/suricata/suricata.yaml /etc/suricata/suricata.yaml.bak
# 主要な設定を編集
sudo cat << 'EOF' > /etc/suricata/suricata.yaml
%YAML 1.1
---
vars:
address-groups:
HOME_NET: "[192.168.1.0/24]"
EXTERNAL_NET: "!$HOME_NET"
HTTP_SERVERS: "[192.168.1.21/32, 192.168.1.22/32]"
DNS_SERVERS: "$HOME_NET"
SMTP_SERVERS: "$HOME_NET"
SQL_SERVERS: "$HOME_NET"
TELNET_SERVERS: "$HOME_NET"
AIM_SERVERS: "$EXTERNAL_NET"
DC_SERVERS: "$HOME_NET"
DNP3_SERVER: "$HOME_NET"
DNP3_CLIENT: "$HOME_NET"
MODBUS_CLIENT: "$HOME_NET"
MODBUS_SERVER: "$HOME_NET"
ENIP_CLIENT: "$HOME_NET"
ENIP_SERVER: "$HOME_NET"
port-groups:
HTTP_PORTS: "80"
SHELLCODE_PORTS: "!80"
ORACLE_PORTS: 1521
SSH_PORTS: 22
DNP3_PORTS: 20000
MODBUS_PORTS: 502
FILE_DATA_PORTS: "[$HTTP_PORTS,110,143]"
FTP_PORTS: 21
GENEVE_PORTS: 6081
VXLAN_PORTS: 4789
TEREDO_PORTS: 3544
default-log-dir: /var/log/suricata/
stats:
enabled: yes
interval: 8
outputs:
- fast:
enabled: yes
filename: fast.log
append: yes
- eve-log:
enabled: yes
filetype: regular
filename: eve.json
pcap-file: false
community-id: true
community-id-seed: 0
types:
- alert:
tagged-packets: yes
payload: yes
payload-buffer-size: 4kb
payload-printable: yes
packet: yes
metadata: yes
http-body: yes
http-body-printable: yes
- anomaly:
enabled: yes
types:
decode: yes
stream: yes
applayer: yes
- http:
extended: yes
custom: [accept, accept-charset, accept-encoding, accept-language,
accept-datetime, authorization, cache-control, cookie,
from, max-forwards, origin, pragma, proxy-authorization,
range, te, via, x-requested-with, x-forwarded-for,
x-authenticated-user]
dump-all-headers: both
- dns:
enabled: yes
version: 2
- tls:
extended: yes
session-resumption: yes
- files:
force-magic: yes
force-hash: [md5, sha256]
- drop:
enabled: yes
- smtp:
extended: yes
custom: [received, x-mailer, x-originating-ip, relays,
reply-to, bcc]
- flow:
enabled: yes
- netflow:
enabled: yes
- stats:
totals: yes
threads: yes
deltas: yes
- pcap-log:
enabled: yes
filename: packets.pcap
limit: 100mb
max-files: 10
compression: none
mode: normal
use-stream-depth: no
honor-pass-rules: no
- alert-debug:
enabled: no
filename: alert-debug.log
af-packet:
- interface: eth0
threads: auto
cluster-id: 99
cluster-type: cluster_flow
defrag: yes
use-mmap: yes
tpacket-v3: yes
ring-size: 2048
block-size: 32768
pcap:
- interface: eth0
pcap-file:
checksum-checks: auto
app-layer:
protocols:
http:
enabled: yes
libhtp:
default-config:
personality: IDS
request-body-limit: 100kb
response-body-limit: 100kb
request-body-minimal-inspect-size: 32kb
request-body-inspect-window: 4kb
response-body-minimal-inspect-size: 40kb
response-body-inspect-window: 16kb
response-body-decompress-layer-limit: 2
http-body-inline: auto
double-decode-path: yes
double-decode-query: yes
tls:
enabled: yes
detection-ports:
dp: 443
ja3-fingerprints: yes
ssh:
enabled: yes
smtp:
enabled: yes
mime:
decode-mime: yes
decode-base64: yes
decode-quoted-printable: yes
dns:
tcp:
enabled: yes
udp:
enabled: yes
run-mode: workers
default-rule-path: /var/lib/suricata/rules
rule-files:
- suricata.rules
- local.rules
classification-file: /etc/suricata/classification.config
reference-config-file: /etc/suricata/reference.config
stream:
memcap: 64mb
checksum-validation: yes
inline: no
reassembly:
memcap: 256mb
depth: 1mb
toserver-chunk-size: 2560
toclient-chunk-size: 2560
randomize-chunk-size: yes
host:
hash-size: 4096
prealloc: 1000
memcap: 32mb
file-store:
version: 2
enabled: yes
dir: /var/log/suricata/files
write-fileinfo: yes
force-filestore: no
EOF
4.5.2 ルールの更新
カスタムルールの作成
# ファイルを初期化 sudo rm -f /var/lib/suricata/rules/local.rules sudo touch /var/lib/suricata/rules/local.rules # SQL Injection echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL SQL Injection SELECT"; flow:to_server,established; content:"select"; nocase; http_uri; classtype:web-application-attack; sid:1000001; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL SQL Injection UNION"; flow:to_server,established; content:"union"; nocase; http_uri; classtype:web-application-attack; sid:1000002; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL SQL Injection quote"; flow:to_server,established; content:"|27|"; http_uri; classtype:web-application-attack; sid:1000005; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null # XSS echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL XSS script tag"; flow:to_server,established; content:"<script"; nocase; http_uri; classtype:web-application-attack; sid:1000010; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL XSS javascript"; flow:to_server,established; content:"javascript"; nocase; http_uri; classtype:web-application-attack; sid:1000011; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL XSS encoded"; flow:to_server,established; content:"%3Cscript"; nocase; http_uri; classtype:web-application-attack; sid:1000013; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null # Directory Traversal echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL Dir Traversal"; flow:to_server,established; content:"../"; http_uri; classtype:web-application-attack; sid:1000020; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL etc passwd access"; flow:to_server,established; content:"/etc/passwd"; http_uri; classtype:web-application-attack; sid:1000022; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null # Command Injection echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL Cmd Injection pipe"; flow:to_server,established; content:"|7c|"; http_uri; classtype:web-application-attack; sid:1000030; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null echo 'alert http any any -> $HTTP_SERVERS $HTTP_PORTS (msg:"LOCAL Cmd Injection semicolon"; flow:to_server,established; content:"|3b|"; http_uri; classtype:web-application-attack; sid:1000031; rev:2;)' | sudo tee -a /var/lib/suricata/rules/local.rules > /dev/null
# suricata-update をインストール apt install suricata-update -y # ルールソースの追加と更新 suricata-update update-sources # disable.conf を作成(産業用プロトコルはエラーになるので全て除外) sudo cat << 'EOF' > /etc/suricata/disable.conf # Industrial protocols - not needed for web server group:modbus group:dnp3 group:enip group:cip re:modbus re:dnp3 re:enip re:cip 2250009 2250010 2250011 2270000 2270001 2270002 EOF # ルールを再更新 (失敗したら、手動でリトライを試してみてください) suricata-update --disable-conf=/etc/suricata/disable.conf # 設定テスト suricata -T -c /etc/suricata/suricata.yaml # サービス有効化・起動 systemctl enable suricata systemctl restart suricata systemctl status suricata
4.6 動作確認とテスト
4.6.1 サービス状態の確認
lb-1, lb-2 で実行:
# 各サービスの状態 systemctl status haproxy systemctl status keepalived systemctl status suricata # VIPの確認(MASTERノードのみ) ip addr show | grep 192.168.1.100
4.6.2 VIPの確認
# lb-1で確認(MASTERの場合、VIPが表示される) ip addr show eth0 # 出力例: # inet 192.168.1.11/24 brd 192.168.1.255 scope global eth0 # inet 192.168.1.100/24 scope global secondary eth0 ← VIP
5. テスト・検証
構築が完了したら、実際にセキュリティ機能が動作しているか検証します。
5.1.1 HTTPアクセステスト
# VIP経由でアクセス(任意のホストから)
curl http://192.168.1.100
# ラウンドロビン確認(複数回実行)
for i in {1..10}; do
curl -s http://192.168.1.100 | grep "Web Server"
done
5.1.2 HAProxy統計ページの確認
ブラウザで以下にアクセス:
http://192.168.1.100:8404/stats ユーザー名: admin パスワード: HaProxyAdmin123
5.1.3 フェイルオーバーテスト
# lb-1でHAProxyを停止 ssh ubuntu@192.168.1.11 sudo systemctl stop haproxy # lb-2でVIPが引き継がれたか確認 ssh ubuntu@192.168.1.12 ip addr show | grep 192.168.1.100 # アクセス継続確認 curl http://192.168.1.100 # lb-1のHAProxyを再起動 ssh ubuntu@192.168.1.11 sudo systemctl start haproxy
5.1.6 Suricataログの確認
# アラートログ tail -f /var/log/suricata/fast.log # JSON詳細ログ tail -f /var/log/suricata/eve.json | jq . # 攻撃シミュレーション(テスト用) curl "http://192.168.1.100/?id=1' OR '1'='1"
6. 実運用のイメージ
6.1 EVE JSONログの活用
Suricataは /var/log/suricata/eve.json に非常に詳細なイベントログを出力します。これにはAlertだけでなく、HTTPアクセスのメタデータ(URL、User-Agent、DNSクエリ等)やTLSハンドシェイクの詳細も含まれます。
実際の運用では、このログをローカルで見続けるのは不可能なので、以下のスタックによる可視化を試してみるのもありですね。
- Log Shipper: Filebeat または Promtail をGateway VMに導入。
- Storage/Search: Elasticsearch または Loki。
- Visualization: Kibana または Grafana。
これにより、「どの国からの攻撃が多いか」「どのルールの検知頻度が高いか」をリアルタイムダッシュボードで監視できるようになります。
6.2 ルールの自動更新
脅威情報は日々更新されます。suricata-update をcronジョブに登録し、毎日自動更新する設定を行いましょう。
ただし、自動更新で新しいルールが追加され、それが誤検知を引き起こすリスクもあるため、重要な環境ではステージング環境でのテストを経てから本番適用するパイプラインが理想的です。
7. まとめ
Proxmox VE上に以下の高可用性構成を構築しました:
[クライアント]
│
▼
[VIP: 192.168.1.100]
│
▼
┌─────┴─────┐
│ Keepalived │ (VRRP フェイルオーバー)
└─────┬─────┘
│
▼
┌─────┴─────┐
│ HAProxy │ (ロードバランシング)
└─────┬─────┘
│
▼
┌─────┴─────┐
│ Suricata │ (IDS/侵入検知)
└─────┬─────┘
│
▼
┌─────┴─────┐
│ Nginx │ (Webサーバー)
│ (web-1/2) │
└───────────┘
今回の記事では、構築したアーキテクチャは商用製品に勝るとも劣らない?(は言い過ぎですね)環境を構築できました!
この構成を自前で作成してみて、いかに普段のマネージドなサービスがありがたいかを再確認できました。
しかし、ブラックボックス化したマネージドサービスに依存せず、自らの手でパケットの挙動を完全に制御・把握できる環境がこれで手に入りましたので、思う存分遊んでみましょう。
We Are Hiring!
ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!) careers.abejainc.com
特に下記ポジションの募集を強化しています!ぜひ御覧ください!
トランスフォーメーション領域:データサイエンティスト(ミドル)
トランスフォーメーション領域:データサイエンティスト(シニア)
*1: サービスを AWS から Hetzner へ移行した事例: https://digitalsociety.coop/posts/migrating-to-hetzner-cloud/
*2:サービスを AWS からベアメタルサーバーに移行した事例: https://oneuptime.com/blog/post/2025-10-29-aws-to-bare-metal-two-years-later/view?utm_source=tldrwebdev
*3:https://www.cfd.co.jp/biz/product/detail/d4n2666cs-16g.html
*4:https://shop.sandisk.com/ja-jp/products/ssd/internal-ssd/sandisk-ultra-3d-sata-iii-ssd?sku=SDSSDH3-1T00-J26
