SQL Serverでスキーマ単位に「ビューだけ」を見せる権限設計|DENY不要・所有権チェーン活用ガイド

アプリのエンドユーザー TM1_User に、アプリ用スキーマ TM1App のビューだけを見せ、ソース側の TMS1 テーブルやビューは直接触らせたくない——この要件は SQL Server/Azure SQL でも頻出です。ありがちな「TMS1 に DENY」を選ぶと、依存ビュー経由の参照が壊れることがあります。この記事では DENY を使わず、所有権チェーンを活かしてスキーマ単位で確実に閲覧権限を設計・実装・検証する方法を詳説します。

目次

シナリオの整理

データベース TM1 には次のオブジェクトが存在します。

  • TMS1 スキーマ … テーブル TMS1.Table1、ビュー TMS1.View1 ~ View5
  • TM1App スキーマ … ビュー TM1App.View6 ~ View10(中身は TMS1.View* を参照)

エンドユーザー用ログイン TM1_User には TM1App 内のオブジェクトだけを「読める」ようにし、TMS1 のオブジェクトは直接読ませないのがゴールです。

結論(先に全体像)

核心はシンプルです。

  1. DENY を使わない。 DENY は最優先で評価されるため、設定場所によってはビュー経由の参照すらブロックします。
  2. ユーザーはデータベースロール public のみ。 便利だからと db_datareader を付けると全テーブルが読めてしまいます。
  3. TM1App スキーマにだけ SELECT を GRANT。 スキーマ単位の GRANT で既存・将来のオブジェクトを一括許可します。
  4. TMS1 には GRANT も DENY もしない。 同一所有者(通常 dbo)なら所有権チェーンが効き、TM1App.View6 → TMS1.View1 → TMS1.Table1 の参照に追加権限は不要です。

なぜ DENY は使わないのか

SQL Server は「許可より拒否が強い」評価規則を持ちます。特に以下のケースは要注意です。

  • スキーマ単位の DENY(例:DENY SELECT ON SCHEMA::TMS1 TO TM1_User) … 所有者が異なると所有権チェーンが切れ、下位オブジェクトに対して個別チェックが走るため、DENY が効いてビュー経由も失敗します。
  • db_denydatareader への追加 … データベース全体の SELECT を否認するため、ビュー自体の SELECT も否認されます。

逆に、同一所有者かつ 所有権チェーンが有効な場合、下位のテーブル・ビューへの個別チェックはスキップされるため、下位オブジェクトに設定した DENY は参照されません。ただし、スキーマ所有者が異なるとチェーンが切れるため、そこで DENY が効果を持ってしまう点が落とし穴です。

実装手順(そのまま使えるスクリプト付き)

前提:所有者(オーナー)をそろえる

所有権チェーンは「同一データベース内のオブジェクトで所有者が一致」していることが条件です。まずは所有者が一致しているか確認します。

-- 所有者の確認
SELECT s.name AS schema_name, dp.name AS owner
FROM sys.schemas s
JOIN sys.database_principals dp ON s.principal_id = dp.principal_id
WHERE s.name IN ('TMS1','TM1App');

もし TMS1 と TM1App の所有者が違えば、以下で合わせます(例:両方とも dbo に統一)。

ALTER AUTHORIZATION ON SCHEMA::TMS1  TO dbo;
ALTER AUTHORIZATION ON SCHEMA::TM1App TO dbo;

1) ログインとユーザーの作成(オンプレミス SQL Server)

USE [master];
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = 'TM1Login')
    CREATE LOGIN TM1Login WITH PASSWORD = 'StrongPw123!', CHECK_POLICY = ON;

USE [TM1];
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = 'TM1_User')
CREATE USER TM1_User FOR LOGIN TM1Login;
-- ロールは既定の public のまま(余計なロールは付けない) 

(参考)Azure SQL Database / Contained DB の場合

-- マスターではなくターゲット DB 上で実施
USE [TM1];
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = 'TM1_User')
    CREATE USER TM1_User WITH PASSWORD = 'StrongPw123!';  -- Contained User

2) TM1App スキーマにだけ SELECT を付与

GRANT SELECT ON SCHEMA::TM1App TO TM1_User;

これで TM1App に存在するビュー(既存・将来)をすべて読めるようになります。追加の作業は不要です。

3) TMS1 には付与・否認を一切しない

ここが設計のキモです。TMS1 には GRANT も DENY もしません。同一所有者であれば、TM1App のビューから参照する際に所有権チェーンが働き、下位の TMS1 に対する権限チェック自体がスキップされます。

動作検証(実行例)

ユーザーとして動作させ、期待どおりか確認しましょう。

USE [TM1];
-- ユーザーの権限確認(一覧)
SELECT * FROM fn_my_permissions(NULL, 'DATABASE') WHERE subentity_name = '';
SELECT * FROM fn_my_permissions('TM1App', 'SCHEMA');  -- スキーマ単位の権限
GO

-- ユーザーに切替(オンプレミス)
EXECUTE AS USER = 'TM1_User';

-- 期待どおり:TM1App のビューは読める
SELECT TOP (10) * FROM TM1App.View6;

-- 期待どおり:TMS1 のテーブル/ビューは直接読めない
BEGIN TRY
SELECT TOP (1) * FROM TMS1.Table1;
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS number, ERROR_MESSAGE() AS message; -- 229 などのエラーを確認
END CATCH;

-- 期待どおり:TM1App.View6 経由で TMS1.Table1 のデータ参照は成功
SELECT COUNT(*) FROM TM1App.View6;

REVERT;  -- コンテキスト戻す 

メタデータの見せ方(VIEW 定義の秘匿)

メタデータ可視性は「アクセス権のあるオブジェクトのみ見える」のが既定動作です。したがって TM1_User には TM1App のオブジェクトだけが見え、TMS1 は一覧にも出てきません。さらにビューの定義を見せたくないなら、以下の方針を組み合わせます。

  • GRANT VIEW DEFINITION を付与しない(既定は権限なし)。
  • 必要に応じて DENY VIEW DEFINITION を TM1_User に与える(SELECT とは別系統なので安全)。
  • どうしてもソース非公開にしたい場合だけ WITH ENCRYPTION オプションを検討(復号が困難・運用上のデメリット有)。

チェックリスト(運用時につまずきやすい点)

チェック項目合格条件備考
スキーマ所有者TMS1 と TM1App が同一所有者(例:dbo)ALTER AUTHORIZATION で統一
ユーザーロールpublic のみdb_datareader や db_denydatareader は付けない
スキーマ権限GRANT SELECT ON SCHEMA::TM1App将来のオブジェクトも自動で許可
TMS1 への設定GRANT/DENY ともに未設定所有権チェーンの邪魔をしない
メタデータ公開VIEW DEFINITION を付与しない必要なら DENY VIEW DEFINITION

補足:所有権チェーンの要点

  • 成立条件 … 同一データベース内で、呼び出し元(ビュー/ストアド)と参照先(テーブル/ビュー/関数)の所有者が一致。
  • 効果 … 参照先の個別権限チェックをスキップ(=GRANT/ DENY を見に行かない)。
  • 途切れる場所 … 所有者が異なる/動的 SQL/一部の権限昇格が伴う操作/データベース境界。
  • データベースをまたぐ参照 … 既定ではチェーンしません。必要なら署名付きモジュール等の別設計を検討。

ベストプラクティス(セキュリティ設計)

  • 最小権限原則(Least Privilege) … エンドユーザーには public のまま、TM1App の SELECT だけを許可。
  • 読み取りはビュー経由に限定 … 業務ロジックや列/行のマスキング、連結、正規化解除はすべて TM1App 側のビューに集約。
  • 列・行の絞り込み … 列はビューで、行は必要に応じて Row-Level Security (RLS) を併用。
  • スキーマ分離 … TMS1 はデータ保管、TM1App は公開インターフェース。責務を分けると保守しやすい。
  • メタデータ監査 … 定期的に sys.database_permissions、sys.objects を点検し、意図しない GRANT を排除。

権限の可視化・監査クエリ集

-- スキーマ単位の権限一覧
SELECT
    pr.principal_id,
    pr.name        AS principal_name,
    pe.class_desc,
    SCHEMA_NAME(pe.major_id) AS schema_name,
    pe.permission_name,
    pe.state_desc
FROM sys.database_permissions pe
JOIN sys.database_principals  pr ON pe.grantee_principal_id = pr.principal_id
WHERE pe.class_desc = 'SCHEMA'
  AND SCHEMA_NAME(pe.major_id) IN ('TM1App','TMS1')
ORDER BY principal_name, schema_name;

-- オブジェクト単位(ビュー/テーブル)で TM1_User に付与された SELECT を確認
SELECT
o.schema_id, SCHEMA_NAME(o.schema_id) AS schema_name,
o.name AS object_name, dp.permission_name, dp.state_desc
FROM sys.database_permissions dp
JOIN sys.objects o ON dp.major_id = o.object_id
JOIN sys.database_principals u ON dp.grantee_principal_id = u.principal_id
WHERE u.name = 'TM1_User' AND dp.permission_name = 'SELECT';

-- TM1_User から見えるオブジェクト(メタデータ可視性)
EXECUTE AS USER = 'TM1_User';
SELECT schema_name = SCHEMA_NAME(o.schema_id), o.name, o.type_desc
FROM sys.objects o
WHERE SCHEMA_NAME(o.schema_id) IN ('TM1App','TMS1')
ORDER BY schema_name, o.name;
REVERT; 

トラブルシューティング

「ビュ―経由の SELECT が失敗する」「TMS1 も見えてしまう」といった場合は、次の順で切り分けます。

  1. 所有者不一致の確認 … 前述のクエリで TMS1 と TM1App の所有者が一致しているか。
  2. 余計なロール付与の有無 … db_datareader や db_denydatareader に所属していないか。
  3. 不要な DENY の有無 … TMS1 やデータベーススコープに DENY SELECT が入っていないか。
  4. ビュー定義の内容 … 動的 SQL を使っていないか。別 DB、別サーバーの参照が無いか。
  5. 権限の伝播経路 … 同名スキーマでも所有者が異なる DB からの参照は所有権チェーンが効きません。

やってはいけない NG パターン

  • GRANT SELECT ON DATABASE::TM1 TO TM1_User; … データベース全体の SELECT を許可してしまいます。
  • EXEC sp_addrolemember 'db_datareader','TM1_User'; … すべてのユーザーテーブル/ビューが読めてしまいます。
  • DENY SELECT ON SCHEMA::TMS1 を安易に設定 … 所有者差異があるとビュー経由も失敗します。
  • シノニム経由の公開 … シノニムには権限が付かず、基底オブジェクトの権限が必要です。公開インターフェースは必ずビューで。

将来拡張:運用設計のコツ

  • リリース手順の固定化 … 新しい公開ビューは TM1App に作成し、所有者が dbo であることを CI などで検証。
  • テストユーザーの常設 … TM1_User を本番・検証環境ともに保持し、毎リリース時に自動テストで SELECT 動作を確認。
  • マスキング … 個人情報など秘匿列は TM1App 側で計算列・CASE 式・マスキング関数等により露出しない設計に。
  • 監査 … 監査ログ(SQL Audit 等)で TM1_User のデータアクセスを記録し、異常検知へ。

一括セットアップ & 検証スクリプト(テンプレート)

以下は記事の要点をまとめたテンプレートです。環境に合わせて名称を変更してください。

/* 0. スキーマ所有者の統一 */
ALTER AUTHORIZATION ON SCHEMA::TMS1   TO dbo;
ALTER AUTHORIZATION ON SCHEMA::TM1App TO dbo;

/* 1. ログイン/ユーザー(オンプレ) */
IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = 'TM1Login')
CREATE LOGIN TM1Login WITH PASSWORD = 'StrongPw123!', CHECK_POLICY = ON;
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = 'TM1_User')
CREATE USER TM1_User FOR LOGIN TM1Login;

/* 2. TM1App スキーマにだけ SELECT を付与 */
GRANT SELECT ON SCHEMA::TM1App TO TM1_User;

/* 3. TMS1 には何もしない(GRANT/DENY ともに設定しない) */

/* 4. 動作検証 */
EXECUTE AS USER = 'TM1_User';
SELECT TOP (1) * FROM TM1App.View6;  -- OK のはず

BEGIN TRY
SELECT TOP (1) * FROM TMS1.Table1;  -- 失敗するはず
END TRY
BEGIN CATCH
SELECT ERROR_NUMBER() AS number, ERROR_MESSAGE() AS message;
END CATCH;

REVERT; 

参考となる設計判断の比較(表)

選択肢長所短所/リスクおすすめ度
本記事の方式(TM1App にだけ SELECT、TMS1 は無設定、所有者統一)保守が容易。将来の追加にも自動対応。意図しない広がりがない。所有者がずれると壊れる。クロス DB には非対応。★★★★★
db_datareader を付与即効で動く全テーブル/ビューが読める=要件を満たせない★☆☆☆☆
TMS1 に DENY見た目は安全そう所有者差異でチェーンが切れ、依存ビューも読めなくなる★☆☆☆☆
オブジェクト個別 GRANT粒度が細かい運用が煩雑。将来追加のたびに付与が必要★★☆☆☆

補足情報(要点サマリ)

補足ポイント内容
所有権チェーン同一 DB 内でオブジェクト所有者が一致すると、下位オブジェクトに対する個別の権限チェックをスキップできる。スキーマの所有者が異なる場合は ALTER AUTHORIZATION ON SCHEMA::TMS1 TO dbo; などでそろえる。
VIEW 定義の隠蔽オブジェクト一覧や定義を見せたくない場合は GRANT VIEW DEFINITION を与えない/必要なら DENY VIEW DEFINITION を使う。
メリット将来 TM1App にビューを追加しても自動で参照可能。余計な権限を与えないぶんセキュリティが単純化。
デメリットTMS1 と TM1App の所有者が異なる場合は動かない。別スキーマの個別テーブルを直接読ませたい場合は追加 GRANT が必要。

まとめ

「DENY を使わず、TM1App だけに SELECT を GRANT、TMS1 は無設定」という設計は、所有権チェーンに基づく SQL Server 本来の権限モデルを活かし、最小権限・低運用コスト・将来拡張に強いアプローチです。実運用では所有者の統一と不要なロール付与の排除だけを徹底すれば、TM1_User は TM1App のビューだけを安全に参照し、TMS1 のオブジェクトには直接アクセスできません。迷ったら本記事のテンプレートをベースに、まずは検証環境で「作る→付ける→試す→見る」を繰り返し、シンプルな権限設計を体得してください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次