ストーリー
高橋アーキテクトがホワイトボードに7つの原則を書き出しました。
1. 最小権限の原則 (Principle of Least Privilege)
ユーザーやプログラムには、その作業に必要な最小限の権限のみを与えます。
// 悪い例:全権限を持つサービスアカウント
const dbConnection = {
user: "root",
password: "admin123",
database: "production",
// rootユーザーで全テーブルにアクセス可能
};
// 良い例:サービスごとに最小限の権限
interface ServicePermissions {
userService: {
tables: ["users", "profiles"];
operations: ["SELECT", "INSERT", "UPDATE"]; // DELETEは不可
};
reportService: {
tables: ["orders", "products"];
operations: ["SELECT"]; // 読み取りのみ
};
}
// アプリケーションレベルでも最小権限を適用
type UserRole = "viewer" | "editor" | "admin";
const permissions: Record<UserRole, string[]> = {
viewer: ["read:articles", "read:comments"],
editor: ["read:articles", "write:articles", "read:comments", "write:comments"],
admin: ["read:articles", "write:articles", "delete:articles",
"read:comments", "write:comments", "delete:comments",
"manage:users"],
};
2. 多層防御 (Defense in Depth)
単一の防御に頼らず、複数の層でセキュリティを確保します。1つの層が突破されても、次の層が守ります。
// 多層防御の実装例
// Layer 1: WAF(Web Application Firewall)
// → 既知の攻撃パターンをブロック
// Layer 2: レートリミット
import rateLimit from "express-rate-limit";
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100, // 最大100リクエスト
message: "Too many requests",
});
app.use("/api/", limiter);
// Layer 3: 認証
const authenticate = (req: Request, res: Response, next: NextFunction) => {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "Unauthorized" });
try {
req.user = verifyJWT(token);
next();
} catch {
return res.status(401).json({ error: "Invalid token" });
}
};
// Layer 4: 認可
const authorize = (permission: string) => {
return (req: Request, res: Response, next: NextFunction) => {
if (!req.user.permissions.includes(permission)) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
};
// Layer 5: 入力バリデーション
import { z } from "zod";
const updateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
});
// Layer 6: ビジネスロジック内のチェック
// Layer 7: データベースレベルの制約
3. フェイルセキュア (Fail Secure)
エラーが発生した場合、安全な状態にフォールバックします。
// 悪い例:エラー時にアクセスを許可してしまう
const checkAccess = async (userId: string, resource: string): Promise<boolean> => {
try {
const result = await authService.check(userId, resource);
return result.allowed;
} catch (error) {
console.error("Auth service error:", error);
return true; // エラー時にアクセスを許可 → 危険
}
};
// 良い例:エラー時にはアクセスを拒否(フェイルセキュア)
const checkAccess = async (userId: string, resource: string): Promise<boolean> => {
try {
const result = await authService.check(userId, resource);
return result.allowed;
} catch (error) {
console.error("Auth service error:", error);
return false; // エラー時はアクセス拒否 → 安全
}
};
4. 攻撃面の最小化 (Minimize Attack Surface)
外部に露出する機能やインターフェースを最小限に抑えます。
// 悪い例:不要なエンドポイントが露出
app.get("/api/debug/config", (req, res) => res.json(config)); // デバッグ情報
app.get("/api/admin/users", (req, res) => { /* ... */ }); // 認証なし
app.get("/api/health", (req, res) => {
res.json({
status: "ok",
database: "connected",
version: "1.2.3",
nodeVersion: process.version, // 内部情報の漏洩
uptime: process.uptime(),
});
});
// 良い例:最小限のエンドポイントのみ公開
// デバッグエンドポイントは本番環境で無効化
if (process.env.NODE_ENV !== "production") {
app.get("/api/debug/config", authenticate, authorize("admin"), debugHandler);
}
// ヘルスチェックは最小限の情報のみ
app.get("/api/health", (req, res) => {
res.json({ status: "ok" }); // 内部情報を含めない
});
5. セキュリティのデフォルト化 (Secure by Default)
何も設定しなくても安全な状態であるように設計します。
// フレームワークレベルでセキュアなデフォルト
interface SecurityConfig {
cors: {
origin: string[]; // デフォルト: [] (全拒否)
credentials: boolean; // デフォルト: false
};
rateLimit: {
enabled: boolean; // デフォルト: true
maxRequests: number; // デフォルト: 100
};
headers: {
hsts: boolean; // デフォルト: true
xFrameOptions: string; // デフォルト: "DENY"
contentSecurityPolicy: boolean; // デフォルト: true
};
authentication: {
required: boolean; // デフォルト: true(明示的にpublicにしない限り認証必須)
};
}
// 「公開」は明示的に宣言する
const publicRoutes = ["/api/health", "/api/auth/login", "/api/auth/register"];
app.use((req, res, next) => {
if (publicRoutes.includes(req.path)) return next();
return authenticate(req, res, next); // それ以外は認証必須
});
6. 関心の分離 (Separation of Duties)
重要な操作は、単一の人物やプロセスでは完結しないようにします。
// 重要な操作に承認フローを導入
interface ApprovalWorkflow {
requesterId: string;
approverId: string; // リクエスターとは別の人物
action: "delete_user" | "change_role" | "export_data";
status: "pending" | "approved" | "rejected";
}
const executeHighRiskAction = async (workflow: ApprovalWorkflow) => {
// 自分自身の承認は不可
if (workflow.requesterId === workflow.approverId) {
throw new Error("Self-approval is not allowed");
}
// 承認済みでなければ実行不可
if (workflow.status !== "approved") {
throw new Error("Action requires approval");
}
// 実行
await performAction(workflow.action);
};
7. 透明性と監査可能性 (Transparency & Auditability)
全ての重要な操作を記録し、後から追跡できるようにします。
interface AuditLog {
timestamp: Date;
userId: string;
action: string;
resource: string;
details: Record<string, unknown>;
ipAddress: string;
result: "success" | "failure";
}
const auditLogger = {
log: async (entry: AuditLog): Promise<void> => {
// 改ざん不可能な監査ログストレージに記録
await auditStore.append(entry);
},
};
原則の適用ガイド
| 原則 | 適用場面 | 質問 |
|---|---|---|
| 最小権限 | 権限設計 | 本当にその権限が必要か? |
| 多層防御 | アーキテクチャ設計 | 1つの層が破られたらどうなるか? |
| フェイルセキュア | エラーハンドリング | エラー時に安全側に倒れるか? |
| 攻撃面最小化 | インターフェース設計 | 不要な露出はないか? |
| デフォルトセキュア | 設定設計 | 未設定時に安全か? |
| 関心の分離 | 運用設計 | 1人で危険な操作を完結できないか? |
| 監査可能性 | ログ設計 | 何が起きたか後から追跡できるか? |
まとめ
| ポイント | 内容 |
|---|---|
| 最小権限 | 必要最小限の権限のみ付与 |
| 多層防御 | 複数の防御層で保護 |
| フェイルセキュア | エラー時は安全な状態に |
| デフォルトセキュア | 何もしなくても安全 |
チェックリスト
- 最小権限の原則を理解し適用方法を把握した
- 多層防御の考え方と実装を理解した
- フェイルセキュアの重要性を理解した
- セキュアなデフォルト設計の方法を把握した
次のステップへ
次は「コンプライアンスと規制」を学びます。技術的な対策だけでなく、法規制への準拠も重要なセキュリティ設計の要素です。
推定読了時間: 25分