LESSON 30分

ストーリー

佐藤CTO
セキュリティは後付けではなく、アーキテクチャの一部として最初から組み込むものだ
あなた
“Security by Design”ということですか?
佐藤CTO
その通り。そして、セキュリティはもはや技術だけの話じゃない。GDPRや個人情報保護法といった法規制への対応(コンプライアンス)も、設計段階で考慮する必要がある
あなた
技術とビジネスの両面から考えるんですね
佐藤CTO
まさにそこがアーキテクトの仕事だ。今日はセキュリティアーキテクチャの全体像と、コンプライアンス対応の設計を学ぼう

セキュリティアーキテクチャの原則

多層防御(Defense in Depth)

単一の防御策に頼らず、複数の層で守ります。

graph TD
    A["ネットワーク層<br/>WAF, DDoS防御, ファイアウォール"]
    B["アプリケーション層<br/>認証, 認可, 入力検証, CSRF防御"]
    C["データ層<br/>暗号化, アクセス制御, マスキング"]
    D["インフラ層<br/>IAM, 監査ログ, セキュリティグループ"]

    A --> B --> C --> D

    classDef netStyle fill:#d9534f,stroke:#b52b27,color:#fff
    classDef appStyle fill:#e8a838,stroke:#b07c1e,color:#fff
    classDef dataStyle fill:#4a90d9,stroke:#2c5f8a,color:#fff
    classDef infraStyle fill:#5cb85c,stroke:#3d8b3d,color:#fff

    class A netStyle
    class B appStyle
    class C dataStyle
    class D infraStyle

セキュリティ設計の7原則

原則説明実装例
最小権限必要最小限のアクセス権のみ付与IAMロール、DBアクセス権限
多層防御複数の防御層を設けるWAF + 認証 + 暗号化
フェイルセーフ障害時は安全側に倒す認証失敗時はアクセス拒否
攻撃面の最小化露出するサービスを最小限に不要なポート閉鎖、API最小化
職務分離権限を分散して不正を防止承認者と実行者を分ける
完全な仲介すべてのアクセスを検証API Gatewayでの認証チェック
安全な既定値デフォルトを安全な状態にデフォルト拒否ポリシー

認証と認可

認証パターン

OAuth 2.0 / OpenID Connect(OIDC)

sequenceDiagram
    participant Client
    participant Auth as Auth Server
    participant Resource as Resource Server
    participant IdP

    Client->>Auth: 1. 認可リクエスト
    Auth->>IdP: 2. ログイン画面
    IdP-->>Auth: 3. 認証完了
    Auth-->>Client: 4. 認可コード
    Client->>Auth: 5. トークン交換
    Auth-->>Client: 6. Access Token + ID Token
    Client->>Resource: 7. APIリクエスト(Bearer Token)
    Resource-->>Client: 8. トークン検証 + レスポンス
// JWT検証ミドルウェアの実装例
import { verify, JwtPayload } from 'jsonwebtoken';

interface AuthenticatedRequest extends Request {
  user: {
    sub: string;
    email: string;
    roles: string[];
  };
}

class JwtAuthMiddleware {
  constructor(
    private readonly publicKey: string,
    private readonly issuer: string,
    private readonly audience: string,
  ) {}

  async authenticate(req: Request): Promise<AuthenticatedRequest> {
    const authHeader = req.headers.get('Authorization');
    if (!authHeader?.startsWith('Bearer ')) {
      throw new UnauthorizedError('Missing or invalid Authorization header');
    }

    const token = authHeader.slice(7);

    try {
      const payload = verify(token, this.publicKey, {
        algorithms: ['RS256'],
        issuer: this.issuer,
        audience: this.audience,
      }) as JwtPayload;

      return Object.assign(req, {
        user: {
          sub: payload.sub!,
          email: payload.email as string,
          roles: (payload.roles ?? []) as string[],
        },
      });
    } catch (error) {
      throw new UnauthorizedError('Invalid or expired token');
    }
  }
}

認可パターン

RBAC(Role-Based Access Control)

// ロールベースのアクセス制御
type Role = 'admin' | 'editor' | 'viewer';
type Permission = 'read' | 'write' | 'delete' | 'admin';

const rolePermissions: Record<Role, Permission[]> = {
  admin: ['read', 'write', 'delete', 'admin'],
  editor: ['read', 'write'],
  viewer: ['read'],
};

class RBACAuthorizer {
  authorize(userRoles: Role[], requiredPermission: Permission): boolean {
    return userRoles.some((role) =>
      rolePermissions[role]?.includes(requiredPermission)
    );
  }
}

// 使用例
class ArticleController {
  constructor(private authorizer: RBACAuthorizer) {}

  async deleteArticle(user: AuthenticatedUser, articleId: string): Promise<void> {
    if (!this.authorizer.authorize(user.roles, 'delete')) {
      throw new ForbiddenError('Insufficient permissions');
    }
    await this.articleService.delete(articleId);
  }
}

ABAC(Attribute-Based Access Control)

// 属性ベースのアクセス制御
interface AccessPolicy {
  effect: 'allow' | 'deny';
  conditions: PolicyCondition[];
}

interface PolicyCondition {
  attribute: string;
  operator: 'equals' | 'contains' | 'greaterThan' | 'in';
  value: unknown;
}

class ABACAuthorizer {
  constructor(private policies: AccessPolicy[]) {}

  authorize(context: AccessContext): boolean {
    for (const policy of this.policies) {
      const conditionsMet = policy.conditions.every((condition) =>
        this.evaluateCondition(condition, context)
      );

      if (conditionsMet) {
        return policy.effect === 'allow';
      }
    }
    return false; // デフォルト拒否
  }

  private evaluateCondition(
    condition: PolicyCondition,
    context: AccessContext,
  ): boolean {
    const actualValue = context[condition.attribute];
    switch (condition.operator) {
      case 'equals':
        return actualValue === condition.value;
      case 'contains':
        return Array.isArray(actualValue) && actualValue.includes(condition.value);
      case 'in':
        return Array.isArray(condition.value) && condition.value.includes(actualValue);
      default:
        return false;
    }
  }
}

// ポリシー例:自部署のデータのみ編集可能
const policies: AccessPolicy[] = [
  {
    effect: 'allow',
    conditions: [
      { attribute: 'action', operator: 'equals', value: 'edit' },
      { attribute: 'user.department', operator: 'equals', value: 'resource.department' },
    ],
  },
];

RBACとABACの比較

観点RBACABAC
粒度ロール単位属性の任意の組み合わせ
柔軟性低い(ロール爆発のリスク)高い(動的なポリシー)
実装複雑度シンプル複雑
適用場面組織の役割が明確な場合細かい条件分岐が必要な場合
「管理者は全操作可能」「勤務時間内かつ自部署データのみ編集可能」

APIセキュリティ

API セキュリティのベストプラクティス

// API セキュリティミドルウェアスタック
class APISecurityStack {
  private middlewares: Middleware[] = [];

  constructor() {
    // 1. レートリミット
    this.middlewares.push(new RateLimiter({
      windowMs: 60_000,       // 1分
      maxRequests: 100,       // 最大100リクエスト
      keyExtractor: (req) => req.ip,
    }));

    // 2. CORS設定
    this.middlewares.push(new CORSMiddleware({
      allowedOrigins: ['https://app.example.com'],
      allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
      allowedHeaders: ['Authorization', 'Content-Type'],
      maxAge: 86400,
    }));

    // 3. 入力バリデーション
    this.middlewares.push(new InputValidator({
      maxBodySize: '1mb',
      sanitizeHtml: true,
      rejectSqlInjection: true,
    }));

    // 4. 認証
    this.middlewares.push(new JwtAuthMiddleware(/* ... */));

    // 5. 認可
    this.middlewares.push(new RBACMiddleware(/* ... */));

    // 6. 監査ログ
    this.middlewares.push(new AuditLogger(/* ... */));
  }
}

API脅威と対策

脅威説明対策
インジェクションSQL/NoSQL/コマンドインジェクションパラメータバインディング、ORM使用
認証の不備弱いパスワード、トークン管理の不備MFA、トークンローテーション
過度なデータ公開不要なフィールドの返却レスポンスフィルタリング
リソース制限の欠如大量リクエスト、巨大ペイロードレートリミット、サイズ制限
認可の不備水平・垂直権限昇格オブジェクトレベルの認可チェック
Mass Assignment意図しないフィールドの更新DTO/ホワイトリスト方式

データ暗号化

保存時の暗号化(At Rest)

// フィールドレベル暗号化の例
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

class FieldEncryptor {
  private readonly algorithm = 'aes-256-gcm';

  constructor(private readonly masterKey: Buffer) {}

  encrypt(plaintext: string): EncryptedField {
    const iv = randomBytes(12);
    const cipher = createCipheriv(this.algorithm, this.masterKey, iv);

    let encrypted = cipher.update(plaintext, 'utf8', 'base64');
    encrypted += cipher.final('base64');

    const authTag = cipher.getAuthTag();

    return {
      ciphertext: encrypted,
      iv: iv.toString('base64'),
      authTag: authTag.toString('base64'),
    };
  }

  decrypt(field: EncryptedField): string {
    const decipher = createDecipheriv(
      this.algorithm,
      this.masterKey,
      Buffer.from(field.iv, 'base64'),
    );
    decipher.setAuthTag(Buffer.from(field.authTag, 'base64'));

    let decrypted = decipher.update(field.ciphertext, 'base64', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
  }
}

// 個人情報のフィールドレベル暗号化
interface UserRecord {
  id: string;
  email: EncryptedField;       // 暗号化
  phoneNumber: EncryptedField; // 暗号化
  name: string;                // 平文(検索に使用)
  createdAt: Date;
}

通信時の暗号化(In Transit)

対象方式設定
クライアント-サーバー間TLS 1.3HTTPS必須、HSTS設定
サービス間通信mTLS双方向証明書検証
データベース接続TLSSSL/TLS接続を必須化
メッセージキューTLSブローカーへの暗号化接続

コンプライアンス要件

主要な法規制・基準

規制/基準対象主な要件
GDPREU市民のデータを扱うサービスデータ主体の権利、DPO設置、72時間以内の侵害通知
個人情報保護法日本国内の個人データ利用目的の特定、安全管理措置、第三者提供制限
PCI-DSSクレジットカード情報を扱うシステムカードデータの暗号化、アクセス制限、定期監査
SOC 2SaaSサービス提供者セキュリティ、可用性、処理の完全性、機密性、プライバシー
HIPAA医療情報を扱うシステム(米国)PHI(保護対象医療情報)の保護

GDPR対応のアーキテクチャ考慮事項

// GDPRの「忘れられる権利」への対応
interface DataSubjectRightsHandler {
  // データアクセス権:ユーザーが自分のデータを閲覧
  handleAccessRequest(userId: string): Promise<PersonalDataExport>;

  // データ削除権(忘れられる権利)
  handleErasureRequest(userId: string): Promise<ErasureResult>;

  // データポータビリティ権:他サービスへのデータ移行
  handlePortabilityRequest(userId: string): Promise<PortableDataPackage>;

  // 処理制限権
  handleRestrictionRequest(userId: string): Promise<void>;
}

class GDPRCompliantUserService implements DataSubjectRightsHandler {
  async handleErasureRequest(userId: string): Promise<ErasureResult> {
    const result: ErasureResult = { deletedSystems: [], errors: [] };

    // 1. メインDBからの削除
    await this.userRepository.delete(userId);
    result.deletedSystems.push('main-database');

    // 2. 検索インデックスからの削除
    await this.searchIndex.deleteUser(userId);
    result.deletedSystems.push('search-index');

    // 3. キャッシュの無効化
    await this.cache.invalidateUser(userId);
    result.deletedSystems.push('cache');

    // 4. バックアップからの削除(暗号化キーの破棄で対応)
    await this.encryptionKeyManager.revokeUserKey(userId);
    result.deletedSystems.push('backup-encryption-keys');

    // 5. 監査ログは法的保持義務があるため匿名化
    await this.auditLog.anonymize(userId);
    result.deletedSystems.push('audit-log-anonymized');

    // 6. 削除完了の記録
    await this.erasureLog.record({
      userId,
      requestedAt: new Date(),
      completedAt: new Date(),
      systems: result.deletedSystems,
    });

    return result;
  }
}

脅威モデリング(STRIDE)

STRIDEフレームワーク

カテゴリ脅威対策
Spoofing(なりすまし)他人になりすまして操作強力な認証(MFA)
Tampering(改ざん)データやコードの改ざんデジタル署名、整合性チェック
Repudiation(否認)行為の否認監査ログ、タイムスタンプ
Information Disclosure(情報漏洩)機密情報の漏洩暗号化、アクセス制御
Denial of Service(サービス拒否)サービスの停止レートリミット、スケーリング
Elevation of Privilege(権限昇格)権限を超えた操作最小権限、認可チェック

脅威モデリングの実施例

// 脅威モデリングテンプレート
interface ThreatModel {
  system: string;
  dataFlows: DataFlow[];
  threats: Threat[];
  mitigations: Mitigation[];
}

interface Threat {
  id: string;
  category: 'S' | 'T' | 'R' | 'I' | 'D' | 'E';
  description: string;
  affectedComponent: string;
  riskLevel: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
  likelihood: 'HIGH' | 'MEDIUM' | 'LOW';
  impact: 'HIGH' | 'MEDIUM' | 'LOW';
}

// ECサイトの脅威モデリング例
const ecommerceThreatModel: ThreatModel = {
  system: 'ECサイト',
  dataFlows: [
    { from: 'ユーザー', to: 'API Gateway', data: '認証情報' },
    { from: 'API Gateway', to: '注文サービス', data: '注文データ' },
    { from: '注文サービス', to: '決済サービス', data: 'カード情報' },
  ],
  threats: [
    {
      id: 'T-001',
      category: 'S',
      description: 'セッションハイジャックによるなりすまし注文',
      affectedComponent: 'API Gateway',
      riskLevel: 'CRITICAL',
      likelihood: 'MEDIUM',
      impact: 'HIGH',
    },
    {
      id: 'T-002',
      category: 'I',
      description: 'APIレスポンスからのカード情報漏洩',
      affectedComponent: '決済サービス',
      riskLevel: 'CRITICAL',
      likelihood: 'LOW',
      impact: 'HIGH',
    },
    {
      id: 'T-003',
      category: 'D',
      description: 'セール時のDDoS攻撃によるサービス停止',
      affectedComponent: 'API Gateway',
      riskLevel: 'HIGH',
      likelihood: 'HIGH',
      impact: 'HIGH',
    },
  ],
  mitigations: [
    { threatId: 'T-001', measure: 'HttpOnly/Secure Cookie + CSRFトークン + セッション固定化対策' },
    { threatId: 'T-002', measure: 'カード情報のトークン化(PCI-DSS準拠の外部決済サービス利用)' },
    { threatId: 'T-003', measure: 'WAF + CDN + オートスケーリング + レートリミット' },
  ],
};

セキュリティアーキテクチャレビューチェックリスト

レビュー項目

## 認証・認可
- [ ] 認証方式は適切か(OAuth2/OIDC、MFA対応)
- [ ] パスワードはbcrypt/argon2でハッシュ化されているか
- [ ] JWTの署名アルゴリズムはRS256以上か
- [ ] トークンの有効期限は適切か(アクセス15分、リフレッシュ7日以内)
- [ ] 認可はリソースレベルで実装されているか

## データ保護
- [ ] 個人情報は暗号化されているか(保存時・通信時)
- [ ] 暗号鍵の管理はKMSを使用しているか
- [ ] バックアップデータも暗号化されているか
- [ ] ログに個人情報が出力されていないか

## API セキュリティ
- [ ] HTTPS が必須になっているか
- [ ] レートリミットが設定されているか
- [ ] 入力バリデーションが全エンドポイントで実装されているか
- [ ] CORS設定は最小限か

## インフラストラクチャ
- [ ] セキュリティグループは最小権限か
- [ ] 不要なポートは閉じているか
- [ ] IAMロールは最小権限か
- [ ] シークレット(API キー、パスワード)はSecrets Managerに格納されているか

## 監視・検知
- [ ] セキュリティイベントの監視は設定されているか
- [ ] 不正アクセスの検知とアラートは設定されているか
- [ ] 監査ログは改ざん防止されているか
- [ ] インシデント対応手順は文書化されているか

## コンプライアンス
- [ ] 対象となる法規制を特定しているか
- [ ] データの保持期間ポリシーは定義されているか
- [ ] データ主体の権利行使に対応できるか
- [ ] プライバシーポリシーは更新されているか

まとめ

ポイント内容
Security by Designセキュリティはアーキテクチャ設計の初期段階から組み込む
多層防御ネットワーク、アプリケーション、データ、インフラの各層で防御
認証・認可OAuth2/OIDC + RBAC/ABACの適切な選択
データ暗号化保存時(At Rest)と通信時(In Transit)の両方を暗号化
コンプライアンス対象法規制を特定し、アーキテクチャで対応
脅威モデリングSTRIDEで体系的に脅威を特定し、対策を設計

チェックリスト

  • 多層防御の各レイヤーを説明できる
  • OAuth2/OIDCのフローを説明できる
  • RBACとABACの使い分けを理解した
  • STRIDEの6カテゴリを列挙できる
  • GDPR/PCI-DSSの主要要件を把握した

次のステップへ

セキュリティとコンプライアンスの設計を学びました。次は総合演習です。ここまで学んだ非機能要件の知識を使い、実際のシステムに対してNFRを定義する演習に取り組みましょう。


推定読了時間: 30分