LESSON 25分

ストーリー

高橋アーキテクト
攻撃者の考え方は分かったかな。次は、守る側の”原則”を学ぼう

高橋アーキテクトがホワイトボードに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分