電子カルテ(EHR)で Premium vCore モデルの Azure SQL Database を用い、PHI(保護対象医療情報)へのアクセスを厳格に監査しつつ、機械学習ベースの脅威検出でインシデントを即応する――この要件を満たすための設計と実装を、運用に乗るレベルまで具体的に解説します。監査の改ざん防止、長期保管、Microsoft Sentinel 連携、HIPAA/HITECH のセクション 164.312 に対応する実装ポイントを“そのまま使える手順”でまとめました。
全体像:監査・脅威検出・対応を一体化するアーキテクチャ
本記事のゴールは、Azure SQL Database 上で発生する「誰が・いつ・何に・どうアクセスしたか」を改ざん不可能な形で残し、異常を早期に検知し、Microsoft Sentinel(SIEM/SOAR)での自動対応までを一貫運用できる状態を作ることです。アプリケーションは Microsoft Entra ID(旧 Azure AD)による認証・認可を前提とし、ワークロード・ネットワーク・鍵管理をゼロトラストで統合します。
リファレンス構成(テキスト図)
[EHR アプリ(Managed Identity / OIDC)]
│ └─ Private Link / Azure Firewall / NSG
▼
[Azure SQL Database(Premium vCore)]
├─ SQL 監査 → Log Analytics(検索/ダッシュボード)
│ └─ Data Export / アーカイブ → Azure Storage(WORM)
├─ Microsoft Defender for SQL(脅威検出アラート)
│ └─ Log Analytics(SecurityAlert)
└─ Azure Policy(構成の強制/ドリフト検出)
[Microsoft Sentinel(SIEM/SOAR)]
├─ 解析ルール(KQL)
├─ インシデント自動化(Logic Apps/Playbooks)
└─ 通知(Teams/PagerDuty/Email)
主なコンポーネントと役割
| 要素 | 役割 | HIPAA の観点 |
|---|---|---|
| Azure SQL Auditing | ログイン/DML/DDL を証跡化し、Log Analytics/Storage に出力 | Audit Controls(164.312(b)) |
| Microsoft Defender for SQL | SQL インジェクション、異常ログイン、データ持ち出し等の検出 | Security Incident Procedures(164.308(a)(6)) |
| Azure Storage(WORM) | 監査証跡の改ざん防止&長期保存(時間ロック/法的ホールド) | Integrity & Non‑repudiation |
| Log Analytics | KQL 検索、可視化、Sentinel へのデータ供給 | 継続的モニタリング |
| Microsoft Sentinel | 相関検知と自動対応(SOAR) | インシデント対応の自動化と証跡化 |
| TDE(CMK/BYOK)+ Key Vault | データ暗号化と鍵の分離管理、年1回以上のローテーション | Encryption & Key Management(164.312(a)(2)(iv)) |
| Entra ID(AD-only 認証) | SQL 認証の排除、MFA/CA/PIM による統制 | Access Control(164.312(a)(1)、(d)) |
前提条件とベースライン(ゼロトラスト設定)
- Entra ID のみを許可:論理サーバーで「Active Directory のみの認証」を有効化し、SQL ログインは無効化。
- 特権アクセスの一時化:Privileged Identity Management(PIM)で「SQL Server Administrator(Entra 組織管理者)」を JIT 付与、MFA と承認フロー必須。
- ネットワークの秘匿化:Private Endpoint を使用し、サーバーの「パブリック ネットワーク アクセス無効化」。必要時は Azure Firewall/Firewall Policy で egress を最小化。
- アプリの機密性:接続は Managed Identity(推奨)または OIDC クライアント資格情報。機密情報は Key Vault 参照。
- TLS 強制:DB 接続は TLS 1.2 以上、Encrypt=True, TrustServerCertificate=False を徹底。
監査のスコープ設計と出力先(改ざん不可・検索性の両立)
推奨スコープ
サーバー(論理サーバー)単位で監査を有効化し、全 DB をデフォルトでカバー。PHI が乗る一部 DB/テーブルのみ、追加の詳細監査をデータベース側で上書きする二層構成が現実的です。
出力先の選び分け
| 出力先 | 用途 | 構成ポイント |
|---|---|---|
| Log Analytics | 調査・相関分析(KQL)/ Sentinel 連携 | ワークスペース当たりの保持期間を 12–24 か月。必要に応じてテーブル単位の保持とアーカイブを併用。 |
| Azure Storage(Blob) | WORM で 6 年以上の長期保管 | コンテナーに不変ポリシー(時間ロック/法的ホールド)を設定。SSE with CMK、アクセスは RBAC 最小権限。 |
| 併用 | 検索性と改ざん耐性の両取り | 監査設定で Log Analytics と Storage を同時送信。運用の第一タッチは Log Analytics、監査委員会向けは WORM 出力。 |
監査イベント(Action Groups)の推奨セット
| Action Group | 目的 | 注意点 |
|---|---|---|
SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP | 成功ログインの追跡(誰が入ったか) | アプリケーション ID と人の ID を分けて可視化。 |
FAILED_DATABASE_AUTHENTICATION_GROUP | ブルートフォース/不正試行の検出 | Sentinel でしきい値超過をアラート化。 |
SCHEMA_OBJECT_CHANGE_GROUP | DDL(スキーマ/権限)変更の証跡化 | 変更申請と突合用の根拠に。 |
DATABASE_OBJECT_ACCESS_GROUP | DML(SELECT/UPDATE/DELETE)アクセスの証跡 | SELECT のパラメータ値は記録されない点に注意(後述)。 |
ポータルでの有効化(サーバー単位)
- 「SQL サーバー」→「監査」→ オン。
- 出力先で「Log Analytics を有効化」「ストレージを有効化」を両方オン。
- Log Analytics ワークスペースを選択し、保持期間(例:18 か月)を設定。
- ストレージは専用アカウントに分離し、コンテナーに不変ポリシー(例:2190 日 = 6 年)を設定。
CLI/自動化例
Entra ID のみの認証・パブリック無効化
az sql server ad-only-auth update -g <rg> -n <server> --enable true
az sql server update -g <rg> -n <server> --public-network-access Disabled
監査(サーバー):Log Analytics + Storage 併用
az sql server audit-policy update -g <rg> -n <server> \
--state Enabled \
--is-azure-monitor-target-enabled true \
--log-analytics-workspace-resource-id </subscriptions/.../resourceGroups/.../providers/Microsoft.OperationalInsights/workspaces/<LAW>> \
--storage-account <storageAccountName> --retention-days 0 \
--audit-actions-and-groups "SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP" \
"FAILED_DATABASE_AUTHENTICATION_GROUP" \
"SCHEMA_OBJECT_CHANGE_GROUP" \
"DATABASE_OBJECT_ACCESS_GROUP"
Blob 不変ポリシー(時間ロック)
az storage container create -n sqlaudit --account-name <sa>
# 2190 日(約 6 年)の WORM(追記のみ許可)
az storage container immutability-policy create \
--account-name <sa> --container-name sqlaudit --period 2190 \
--allow-protected-append-write true
# 返却された ETag を使ってロック確定(以後は短縮不可)
# az storage container immutability-policy lock ...
Bicep(監査・Defender・VA)
resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' existing = {
name: 'my-sqlsrv'
}
resource audit 'Microsoft.Sql/servers/auditingSettings@2023-05-01-preview' = {
name: 'default'
parent: sqlServer
properties: {
state: 'Enabled'
isAzureMonitorTargetEnabled: true
logAnalyticsWorkspaceResourceId: lawId
storageEndpoint: 'https://.blob.core.windows.net/'
storageAccountSubscriptionId: subscription().subscriptionId
retentionDays: 0
auditActionsAndGroups: [
'SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP'
'FAILED_DATABASE_AUTHENTICATION_GROUP'
'SCHEMA_OBJECT_CHANGE_GROUP'
'DATABASE_OBJECT_ACCESS_GROUP'
]
}
}
resource sap 'Microsoft.Sql/servers/securityAlertPolicies@2023-05-01-preview' = {
name: 'default'
parent: sqlServer
properties: {
state: 'Enabled'
emailAccountAdmins: 'Enabled'
emailAddresses: '[[email protected]](mailto:[email protected])'
disabledAlerts: ''
}
}
resource va 'Microsoft.Sql/servers/vulnerabilityAssessments@2021-02-01-preview' = {
name: 'default'
parent: sqlServer
properties: {
storageContainerPath: 'https://.blob.core.windows.net/va'
recurringScans: {
isEnabled: true
emailSubscriptionAdmins: true
emails: [ '[[email protected]](mailto:[email protected])' ]
}
}
}
Microsoft Defender for SQL(旧 ATP):脅威検出の設計
Defender for SQL は、 Azure SQL Database に対する機械学習ベースの検出を提供します。典型例は以下です。
- SQL インジェクション:アプリ層を経由した異常パターンを識別。
- 異常ログイン:未知の IP、地理的に不自然な散逸、クレデンシャル詐取兆候。
- データ持ち出し:短時間の大量 SELECT/BCP などの異常振る舞い。
発報は Log Analytics の SecurityAlert テーブルに取り込まれ、Sentinel の分析ルールでインシデント化できます。Playbook(Logic Apps)で「疑わしい IP を SQL サーバーのファイアウォールから即時遮断」「該当ユーザーの PIM ロールを手動承認に切り替え」「PagerDuty/Teams へ即通知」といった自動化が可能です。
Log Analytics(KQL)での実践的な可視化・検知
監査ログは SQLSecurityAuditEvents テーブル、Defender のアラートは SecurityAlert テーブルに格納される想定で、運用にそのまま使える KQL の雛形を示します(列名は環境で差異があるため project/extend で調整してください)。
過去 7 日間の監査イベント内訳
SQLSecurityAuditEvents
| where TimeGenerated > ago(7d)
| summarize count() by tostring(action_id_s)
| order by count_ desc
失敗ログインの多発ユーザー/IP
SQLSecurityAuditEvents
| where TimeGenerated > ago(24h)
| where tostring(action_id_s) has 'FAILED_DATABASE_AUTHENTICATION'
| summarize Failed=count() by User=tostring(database_principal_name_s), IP=tostring(client_ip_s)
| where Failed >= 5
| order by Failed desc
PHI テーブル(例:dbo.Patient)への DML 監査
let PhiTables = dynamic(['dbo.Patient','dbo.Exam','dbo.Claim']);
SQLSecurityAuditEvents
| where TimeGenerated > ago(7d)
| where tostring(action_id_s) has 'DATABASE_OBJECT_ACCESS'
| where tostring(object_name_s) in (PhiTables)
| project TimeGenerated, User=tostring(server_principal_name_s),
Object=tostring(object_name_s), Statement=substring(tostring(statement_s), 0, 200),
Succeeded=coalesce(succeeded_b, true), ClientIP=tostring(client_ip_s)
| order by TimeGenerated desc
業務時間外アクセス(平日 9–18 時以外)の監視
let BusinessHours = 9..18;
SQLSecurityAuditEvents
| where TimeGenerated > ago(7d)
| where datetime_part('hour', TimeGenerated) !in (BusinessHours)
| where tostring(action_id_s) has 'DATABASE_OBJECT_ACCESS'
| summarize Access=count() by User=tostring(server_principal_name_s)
| where Access > 100
| order by Access desc
Defender アラートの高優先度一覧
SecurityAlert
| where TimeGenerated > ago(30d)
| where AlertSeverity in ('High','Medium')
| where ProductName has 'SQL' or VendorOriginalProductName has 'SQL'
| project TimeGenerated, AlertName, AlertSeverity, CompromisedEntity, Description
| order by TimeGenerated desc
Sentinel でのインシデント化と自動対応
コネクタとデータスキーマ
- データコネクタ:Azure SQL Database Auditing(Log Analytics 経由)、Microsoft Defender for Cloud(SecurityAlert)。
- インシデントルール:分析ルール(KQL)で条件を定義。ベースラインしきい値は最初の 2–4 週の観測から決定。
大量データ持ち出しの疑い(疑似例)
let threshold = 10000; // セッション当たりの SELECT 回数のしきい値例
SQLSecurityAuditEvents
| where TimeGenerated > ago(1d)
| where tostring(action_id_s) has 'DATABASE_OBJECT_ACCESS'
| where tostring(statement_s) startswith_cs 'SELECT'
| summarize Selects=count() by bin(TimeGenerated, 1h), User=tostring(server_principal_name_s), IP=tostring(client_ip_s)
| where Selects > threshold
上記をベースに Analytics ルールを作成し、Playbook で「IP 遮断」「一時的に DB へのアプリ マネージド ID を無効化」「チケット発行」を自動化します。
よく使う自動化(Playbooks)の例
| トリガ | 自動アクション | 備考 |
|---|---|---|
| High の Defender for SQL アラート | 疑わしい送信元 IP をファイアウォールから遮断 | SQL サーバーの公開を無効化していれば、Private Link 側の NSG/Azure Firewall で遮断 |
| 短時間の失敗ログイン多発 | 該当アカウントの PIM ロールを停止、MFA 強制、Teams 通知 | Entra ID の条件付きアクセスと連携 |
| PHI テーブルへの業務時間外大量 SELECT | SOC へのメンション通知、該当接続文字列の回転 | Key Vault のバージョンを切り替え、アプリはマネージド ID で自動反映 |
暗号化と鍵管理(TDE/CMK、Always Encrypted)
PHI を扱うため、保存時暗号化(at rest)は TDE を前提とし、顧客管理キー(CMK/BYOK)でサーバーの TDE プロテクターを置き換えます。鍵は Key Vault または Managed HSM で管理し、年 1 回以上のローテーション、アクセスは Managed Identity 経由に限定します。
TDE プロテクターの CMK への切替(CLI)
# Key Vault で RSA キーを用意(省略)
# SQL サーバーの TDE プロテクターを CMK に変更
az sql server tde-key set -g <rg> -s <server> \
--kid https://<kv>.vault.azure.net/keys/<keyName>/<version> \
--type AzureKeyVault
監査ログの保管先(Storage)も SSE with CMK とし、鍵はアプリ運用者と分離した「監査・鍵管理チーム」のみがアクセスできるように RBAC を分離します。
Always Encrypted(必要に応じて)
患者のマイナンバー等の特定個人識別符号を含む列は Always Encrypted(Secure Enclaves) を併用することで、DB サーバー側で復号せず計算を許可できます。アプリケーション ドライバー設定とキーの運用が必要になります。
PHI 単位の追跡をどう実現するか(SELECT パラメータが残らない課題への対処)
標準の SQL 監査では SELECT のパラメータ値(例:患者 ID)が記録されません。どの患者が閲覧されたかの粒度が必要な場合は、次のいずれかを組み合わせます。
- アプリ層での明示的なトレーシング:Application Insights 等に「患者 ID」「リクエスト ID」「ユーザー ID」を記録し、SQL 監査の session_id または接続の相関 ID と突合。
- Row-Level Security(RLS)+ SESSION_CONTEXT:アプリが
SESSION_CONTEXT('actor')へ患者またはテナント情報を投入し、RLS ポリシーで許可/拒否を制御。監査上は「誰がどのテーブルにアクセスしたか」を残しつつ、アプリ側ログで患者 ID を復元できるようにします。
RLS と SESSION_CONTEXT の雛形
-- セッションにアクター情報を設定(アプリ接続直後)
EXEC sys.sp_set_session_context @key = N'actor', @value = @UserObjectId;
-- RLS: 患者テーブルにアクセス制御(例)
CREATE SCHEMA sec;
GO
CREATE FUNCTION sec.fn_rls_patient(@owner UNIQUEIDENTIFIER)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS accessGranted
WHERE @owner = CAST(SESSION_CONTEXT(N'actor') AS UNIQUEIDENTIFIER);
GO
CREATE SECURITY POLICY sec.policy_patient
ADD FILTER PREDICATE sec.fn_rls_patient(owner_id) ON dbo.Patient
WITH (STATE = ON);
この方式により、SQL 監査とアプリ監査を合わせて「誰が・どの患者レコードに・いつアクセスしたか」を再構成できます。
運用:ダッシュボード、定期評価、テスト
- ワークブック:Log Analytics で「失敗ログインの推移」「PHI テーブル別 DML」「DDL 変更の時系列」を可視化。Sentinel の Workbook として SOC に共有。
- 脆弱性評価(VA):月次のレポートを DBA/SOC が確認し、High は 2 週間以内に是正。
- 四半期テスト:擬似 SQLi、疑似 BCP ダンプ、業務時間外の連続 SELECT をシナリオ化して発報〜自動化の有効性を検証。
- 鍵ローテーション訓練:年 1 回、TDE プロテクターのローテーション演習と復旧手順のレビュー。
運用ダッシュボードの KPI 例
| KPI | 指標 | 目安 |
|---|---|---|
| 失敗ログイン率 | 失敗/総ログイン | < 2% |
| 高深刻度アラート TTD | 検出までの時間 | < 5 分 |
| 高深刻度アラート TTR | 封じ込めまでの時間 | < 15 分(自動化で短縮) |
| 未解決インシデント | オープン件数 | = 0 を維持 |
Azure Policy で“設定抜け”を防ぐ
監査や Defender を「必須化」するには Azure Policy が有効です。代表例:
- SQL サーバーは監査を有効にし Log Analytics へ送る(DeployIfNotExists)
- SQL サーバーは AD-only 認証を強制
- SQL データベースは TDE を有効(CMK)
- ストレージは不変ポリシーが設定済み
イニシアチブとして環境に割り当て、準拠状況をセキュリティ委員会に定期レポートします。
HIPAA/HITECH の技術的保護要件へのマッピング
| 要件 | 対応実装 | 補足 |
|---|---|---|
| Access Control(164.312(a)) | Entra ID のみ、PIM、RLS、最小権限、Private Link | 管理者アクセスは JIT、MFA 必須 |
| Audit Controls(164.312(b)) | SQL 監査(ログイン/DML/DDL)、WORM 保管 | 保持 6 年以上、改ざん不可 |
| Integrity(164.312(c)) | TDE(CMK)、ストレージの不変化、ハッシュ検証(必要時) | 鍵とデータの分離管理 |
| Person/Entity Authentication(164.312(d)) | Entra ID + MFA、SQL ログイン無効化 | サービス プリンシパル/Managed Identity を分離 |
| Transmission Security(164.312(e)) | TLS1.2+、Encrypt=True、Private Link | 証明書検証の強制 |
落とし穴とアンチパターン
- DB 単位だけで監査を有効化:新設 DB の取りこぼしが発生。サーバー単位を既定に。
- WORM を未設定:法的に有効な不可変性が担保できない。必ず時間ロック/法的ホールドを設定。
- SQL ログインが残存:Entra ID(AD-only)に統一して除去。
- ログ保持が短すぎる:Log Analytics は検索用、長期は Storage へ二重化。
- アプリの患者 ID 追跡を DB 監査だけに依存:アプリ層の相関ログを必ず併用。
即導入できるチェックリスト
- 論理サーバーで監査オン、Log Analytics と Storage(WORM)に同時出力。
- 監査イベントは
SUCCESSFUL/FAILED_AUTH、SCHEMA_OBJECT_CHANGE、DATABASE_OBJECT_ACCESSを基本セット。 - Defender for SQL をオン、Soc 通知先・Sentinel 連携を設定。
- Sentinel の分析ルールと Playbooks を有効化(IP 遮断、PIM 停止、通知)。
- TDE(CMK/BYOK) へ切替、鍵は Key Vault/Managed HSM、年 1 回以上ローテーション。
- AD-only 認証、MFA、PIM、Private Link に統一。
- RLS + SESSION_CONTEXT、アプリ層ログで患者 ID を追跡可能に。
- Azure Policy で必須設定を自動適用・監査。
- VA 月次、監査/自動化の四半期テストを継続。
実装スニペット集(Terraform 例)
resource "azurerm_mssql_server_extended_auditing_policy" "srv" {
server_id = azurerm_mssql_server.this.id
log_monitoring_enabled = true
log_analytics_workspace_id = azurerm_log_analytics_workspace.law.id
storage_endpoint = azurerm_storage_account.sa.primary_blob_endpoint
retention_in_days = 0
audit_actions_and_groups = [
"SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP",
"FAILED_DATABASE_AUTHENTICATION_GROUP",
"SCHEMA_OBJECT_CHANGE_GROUP",
"DATABASE_OBJECT_ACCESS_GROUP"
]
}
resource "azurerm_mssql_server_security_alert_policy" "defender" {
resource_group_name = azurerm_resource_group.rg.name
server_name = azurerm_mssql_server.this.name
state = "Enabled"
email_account_admins = true
email_addresses = ["[[email protected]](mailto:[email protected])"]
}
ケーススタディに基づく運用のコツ
- Premium vCore の利点を活かす:監査・Defender のオーバーヘッドは小さいが、ピーク時に影響が出ないよう IOPS/ログ出力帯域 を余裕持って確保。
- テナント分離:医療機関/部門ごとに接続主体(Group Managed Identity)を分け、監査時のフィルタリングとブロックを精緻化。
- ダッシュボードは“問い”から:「誰が、勤務時間外に、どの PHI テーブルをどれだけ閲覧したか」を 1 画面で把握できる設計にする。
まとめ
監査は「サーバー全体→必要に応じて DB 上書き」の二層で漏れを防ぎ、Defender for SQL の機械学習検出で未知の脅威を捕捉します。Log Analytics での可視化と Sentinel の自動化を組み合わせ、WORM による改ざん防止・長期保管を徹底すれば、HIPAA/HITECH の技術的保護要件に適合した監視・対応基盤が完成します。SELECT のパラメータ値を補うためのアプリ層ログや RLS を取り入れ、鍵管理(CMK)とネットワーク秘匿化(Private Link)を含む多層防御を実装すれば、EHR の現場でも実運用に耐えるセキュリティ態勢を維持できます。

コメント