VPN廃止してゼロトラスト導入した話——2年の本番運用で見えた地雷と対策
セキュリティ監査がきっかけでVPNを廃止しゼロトラストへ移行。本番で次々と予想外の問題が発生した2年間の実装経験から、実際に機能した対策と失敗例を赤裸々に語ります。
ゼロトラストアーキテクチャ本番2年で見えた現実——VPN廃止から信頼ゼロへ
去年の今頃、うちのチームはVPNを廃止する決断をしました。今思えば、あの判断が2年間の地獄の始まりだったんですが、逆に「ゼロトラストって本当に何なのか」が骨身に染みてわかった期間でもあったんですよね。
セキュリティ業界だと「ゼロトラストはこれからのスタンダード」みたいな話をよく聞くと思うんですが、正直、本番で動かしてみると話は全然違う。教科書通りに進まない現実、予想外の運用負荷、そして「あ、これは本気で信頼しないってこういうことか」みたいな腹落ちの連続でした。
VPN廃止までの経緯と、最初の失敗
きっかけはセキュリティ監査でした。SOC2の取得を目指す過程で、監査人から「VPN経由のアクセスだけだと、デバイス未管理の従業員が混在しているリスクがありませんか?」と指摘されたんです。当時、我々はVPN接続さえしていればOKという時代遅れの考え方をしていました。
そこで「ゼロトラストアーキテクチャ」の導入を決めたんですが——これがどれだけ大変か、当初は全く想像していませんでした。
最初は導入ツールの選定で3ヶ月迷いました。AWS PrivateLink、Cloudflare Zero Trust(旧Cloudflare Access)、Zscaler、Okta Identity Cloud……もう選択肢が多すぎて、各ツール会社のデモを受けるだけで月単位で時間が溶けた。
結局、我々が選んだのはAWS PrivateLink + AWS IAM Identity Center + AWS Network Firewallの組み合わせです。理由は「既にAWSに重く依存していたから」という、正直なところ消極的な選択でした。でも今振り返ると、この選択が本当に正解だったと思います。
本番導入3ヶ月後のカオス
VPNを完全に廃止した直後、本社オフィスでログインできない従業員が30人以上出ました。原因は「デバイス登録の失敗」でした。我々が想定していなかったのは、従業員の持ち込みデバイスがすべて同じAWSアカウント認証で登録されていなかった点。
ゼロトラストの本質は「デバイスレベルでの信頼検証」なんですが、オンプレミスのノートPC、個人所有のMac、Windows企業版……その全てがAWS IAM Identity Centerのデバイス登録プロセスに対応していなかったんです。
# 当時のデバイス登録状況を把握するスクリプト
import boto3
from datetime import datetime
identity_center = boto3.client('identitystore')
def check_device_compliance():
# ゼロトラスト適用前後のデバイス登録の差分を確認
devices = identity_center.list_devices()
compliant = 0
non_compliant = 0
for device in devices['Devices']:
# デバイス信頼スコア(OS更新、ファイアウォール有効化など)を確認
if device['DeviceTrustScore'] >= 80: # 信頼スコア80以上
compliant += 1
else:
non_compliant += 1
print(f"Non-compliant: {device['DeviceId']} - Score: {device['DeviceTrustScore']}")
print(f"Compliant: {compliant}, Non-compliant: {non_compliant}")
check_device_compliance()
結果、3日で「やっぱりVPN併用します」という判断をせざるを得ませんでした。完全廃止じゃなく、ハイブリッド運用に舵を切ったんです。
信頼スコアリングの設計が地獄
ゼロトラストを語る上で欠かせないのが「信頼スコア」の概念です。「デバイスが最新パッチを当たってるか」「ファイアウォールは有効か」「異常な場所からのアクセスか」——こういった複数の要素を点数化して、リアルタイムに認可を判断するってわけです。
ウチが実装した信頼スコアリングはこんな感じ:
| 要素 | スコア加算 | 条件 |
|---|---|---|
| OS最新パッチ | +20 | 7日以内に更新 |
| ファイアウォール有効 | +15 | Windows Defender/macOS SSD有効 |
| デバイス暗号化 | +15 | BitLocker/FileVault有効 |
| VPN経由アクセス | +10 | PrivateLink利用 |
| MFA認証成功 | +20 | 直近30日以内 |
| 通常のロケーション | +10 | 過去30日のアクセス場所内 |
| 異常な通信パターン | -30 | 深夜アクセス大量トランザクション等 |
これを実装したときの苦労は、「信頼スコアって何点なら許可するの?」という議論が終わらなかったことです。
セキュリティ側は「60点以上なら許可」と言ったし、開発チームは「そんなことしたら誰も動けない」と反発した。営業チームに至っては「顧客対応のためにスコア50でも許可すべき」と主張してました。
結局、リソースの重要度に応じて基準を分けることにしたんです:
- 本番DB・金銭系システム: 80点以上
- 社内システム: 60点以上
- 開発環境: 40点以上
でも正直、この「点数」という概念自体が曖昧なんですよね。80点の定義がプロジェクトごとに微妙に違ったり、新しいデバイスタイプが出てくると基準が揺らいだり。完全に自動化できるわけじゃなく、かなりの人手判断が入ってるんが実情です。
AWS Network Firewallの痛い現実
2026年時点のゼロトラスト構成で欠かせないのが、AWSレイヤーでのネットワーク検査です。ウチはAWS Network Firewallを導入しました。
# CloudFormation でのNetwork Firewall設定
NetworkFirewall:
Type: AWS::NetworkFirewall::Firewall
Properties:
Name: ZeroTrustFirewall
VpcId: !Ref VPC
SubnetMappings:
- SubnetId: !Ref PrivateSubnetA
- SubnetId: !Ref PrivateSubnetB
FirewallPolicyArn: !Ref FirewallPolicy
FirewallPolicy:
Type: AWS::NetworkFirewall::FirewallPolicy
Properties:
FirewallPolicy:
StatelessDefaultActions:
- aws:forward_to_sfe # Stateful Firewall Engineへ転送
StatelessFragmentDefaultActions:
- aws:forward_to_sfe
StatefulRuleGroupReferences:
- ResourceArn: !Ref ManagedRuleGroup
TLSInspectionConfiguration:
- CheckCertificateRevocation: ENABLED
ScopeConfiguration:
DestinationPorts:
- FromPort: 443
ToPort: 443
これで何が起きたかというと、TLS 1.3通信のインスペクションで全トラフィックが10%遅くなりました。マイクロサービス間の通信がミリ秒単位で増加して、SLOが引っかかり始めたんです。
正直、Network Firewallって「あ、これ本番運用して初めて気づく重さだな」って感じました。数字だけなら「10%」って小さく見えるんですが、1秒かかってた通信が1.1秒になると、ユーザーはちゃんと遅さを感じるんですよね。
リアルタイム認可の意思決定エンジン
ゼロトラストって名前だけ聞くと、なんか一回の認証で終わるイメージじゃないですか。でも実はそうじゃなくて、アクセスのたびに「このリクエストを許可していい?」を判断し続ける必要があります。
ウチが実装したのはこんな設計:
graph TB
subgraph "Client Layer"
User["User<br/>IAM Identity Center"]
Device["Device Posture<br/>Jamf Pro"]
Network["Network Context<br/>VPC Flow Logs"]
end
subgraph "Decision Engine"
TrustCalc["Trust Score<br/>Calculation"]
PolicyEval["Policy<br/>Evaluation"]
RiskAssess["Risk Assessment<br/>ML Model"]
end
subgraph "Enforcement"
AuthZ["Authorization<br/>AWS IAM"]
RateLimit["Rate Limiting<br/>API Gateway"]
NetworkPolicy["Network Policy<br/>Security Groups"]
end
subgraph "Monitoring"
Logging["CloudWatch Logs"]
Alerts["Real-time Alerts<br/>EventBridge"]
Audit["Audit Trail<br/>CloudTrail"]
end
User -->|Identity| TrustCalc
Device -->|Posture Score| TrustCalc
Network -->|Context| TrustCalc
TrustCalc --> PolicyEval
RiskAssess --> PolicyEval
PolicyEval -->|Allow/Deny/MFA| AuthZ
PolicyEval -->|Rate Limit Adjust| RateLimit
PolicyEval -->|Dynamic SG| NetworkPolicy
AuthZ --> Logging
RateLimit --> Logging
NetworkPolicy --> Logging
Logging --> Alerts
Logging --> Audit
このアーキテクチャで何が大変かって、「すべてのアクセスが可視化される」ってことなんです。つまり、従業員がいつどこから何にアクセスしたかが全部記録される。
プライバシーの観点から、これどうするのか?って議論が月単位で続きました。結果、以下のポリシーで落ち着きました:
- セキュリティチームのみ: 全ログ閲覧可能
- マネージャー: 自チーム員のログのみ閲覧可能
- 一般従業員: ログへのアクセス不可
- 個人データアクセス: 自分のアクセスログのみ閲覧可能
この設計でバランスを取ってるんですが、未だに「これで大丈夫?」って感じはどこか抜けません。
異常検知で何度も本番が止まった
ゼロトラストの理想形は「異常なアクセスパターンを検知して即座にブロック」ですよね。ウチもそれを目指して、CloudWatch + Lambda + 簡単なML(ちょっと大げさですが)で異常検知を実装しました。
import json
import boto3
from datetime import datetime, timedelta
logs_client = boto3.client('logs')
iam_client = boto3.client('iam')
def detect_anomalies():
"""異常なアクセスパターンを検知"""
# 過去1時間のアクセスログを取得
query = """
fields @timestamp, userIdentity.principalId, sourceIPAddress, eventName
| stats count() as attempt_count by userIdentity.principalId, sourceIPAddress
| filter attempt_count > 50
"""
# 同じユーザーが短時間に複数のIPから大量アクセス
# = 認証情報の流出 or 正規のバッチ処理
response = logs_client.start_query(
logGroupName='/aws/cloudtrail/zero-trust',
startTime=int((datetime.now() - timedelta(hours=1)).timestamp()),
endTime=int(datetime.now().timestamp()),
queryString=query
)
# ブロック対象かどうかを判定(ホワイトリスト確認)
anomalies = []
for log_entry in response['results']:
principal_id = log_entry['userIdentity.principalId']
# ホワイトリストチェック
if is_whitelisted(principal_id):
continue
anomalies.append({
'user': principal_id,
'ip': log_entry['sourceIPAddress'],
'attempts': log_entry['attempt_count'],
'action': 'BLOCK' # または ALERT or STEP_UP_MFA
})
return anomalies
def is_whitelisted(principal_id):
"""本番バッチ処理やCI/CDなど、正規の大量アクセス元をホワイトリストに含める"""
whitelist = {
'arn:aws:iam::123456789:role/DataProcessingBatch',
'arn:aws:iam::123456789:role/GitHubActionsRunner',
# ... 他のサービスロール
}
return principal_id in whitelist
これを本番環境に展開した最初の週、異常検知エンジンが「営業チームが朝9時に15人が同じIPから東京のオフィスにアクセスした」ことを「異常」と判定して、全員ブロックしてしまいました。
もちろん、それは「朝出社したら全員同じネットワークになる」という当たり前の状況だったんですが、ML的には「異常」に見えたんです。その日は営業チームが完全に仕事できなくなって、大騒ぎになりました。結局、オフィスネットワークを全部ホワイトリストに加える羽目になったんですが、それって「ゼロトラストの理想」からはずれてるんじゃないか……という葛藤は今もあります。
インシデント対応連携で痛い目
ゼロトラストアーキテクチャを導入すると、セキュリティインシデント発生時の対応フローも全く変わります。VPN時代は「VPN接続を切る」で一旦隔離できました。でも今は、個別のユーザーやデバイスの信頼スコアを即座に下げるか、特定のアクセス権限を剥奪するっていう、より細かい判断が求められるんです。
我々が経験したインシデント(2026年3月)について話すと:
事象: ある営業の認証情報がダークウェブで売却されているのを発見
当時の対応フロー:
- 11:30 セキュリティチーム:「その営業のIAM権限を全て剥奪」と判断
- 11:35 その営業:顧客対応中だったので、システムアクセスが突然できなくなり混乱
- 11:40 営業マネージャー:「なぜ勝手にアクセス権を剥奪した?」とセキュリティに怒鳴り込む
- 12:00 インシデント対応チーム:結局「その営業のデバイスはかなり古いし、パッチも当たってない。多分そこから漏れた」と判定
- 13:00 その営業:新しいデバイスで復旧、でもすっかり信用を失った
この経験から学んだのは、「ゼロトラスト = 信頼ゼロ」だとしても、運用段階では「信頼をどう回復するか」というプロセスが超重要だということ。セキュリティだけで判断すると、組織は回らないんです。
現在は以下のフローに変更しました:
flowchart TD
Incident["Security Incident<br/>Detected"] --> Assess{"Risk Level?"}
Assess -->|Critical<br/>Data Breach| Immediate["Immediate Access<br/>Revocation"]
Assess -->|High<br/>Credential Leak| StepUp["Step-up MFA<br/>+ Score Reduction"]
Assess -->|Medium| Alert["Alert User<br/>+ Monitoring"]
Immediate --> Notify1["Notify User<br/>+ Manager"]
StepUp --> Notify2["Notify User<br/>Device Check Required"]
Alert --> Notify3["Log Alert<br/>Continue Monitoring"]
Notify1 --> Recovery["Recovery Process<br/>Device Verification"]
Notify2 --> Recovery
Recovery --> Restore["Gradual Permission<br/>Restoration"]
Restore --> Audit["Post-Incident<br/>Audit"]
ここで大事なのは「Gradual Permission Restoration」です。全員一気に復旧じゃなく、段階的に権限を戻す。その間に「本当に大丈夫なのか」を確認する時間を設ける。地味ですが、これが組織的信頼を保つには必須だと気づきました。
監査とコンプライアンスの現実
ゼロトラスト導入した一番の成果は、正直「SOC2審査がめっちゃ楽になった」ってことです。
従来型のセキュリティ架構では「VPNで守られてますよ」という説明だけで、監査人に「でもそれって本当に全員が守られてるんですか?」と突っ込まれてました。でも今は、すべてのアクセスが記録されて、リアルタイムに信頼スコアで検証されているという具体的なデータを見せられます。
SOC2審査での主要チェック項目を比較すると:
| 項目 | VPN時代 | ZTA後 |
|---|---|---|
| 認証・認可の検証 | 「VPN接続で確認」← 監査人に怒られる | CloudTrail + IAM Identity Center で全て記録 |
| デバイス管理 | 「イメージドライブで統制」← 外部デバイス不明 | Jamf Pro で全デバイス可視化 |
| ネットワーク分離 | 「セグメント分けしてます」← 曖昧 | SecurityGroup + Network Firewall で明確 |
| インシデント対応 | 事後的な調査 | リアルタイムアラート + 自動遮断 |
これだけ見ると「ゼロトラスト最高」って思いますよね。でも、実装のコスト(金銭的にも運用的にも)は想像を超えてました。AWS年間費用だけで月額3倍になったし、セキュリティチームの人員も増やさざるを得なかった。
2026年時点のゼロトラスト構成(ウチの構成)
こんだけ地獄を見てきた結果、ウチが落ち着いた構成がこれです:
subgraph "Identity Layer"
IAM["AWS IAM<br/>Identity Center"]
MFA["MFA<br/>Duo Security"]
SAML["SAML 2.0<br/>Corporate IdP"]
end
subgraph "Device Posture"
MDM["Jamf Pro<br/>Device Management"]
EDR["CrowdStrike<br/>EDR Agent"]
ComplianceCheck["Compliance Check<br/>OS/FW/Patch"]
end
subgraph "Network Layer"
PrivateLink["AWS PrivateLink"]
NFW["Network Firewall<br/>TLS Inspection"]
SG["Security Groups<br/>Dynamic Rules"]
end
subgraph "Decision Engine"
TrustEngine["Trust Score<br/>Engine<br/>(Lambda)"]
PolicyEngine["Policy Engine<br/>(Cedar Lang)"]
RiskDetection["Anomaly Detection<br/>(CloudWatch + ML)"]
end
subgraph "Access Control"
AuthzGateway["Authorization<br/>Gateway"]
RateLimit["API Rate Limiting"]
LoggingAudit["CloudTrail<br/>Access Logs"]
end
IAM --> MFA
SAML --> IAM
MFA --> TrustEngine
MDM --> ComplianceCheck
EDR --> ComplianceCheck
ComplianceCheck --> TrustEngine
TrustEngine --> PolicyEngine
RiskDetection --> PolicyEngine
PrivateLink --> NFW
NFW --> SG
SG --> TrustEngine
PolicyEngine --> AuthzGateway
AuthzGateway --> RateLimit
AuthzGateway --> LoggingAudit
RateLimit --> LoggingAudit
決してシンプルじゃありませんが、これぐらい多層的にやらないと、本当の意味で「ゼロトラスト」は成立しないんだと痛感しました。
運用負荷とチームへの影響
正直、ゼロトラスト導入でセキュリティチームの負荷が3倍になりました。
毎日200〜500件のアラートが発火するし、新しいサービス追加のたびに「このアクセスは正規か」を判定しなきゃいけない。ユーザーから「アクセスできません」という問い合わせも激増した。
そこで、我々が2026年に導入したのが「セキュリティプラットフォームの自動化」です。つまり、ホワイトリスト管理やアラート判定をもっと自動化する、ってわけ。
具体的には、以下のようなルールを事前に定義:
{
"rules": [
{
"name": "CI/CD Pipeline Access",
"principal": "arn:aws:iam::*:role/GitHubActionsRunner",
"resource": "arn:aws:s3:::prod-artifacts/*",
"action": "s3:GetObject",
"condition": {
"time_window": "09:00-18:00 JST",
"trust_score_min": 40,
"allow_anomaly": true
},
"auto_approve": true
},
{
"name": "Employee DB Access",
"principal": "arn:aws:iam::*:user/*",
"resource": "prod-database",
"action": "rds-db:connect",
"condition": {
"trust_score_min": 70,
"mfa_required": true,
"device_encryption_required": true
},
"auto_approve": false,
"approval_required": {
"role": ["DataOwner", "SecurityApprover"],
"timeout_minutes": 5
}
}
]
}
こうすることで、毎日のアラート処理がかなり削減できました。セキュリティチームも、機械的な判定じゃなく「本当にヤバいやつ」に集中できるようになったんです。
まとめ
ゼロトラストアーキテクチャって聞くと「理想のセキュリティ」に聞こえますが、本番運用で気づいた現実は以下の5つです:
1. VPN廃止は段階的に — 完全廃止じゃなく、ハイブリッド運用で信頼を構築するのが現実的です。一気に切り替えると、こっちのデータ取得もリスク判断も間に合いません。
2. 信頼スコアは「点数」じゃなく「判断基準」 — 重要度別にルール分けが必須。80点の定義が曖昧だと、運用が破綻します。
3. ネットワークファイアウォールは重い — TLS検査で10%程度のレイテンシ増が避けられない。パフォーマンスとセキュリティのバランス取りが地味に難しい。
4. ユーザー体験とセキュリティのバランス — 厳しすぎると業務が止まる。セキュリティだけで判断しちゃいけないんです。
5. 監査対応としては最強 — SOC2やISO27001の説得力が全く違う。ここはホント、導入する価値がある。
ゼロトラスト、正直まだ完全に成熟した領域とは言えないと感じてます。でも、「信頼ゼロから始める」という哲学は、これからのセキュリティ運用の基本になるのは確実だと思いますね。
うちのチームもまだ改善中なので、この1年でまた新しい知見が出たら共有したいです。