ストーリー
高橋アーキテクトがコードエディタを開きました。
パターン1: ゲートキーパー
全てのリクエストを単一のゲートで検証するパターンです。API Gatewayやミドルウェアがこの役割を担います。
// ゲートキーパーパターン:認証・認可ミドルウェア
type Middleware = (req: Request, res: Response, next: NextFunction) => void;
const securityGateway: Middleware[] = [
// 1. レートリミット
rateLimiter({ windowMs: 60000, max: 100 }),
// 2. リクエストサイズ制限
express.json({ limit: "10kb" }),
// 3. セキュリティヘッダー
helmet(),
// 4. CORS
cors({ origin: allowedOrigins }),
// 5. 認証
authenticate,
// 6. リクエストログ
requestLogger,
];
app.use("/api", ...securityGateway);
パターン2: 入力バリデーションパイプライン
全ての入力を多段階で検証するパターンです。
import { z } from "zod";
// バリデーションスキーマの定義
const createUserSchema = z.object({
name: z.string()
.min(1, "名前は必須です")
.max(100, "名前は100文字以内です")
.regex(/^[a-zA-Z\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\s]+$/,
"使用できない文字が含まれています"),
email: z.string()
.email("有効なメールアドレスを入力してください")
.max(254),
age: z.number()
.int("年齢は整数で入力してください")
.min(0).max(150),
});
// バリデーションミドルウェア
const validate = <T>(schema: z.ZodSchema<T>) => {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: "Validation failed",
details: result.error.issues.map(i => ({
field: i.path.join("."),
message: i.message,
})),
});
}
req.validatedBody = result.data;
next();
};
};
app.post("/api/users", validate(createUserSchema), createUserHandler);
パターン3: 安全なエラーハンドリング
内部情報を漏洩させずにエラーを返すパターンです。
// カスタムエラークラス
class AppError extends Error {
constructor(
public statusCode: number,
public userMessage: string,
public internalMessage: string, // ログにのみ記録
) {
super(userMessage);
}
}
// エラーハンドリングミドルウェア
const errorHandler = (
err: Error,
req: Request,
res: Response,
next: NextFunction,
) => {
// 内部情報をログに記録
logger.error({
error: err.message,
stack: err.stack,
path: req.path,
method: req.method,
userId: req.user?.id,
});
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: err.userMessage, // ユーザーには安全なメッセージのみ
});
}
// 予期しないエラーは汎用メッセージ
res.status(500).json({
error: "Internal server error",
// スタックトレースやDB情報は絶対に返さない
});
};
パターン4: トークンバケット(レートリミット)
リソースの過剰使用を防ぐパターンです。
interface RateLimitConfig {
maxTokens: number; // バケットの容量
refillRate: number; // 1秒あたりの補充数
refillInterval: number; // 補充間隔(ms)
}
class TokenBucket {
private tokens: number;
private lastRefill: number;
constructor(private config: RateLimitConfig) {
this.tokens = config.maxTokens;
this.lastRefill = Date.now();
}
tryConsume(): boolean {
this.refill();
if (this.tokens > 0) {
this.tokens--;
return true; // リクエスト許可
}
return false; // レート制限超過
}
private refill(): void {
const now = Date.now();
const elapsed = now - this.lastRefill;
const tokensToAdd = Math.floor(
elapsed / this.config.refillInterval * this.config.refillRate
);
this.tokens = Math.min(this.config.maxTokens, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
パターン5: Immutable Audit Log(不変監査ログ)
改ざん不可能な形で操作履歴を記録するパターンです。
interface AuditEntry {
id: string; // UUID
timestamp: Date;
actor: {
userId: string;
role: string;
ipAddress: string;
};
action: string; // "user.create", "order.delete" など
resource: {
type: string;
id: string;
};
details: Record<string, unknown>;
previousHash: string; // 前のエントリのハッシュ(チェーン)
hash: string; // このエントリのハッシュ
}
const createAuditEntry = async (
entry: Omit<AuditEntry, "id" | "hash" | "previousHash">,
): Promise<AuditEntry> => {
const previousEntry = await auditStore.getLatest();
const previousHash = previousEntry?.hash ?? "GENESIS";
const auditEntry: AuditEntry = {
...entry,
id: crypto.randomUUID(),
previousHash,
hash: "", // 計算後に設定
};
// ハッシュチェーンで改ざんを検知可能に
auditEntry.hash = crypto
.createHash("sha256")
.update(JSON.stringify({ ...auditEntry, hash: undefined }))
.digest("hex");
// 追記のみ可能なストレージに保存(DELETE不可)
await auditStore.append(auditEntry);
return auditEntry;
};
パターンの選択ガイド
| 脅威 | 推奨パターン |
|---|---|
| 不正アクセス | ゲートキーパー |
| インジェクション | 入力バリデーションパイプライン |
| 情報漏洩 | 安全なエラーハンドリング |
| DoS攻撃 | トークンバケット |
| 否認 | Immutable Audit Log |
まとめ
| ポイント | 内容 |
|---|---|
| ゲートキーパー | 単一ゲートで全リクエストを検証 |
| バリデーション | Zodなどで型安全に入力を検証 |
| エラーハンドリング | 内部情報を漏洩させない |
| 監査ログ | ハッシュチェーンで改ざん検知 |
チェックリスト
- ゲートキーパーパターンの実装方法を理解した
- 入力バリデーションパイプラインの設計を把握した
- 安全なエラーハンドリングの重要性を理解した
- 不変監査ログの仕組みを理解した
次のステップへ
次は演習です。ここまで学んだ脅威モデリングの知識を使って、実際にシステムの脅威分析を行いましょう。
推定読了時間: 30分