Row-Level Security (RLS) 導入分析
マルチテナント分離における Row Level Security の適用方針と EF Core での実装案。
概要
このドキュメントでは、EcAuth IdentityProviderにおけるSQL Server Row-Level Security (RLS) の導入可否について分析する。
現在のマルチテナント実装
アーキテクチャ
HTTP Request
↓
TenantMiddleware (ホスト名からテナント特定)
↓
ITenantService.SetTenant(tenantName)
↓
EcAuthDbContext (EF Core グローバルクエリフィルター適用)
↓
SQL Server
実装詳細
TenantMiddleware
リクエストのホスト名からテナント名を抽出し、ITenantServiceに設定する。
// Middlewares/TenantMiddleware.cs
public async Task InvokeAsync(HttpContext context, ITenantService tenantService)
{
var host = context.Request.Host.Host;
var tenantName = ExtractTenantNameFromHost(host);
tenantService.SetTenant(finalTenantName);
await _next(context);
}EF Core グローバルクエリフィルター
EcAuthDbContextで各エンティティにテナントフィルターを適用。
// Models/EcAuthDbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Organization>()
.HasQueryFilter(o => o.TenantName == _tenantService.TenantName);
modelBuilder.Entity<EcAuthUser>()
.HasQueryFilter(u => u.Organization != null &&
u.Organization.TenantName == _tenantService.TenantName);
modelBuilder.Entity<ExternalIdpMapping>()
.HasQueryFilter(m => m.EcAuthUser != null &&
m.EcAuthUser.Organization != null &&
m.EcAuthUser.Organization.TenantName == _tenantService.TenantName);
modelBuilder.Entity<AuthorizationCode>()
.HasQueryFilter(ac => ac.EcAuthUser != null &&
ac.EcAuthUser.Organization != null &&
ac.EcAuthUser.Organization.TenantName == _tenantService.TenantName);
modelBuilder.Entity<AccessToken>()
.HasQueryFilter(at => at.EcAuthUser != null &&
at.EcAuthUser.Organization != null &&
at.EcAuthUser.Organization.TenantName == _tenantService.TenantName);
modelBuilder.Entity<ExternalIdpToken>()
.HasQueryFilter(eit => eit.EcAuthUser != null &&
eit.EcAuthUser.Organization != null &&
eit.EcAuthUser.Organization.TenantName == _tenantService.TenantName);
}対象エンティティ(6テーブル)
| エンティティ | フィルター方式 |
|---|---|
| Organization | 直接TenantNameでフィルター |
| EcAuthUser | Organization経由でフィルター |
| ExternalIdpMapping | EcAuthUser.Organization経由 |
| AuthorizationCode | EcAuthUser.Organization経由 |
| AccessToken | EcAuthUser.Organization経由 |
| ExternalIdpToken | EcAuthUser.Organization経由 |
SQL Server RLS の概要
Row-Level Security とは
SQL Server 2016で導入されたデータベース層でのセキュリティ機能。テーブルへのアクセス時に自動的にフィルター述語が適用され、ユーザーがアクセス可能な行のみを返す。
主要コンポーネント
- フィルター述語関数: アクセス可能な行を判定するインライン テーブル値関数
- セキュリティポリシー: テーブルとフィルター述語関数を紐付ける
- SESSION_CONTEXT: 接続ごとのコンテキスト情報(テナント識別子等)を保持
RLS 導入パターン
データベース層の実装
-- スキーマ作成
CREATE SCHEMA Security;
GO
-- フィルター述語関数
CREATE FUNCTION Security.fn_TenantFilter(@TenantName NVARCHAR(255))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
SELECT 1 AS result
WHERE @TenantName = CAST(SESSION_CONTEXT(N'TenantName') AS NVARCHAR(255));
GO
-- セキュリティポリシー(organizationテーブル)
CREATE SECURITY POLICY Security.OrganizationPolicy
ADD FILTER PREDICATE Security.fn_TenantFilter(tenant_name) ON dbo.organization
WITH (STATE = ON);
GO
-- セキュリティポリシー(ec_auth_userテーブル - organization_idを経由)
CREATE FUNCTION Security.fn_UserTenantFilter(@OrganizationId INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
SELECT 1 AS result
WHERE EXISTS (
SELECT 1 FROM dbo.organization o
WHERE o.id = @OrganizationId
AND o.tenant_name = CAST(SESSION_CONTEXT(N'TenantName') AS NVARCHAR(255))
);
GO
CREATE SECURITY POLICY Security.EcAuthUserPolicy
ADD FILTER PREDICATE Security.fn_UserTenantFilter(organization_id) ON dbo.ec_auth_user
WITH (STATE = ON);
GOEF Core側の実装
// DbConnectionInterceptorでSESSION_CONTEXTを設定
public class TenantSessionInterceptor : DbConnectionInterceptor
{
private readonly ITenantService _tenantService;
public TenantSessionInterceptor(ITenantService tenantService)
{
_tenantService = tenantService;
}
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(
DbConnection connection,
ConnectionEventData eventData,
InterceptionResult result,
CancellationToken cancellationToken = default)
{
return result;
}
public override async Task ConnectionOpenedAsync(
DbConnection connection,
ConnectionOpenedEventData eventData,
CancellationToken cancellationToken = default)
{
if (!string.IsNullOrEmpty(_tenantService.TenantName))
{
using var command = connection.CreateCommand();
command.CommandText = "EXEC sp_set_session_context @key=N'TenantName', @value=@tenant";
var param = command.CreateParameter();
param.ParameterName = "@tenant";
param.Value = _tenantService.TenantName;
command.Parameters.Add(param);
await command.ExecuteNonQueryAsync(cancellationToken);
}
}
}
// Program.cs での設定
builder.Services.AddDbContext<EcAuthDbContext>((sp, options) =>
{
var tenantService = sp.GetRequiredService<ITenantService>();
options.UseSqlServer(connectionString)
.AddInterceptors(new TenantSessionInterceptor(tenantService));
});比較分析
セキュリティ特性
| 観点 | EF Core グローバルクエリフィルター | SQL Server RLS |
|---|---|---|
| 強制レベル | アプリケーション層 | データベース層 |
IgnoreQueryFilters()でのバイパス |
可能 | 不可 |
| 直接SQL実行時の分離 | なし | あり |
| SSMSからの直接アクセス | フィルターなし | フィルター適用 |
| ADO.NET直接使用時 | フィルターなし | フィルター適用 |
パフォーマンス
| 観点 | EF Core グローバルクエリフィルター | SQL Server RLS |
|---|---|---|
| クエリ最適化 | EF Coreが最適化 | SQL Serverが最適化 |
| インデックス活用 | 通常通り | 述語関数内でのJOINに注意 |
| 追加オーバーヘッド | 最小限 | SESSION_CONTEXT設定 + 述語評価 |
運用・保守性
| 観点 | EF Core グローバルクエリフィルター | SQL Server RLS |
|---|---|---|
| 実装複雑度 | 低(C#コードのみ) | 高(SQL + C#) |
| マイグレーション | EF Core標準 | セキュリティポリシーの別管理必要 |
| テスト容易性 | モック可能 | テスト環境での設定必要 |
| デバッグ | Visual Studioで完結 | SQL Profiler等も必要 |
導入判断
RLSを導入すべきケース
コンプライアンス要件
- 監査でデータベース層での分離証明が必要
- 業界規制(金融、医療等)でDB層セキュリティが必須
運用要件
- DBA/開発者がSSMSから本番DBに直接アクセスする運用
- 複数チームや外部委託がDB直接操作を行う
アプリケーション複雑度
- 大規模コードベースで
IgnoreQueryFilters()の誤用リスクが高い - 複数アプリケーションが同一DBを共有
- 大規模コードベースで
EcAuthでの結論: 現時点では不要
理由
直接DB操作の限定性
- 本番環境ではAPI経由のアクセスのみ
- DB直接操作は開発・緊急時のメンテナンスに限定
コードベースの規模
- 小規模なコードベース
IgnoreQueryFilters()の使用箇所を容易に把握可能- コードレビューでの漏れリスクが低い
個人情報最小化設計
- EcAuthは個人情報を最小限しか保持しない(ハッシュ化メールアドレス、UUID)
- 万が一のテナント間漏洩でも影響が限定的
現在の実装の十分性
- EF Coreグローバルクエリフィルターで全エンティティに適用済み
- OrganizationFilterによるリクエスト単位の検証も実施
実装・保守コスト
- RLS導入による複雑度増加に見合うメリットがない
- マイグレーション管理の複雑化を避けられる
将来の再検討トリガー
以下の状況が発生した場合、RLS導入を再検討する:
規模拡大
- 開発チームの増加(外部委託含む)
- 複数アプリケーションからの同一DB接続
コンプライアンス要件
- SOC 2、ISO 27001等の認証取得
- 金融機関向けサービス展開
運用変更
- DBAによる本番DB直接操作の定常化
- BIツール等からの直接DB接続
セキュリティインシデント
- アプリケーションバグによるテナント間データ漏洩
参考資料
更新履歴
| 日付 | 内容 |
|---|---|
| 2025-12-01 | 初版作成 |