ストーリー
佐
佐藤CTO
認証・認可をしっかり設計しても、ネットワークが穴だらけでは意味がない
佐
佐藤CTO
ゼロトラストでは、ネットワーク上の位置を信頼しない。つまり内部ネットワークでも暗号化と検証が必要だ
あなた
マイクロセグメンテーションですね。サービスごとにネットワークを分離する
あ
佐
佐藤CTO
その通り。さらにWAF、VPC設計、Kubernetesのネットワークポリシーまで。多層的にネットワークを守る方法を見ていこう
マイクロセグメンテーション
従来のネットワークセグメンテーションとの違い
| 観点 | 従来のセグメンテーション | マイクロセグメンテーション |
|---|
| 粒度 | サブネット/VLAN単位 | ワークロード/Pod単位 |
| 制御対象 | 南北トラフィック(外→内) | 東西トラフィック(内部間)も含む |
| ポリシー | IPアドレスベース | アイデンティティ/ラベルベース |
| 変更頻度 | 低い(ハードウェア依存) | 高い(ソフトウェア定義) |
| スケーラビリティ | ハードウェアに制限 | オートスケールに追従 |
Kubernetes Network Policy
# 注文サービスのネットワークポリシー
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: order-service-netpol
namespace: production
spec:
podSelector:
matchLabels:
app: order-service
policyTypes:
- Ingress
- Egress
ingress:
# API Gatewayからのみ受信許可
- from:
- podSelector:
matchLabels:
app: api-gateway
- namespaceSelector:
matchLabels:
name: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 9090 # メトリクス用
egress:
# 注文DB(PostgreSQL)への接続のみ
- to:
- podSelector:
matchLabels:
app: order-db
ports:
- protocol: TCP
port: 5432
# 決済サービスへの通信
- to:
- podSelector:
matchLabels:
app: payment-service
ports:
- protocol: TCP
port: 8080
# 外部DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# デフォルト拒否ポリシー(全通信をブロック)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
VPCアーキテクチャ設計
セキュアなVPC構成
graph TD
subgraph VPC["VPC: 10.0.0.0/16"]
subgraph Public["Public Subnet (10.0.1.0/24, 10.0.2.0/24)"]
ALB["ALB/NLB"]
NAT["NAT GW"]
Bastion["Bastion"]
end
subgraph PrivateApp["Private Subnet - App (10.0.10.0/24, 10.0.11.0/24)"]
SvcA["ECS/EKS<br/>Service A"]
SvcB["ECS/EKS<br/>Service B"]
SvcC["ECS/EKS<br/>Service C"]
end
subgraph PrivateData["Private Subnet - Data (10.0.20.0/24, 10.0.21.0/24)"]
RDS[("RDS Primary")]
ES[("ElasticSearch")]
Redis[("Redis Cluster")]
end
end
ALB --> SvcA
ALB --> SvcB
ALB --> SvcC
SvcA --> RDS
SvcB --> ES
SvcC --> Redis
classDef publicSn fill:#198754,stroke:#146c43,color:#fff
classDef privateSn fill:#0d6efd,stroke:#0a58ca,color:#fff
classDef dataSn fill:#f5a623,stroke:#c47d10,color:#fff
class ALB,NAT,Bastion publicSn
class SvcA,SvcB,SvcC privateSn
class RDS,ES,Redis dataSn
セキュリティグループの設計
// CDKでのセキュリティグループ設計例
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as cdk from 'aws-cdk-lib';
export class SecureVpcStack extends cdk.Stack {
constructor(scope: cdk.App, id: string) {
super(scope, id);
const vpc = new ec2.Vpc(this, 'SecureVpc', {
maxAzs: 2,
subnetConfiguration: [
{ name: 'Public', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24 },
{ name: 'Private-App', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24 },
{ name: 'Private-Data', subnetType: ec2.SubnetType.PRIVATE_ISOLATED, cidrMask: 24 },
],
});
// ALBセキュリティグループ(最外層)
const albSg = new ec2.SecurityGroup(this, 'AlbSg', {
vpc,
description: 'ALB - HTTPS only',
allowAllOutbound: false,
});
albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'HTTPS from internet');
// アプリケーションセキュリティグループ
const appSg = new ec2.SecurityGroup(this, 'AppSg', {
vpc,
description: 'App - ALB only',
allowAllOutbound: false,
});
appSg.addIngressRule(albSg, ec2.Port.tcp(8080), 'From ALB only');
// データベースセキュリティグループ(最内層)
const dbSg = new ec2.SecurityGroup(this, 'DbSg', {
vpc,
description: 'DB - App only',
allowAllOutbound: false,
});
dbSg.addIngressRule(appSg, ec2.Port.tcp(5432), 'PostgreSQL from App only');
// VPCフローログの有効化(監査用)
vpc.addFlowLog('FlowLog', {
destination: ec2.FlowLogDestination.toCloudWatchLogs(),
trafficType: ec2.FlowLogTrafficType.ALL,
});
}
}
WAF(Web Application Firewall)
WAFルールの設計
| ルールカテゴリ | 目的 | 設定例 |
|---|
| レート制限 | DDoS / ブルートフォース防止 | 同一IPから5分間に1000リクエスト |
| SQLインジェクション | DB攻撃防止 | SQLiパターンの検出とブロック |
| XSS | クロスサイトスクリプティング防止 | スクリプトタグの検出 |
| Geo制限 | 地域ベースのアクセス制御 | 特定国からのアクセスをブロック |
| Bot管理 | 悪意のあるボットの排除 | 既知のBot IPリストとの照合 |
| カスタムルール | アプリ固有の保護 | 特定パスへのアクセス制限 |
// AWS WAF v2 ルール定義(CDK)
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
const webAcl = new wafv2.CfnWebACL(this, 'WebAcl', {
scope: 'REGIONAL',
defaultAction: { allow: {} },
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'WebACLMetric',
sampledRequestsEnabled: true,
},
rules: [
{
name: 'RateLimit',
priority: 1,
action: { block: {} },
statement: {
rateBasedStatement: {
limit: 1000,
aggregateKeyType: 'IP',
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'RateLimitRule',
sampledRequestsEnabled: true,
},
},
{
name: 'AWSManagedRulesSQLi',
priority: 2,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesSQLiRuleSet',
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'SQLiRule',
sampledRequestsEnabled: true,
},
},
{
name: 'AWSManagedRulesXSS',
priority: 3,
overrideAction: { none: {} },
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesKnownBadInputsRuleSet',
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'XSSRule',
sampledRequestsEnabled: true,
},
},
{
name: 'LoginRateLimit',
priority: 4,
action: { block: {} },
statement: {
rateBasedStatement: {
limit: 100,
aggregateKeyType: 'IP',
scopeDownStatement: {
byteMatchStatement: {
searchString: '/api/auth/login',
fieldToMatch: { uriPath: {} },
textTransformations: [{ priority: 0, type: 'LOWERCASE' }],
positionalConstraint: 'STARTS_WITH',
},
},
},
},
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: 'LoginRateLimit',
sampledRequestsEnabled: true,
},
},
],
});
TLS / 証明書管理
TLS設定のベストプラクティス
| 項目 | 推奨設定 | 避けるべき設定 |
|---|
| プロトコル | TLS 1.3(最低TLS 1.2) | SSL 3.0, TLS 1.0, TLS 1.1 |
| 暗号スイート | AES-256-GCM, ChaCha20 | RC4, DES, 3DES |
| 鍵長 | RSA 2048bit以上 / ECDSA 256bit以上 | RSA 1024bit |
| 証明書 | ACM / Let’s Encrypt 自動更新 | 手動管理、期限切れリスク |
| HSTS | max-age=31536000; includeSubDomains | HSTSなし |
// Node.js / Express でのTLS設定
import https from 'https';
import fs from 'fs';
const tlsOptions: https.ServerOptions = {
key: fs.readFileSync('/etc/ssl/private/server.key'),
cert: fs.readFileSync('/etc/ssl/certs/server.crt'),
ca: fs.readFileSync('/etc/ssl/certs/ca.crt'),
// TLS 1.2以上のみ許可
minVersion: 'TLSv1.2',
// 強力な暗号スイートのみ使用
ciphers: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256',
].join(':'),
// サーバー側の暗号スイート優先順位を使用
honorCipherOrder: true,
};
const server = https.createServer(tlsOptions, app);
DNS セキュリティ
| 対策 | 説明 | 実装方法 |
|---|
| DNSSEC | DNS応答の改ざん防止 | Route 53 DNSSEC signing |
| DNS over HTTPS (DoH) | DNS通信の暗号化 | CloudflareのDNS (1.1.1.1) |
| Split-horizon DNS | 内部/外部で異なるDNS応答 | Route 53 Private Hosted Zone |
| DNS Firewall | 悪意のあるドメインへのアクセス防止 | Route 53 Resolver DNS Firewall |
まとめ
| ポイント | 内容 |
|---|
| マイクロセグメンテーション | ワークロード単位でネットワークを分離、東西トラフィックも制御 |
| K8s Network Policy | Pod単位のIngress/Egressルール、デフォルト拒否が基本 |
| VPC設計 | Public/Private/Isolated の3層、セキュリティグループで最小権限 |
| WAF | レート制限、SQLi/XSS防止、マネージドルールの活用 |
| TLS | TLS 1.3推奨、証明書の自動更新、HSTSの設定 |
チェックリスト
次のステップへ
次は「データ保護」を学びます。暗号化(at rest / in transit)、KMS/Vault、PII保護、GDPRなど、データ中心のセキュリティを身につけましょう。
推定読了時間: 40分