ストーリー
多層防御(Defense in Depth)
多層防御は、複数のセキュリティ層を重ねることで、1つの層が破られても他の層で防御する戦略です。
多層防御の7つの層
| 層 | 対象 | 対策例 |
|---|---|---|
| 物理層 | データセンター、サーバー | 入退室管理、監視カメラ |
| ネットワーク層 | 通信経路 | ファイアウォール、IDS/IPS、VPN |
| 境界層 | DMZ、エッジ | WAF、DDoS防御、CDN |
| アプリケーション層 | Webアプリ、API | 入力検証、認証・認可、CSRF対策 |
| データ層 | データベース、ファイル | 暗号化、アクセス制御、バックアップ |
| ホスト層 | OS、コンテナ | パッチ管理、ハードニング、EDR |
| ポリシー層 | 組織、人 | セキュリティ教育、インシデント対応計画 |
// 多層防御をExpressミドルウェアで実装する例
import express from 'express';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import cors from 'cors';
const app = express();
// 層1: HTTPヘッダーセキュリティ(境界層)
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:'],
},
}));
// 層2: CORS制御(境界層)
app.use(cors({
origin: ['https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
}));
// 層3: レート制限(ネットワーク層)
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100, // 最大100リクエスト
standardHeaders: true,
legacyHeaders: false,
}));
// 層4: 入力検証(アプリケーション層)
app.use(express.json({ limit: '10kb' })); // ペイロードサイズ制限
// 層5: 認証(アプリケーション層)
app.use('/api', authenticateMiddleware);
// 層6: 認可(アプリケーション層)
app.use('/api/admin', authorizeMiddleware('admin'));
// 層7: 監査ログ(ポリシー層)
app.use(auditLogMiddleware);
最小権限の原則(Principle of Least Privilege)
ユーザーやプロセスには、必要最小限の権限だけを付与します。
| 適用対象 | Before(違反) | After(準拠) |
|---|---|---|
| IAMロール | AdministratorAccessを全員に付与 | 役割ごとにカスタムポリシーを作成 |
| DBアクセス | rootユーザーでアプリケーション接続 | 読み取り専用・書き込み専用ユーザーを分離 |
| APIスコープ | *(全権限)のAPIキーを発行 | 必要なエンドポイントだけに限定 |
| ファイル権限 | chmod 777 | chmod 644(オーナーだけ書き込み可能) |
| コンテナ | rootユーザーで実行 | 非rootユーザーで実行、読み取り専用FS |
// 最小権限の原則を適用したIAMポリシー例
const developerPolicy = {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: [
's3:GetObject',
's3:PutObject',
],
Resource: 'arn:aws:s3:::app-uploads/*',
Condition: {
StringEquals: {
's3:x-amz-server-side-encryption': 'aws:kms',
},
},
},
{
Effect: 'Allow',
Action: [
'dynamodb:GetItem',
'dynamodb:PutItem',
'dynamodb:Query',
],
Resource: 'arn:aws:dynamodb:ap-northeast-1:*:table/app-*',
},
// 明示的に拒否: 本番環境のリソースへのアクセス
{
Effect: 'Deny',
Action: '*',
Resource: '*',
Condition: {
StringEquals: {
'aws:ResourceTag/Environment': 'production',
},
},
},
],
};
フェイルセーフデフォルト(Fail-Safe Defaults)
システムがエラーや障害に遭遇した場合、安全な状態にフォールバックする原則です。
| 原則 | 説明 | 例 |
|---|---|---|
| デフォルト拒否 | 明示的に許可されない限りアクセスを拒否 | ファイアウォールのデフォルト拒否ルール |
| 安全な障害処理 | エラー時に情報を漏洩しない | スタックトレースを非表示にする |
| タイムアウトの設定 | 無期限のセッションを避ける | セッションの有効期限を設定 |
| 安全な初期状態 | 新規リソースはデフォルトで制限的 | S3バケットのデフォルト非公開 |
// フェイルセーフデフォルトの実装例
// 悪い例: デフォルト許可
function checkAccess(user: User, resource: Resource): boolean {
if (user.role === 'blocked') {
return false; // ブロックされたユーザーだけ拒否
}
return true; // それ以外は全員許可(危険)
}
// 良い例: デフォルト拒否
function checkAccess(user: User, resource: Resource): boolean {
const allowedRoles = resource.allowedRoles;
if (!allowedRoles || allowedRoles.length === 0) {
return false; // 許可ロールが未定義ならデフォルト拒否
}
return user.roles.some(role => allowedRoles.includes(role));
}
// エラーハンドリング: 安全な障害処理
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
// 内部エラーの詳細を外部に漏らさない
securityLogger.error('Unhandled error', {
error: err.message,
stack: err.stack,
path: req.path,
userId: req.user?.id,
});
// ユーザーには汎用的なメッセージだけ返す
res.status(500).json({
error: 'Internal Server Error',
requestId: req.id, // 問い合わせ用のIDだけ提供
});
});
職務の分離(Separation of Duties)
1つの重要な操作を、複数の人や役割に分けることで不正を防止する原則です。
| 分離対象 | Before | After |
|---|---|---|
| デプロイ権限 | 開発者が直接本番にデプロイ | 開発者がPRを作成 → 別の人がレビュー → CI/CDが自動デプロイ |
| DB操作 | 開発者がSQL直接実行 | 変更はマイグレーションスクリプトとして管理 → DBAがレビュー |
| 秘密情報管理 | 1人が全てのシークレットを管理 | 暗号化キーとデータを別々の管理者が保持 |
| コードレビュー | 自分のコードを自分でマージ | 別の開発者のApproveが必須 |
セキュリティ・バイ・デザイン(Security by Design)
セキュリティを後付けではなく、設計段階から組み込む考え方です。
設計段階で考慮すべきセキュリティ要件
// セキュリティ要件を型で表現する例
interface SecureEndpointConfig {
// 認証要件
authentication: {
required: boolean;
methods: ('jwt' | 'apiKey' | 'oauth2')[];
mfaRequired: boolean;
};
// 認可要件
authorization: {
model: 'RBAC' | 'ABAC';
requiredRoles?: string[];
requiredPermissions?: string[];
};
// 入力検証
inputValidation: {
schema: object; // JSON Schema or Zod schema
maxPayloadSize: string; // e.g., '10kb'
sanitize: boolean;
};
// レート制限
rateLimit: {
windowMs: number;
maxRequests: number;
keyBy: 'ip' | 'userId' | 'apiKey';
};
// 監査
audit: {
logRequest: boolean;
logResponse: boolean;
sensitiveFields: string[]; // ログに含めないフィールド
};
// データ保護
dataProtection: {
encryptResponse: boolean;
cacheControl: string;
cors: {
allowedOrigins: string[];
};
};
}
まとめ
| ポイント | 内容 |
|---|---|
| 多層防御 | 複数のセキュリティ層を重ねて単一障害点を排除 |
| 最小権限の原則 | 必要最小限の権限だけを付与し、過剰な権限を避ける |
| フェイルセーフデフォルト | エラー時は安全な状態にフォールバック、デフォルト拒否 |
| 職務の分離 | 重要な操作を複数の役割に分けて不正を防止 |
| セキュリティ・バイ・デザイン | 設計段階からセキュリティを組み込む |
チェックリスト
- 多層防御の7つの層を説明できる
- 最小権限の原則をIAMポリシーやアプリケーション設計に適用できる
- フェイルセーフデフォルトの考え方を実装に反映できる
- 職務の分離を開発プロセスに適用できる
- セキュリティ・バイ・デザインの考え方を理解した
次のステップへ
次は「演習:脅威分析を実施しよう」です。ここまで学んだSTRIDE、リスクアセスメント、セキュリティアーキテクチャの原則を使って、実際のWebアプリケーションに対する脅威分析を実施しましょう。
推定読了時間: 25分