0Pricing
SQL Academy · レッスン

アクセスを監査する

誰が何を閲覧できるかを追跡します。

「アクセスを監査する」はCoddyKit上の無料SQL Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSQL Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 SQL Academyコースには全4レッスンが含まれています。

アクセスを監査する理由

誰が、どのデータに、いつアクセスしたかを把握することは、データベースセキュリティの基本です。監査によってイベントの信頼できる記録が作成されるため、不正アクセスの検出、インシデントの調査、GDPR、HIPAA、SOC 2 などのコンプライアンス要件への対応が可能になります。

このレッスンでは、監査テーブルの設計、トリガーによるアクセスイベントの自動記録、PostgreSQL の組み込みログ機能の使用、監査記録のクエリによる「誰が何を見られるのか」という問いへの回答方法を学びます。

監査ログテーブルの設計

最初の手順は、重要なイベントをすべて記録する専用テーブルを用意することです。適切な監査ログには、テーブル名、操作の種類、変更前と変更後の値、操作を実行したユーザー、正確なタイムスタンプを保存します。

以下の例では、行のスナップショットを保存するために JSONB 列を使用した汎用的な audit_log テーブルを作成します。スキーマを変更せずに、任意のテーブルに対応できます。

CREATE TABLE audit_log (
  id          BIGSERIAL PRIMARY KEY,
  event_time  TIMESTAMPTZ NOT NULL DEFAULT now(),
  db_user     TEXT NOT NULL DEFAULT current_user,
  app_user    TEXT,
  table_name  TEXT NOT NULL,
  operation   TEXT NOT NULL CHECK (operation IN ('INSERT','UPDATE','DELETE','SELECT')),
  row_id      BIGINT,
  old_data    JSONB,
  new_data    JSONB
);

現在のユーザーの記録

PostgreSQL には、クエリを実行しているユーザーを識別する組み込み関数がいくつか用意されています。current_user は、SET ROLE の実行後に有効なロール名を返します。session_user は、ロールを切り替えた場合でも、常に最初にログインしたロールを返します。

単一の共有 DB ロールを使用しつつ、SET LOCAL app.current_user でアプリケーションレベルのユーザーを渡すアプリケーションでは、current_setting() を使ってその設定値を読み取れます。

-- Who is the database user right now?
SELECT current_user,
       session_user;

-- Read an application-level user injected by the app layer
SELECT current_setting('app.current_user', true) AS app_user;

監査トリガー関数の作成

トリガー関数は、データ変更イベントを記録する最も信頼性の高い方法です。自動的に実行されるため、アプリケーションコードから回避できません。以下の関数は、これを設定した任意のテーブルで発生するすべての INSERT、UPDATE、DELETE を記録し、変更前と変更後の行の値を JSONB として保存します。

TG_TABLE_NAME(トリガーを起動したテーブル)と、行の値を保存可能な形式に変換する row_to_json() の使用に注目してください。

CREATE OR REPLACE FUNCTION fn_audit_changes()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
  INSERT INTO audit_log (
    db_user,
    app_user,
    table_name,
    operation,
    row_id,
    old_data,
    new_data
  ) VALUES (
    current_user,
    current_setting('app.current_user', true),
    TG_TABLE_NAME,
    TG_OP,
    COALESCE(NEW.id, OLD.id),
    CASE WHEN TG_OP = 'INSERT' THEN NULL ELSE row_to_json(OLD)::JSONB END,
    CASE WHEN TG_OP = 'DELETE' THEN NULL ELSE row_to_json(NEW)::JSONB END
  );
  RETURN NULL;
END;
$$;

テーブルへのトリガーの設定

トリガー関数を作成したら、監査対象にする各テーブルに CREATE TRIGGER 文で設定します。AFTER を使用すると、ログエントリが作成される前にデータが実際に書き込まれたことを確認できます。FOR EACH ROW 句を指定すると、変更された行ごとに1回ずつトリガーが実行されます。

ここでは、仮想的な patients テーブルにトリガーを設定し、すべての INSERT、UPDATE、DELETE を自動的に記録します。

CREATE TRIGGER trg_audit_patients
AFTER INSERT OR UPDATE OR DELETE
ON patients
FOR EACH ROW
EXECUTE FUNCTION fn_audit_changes();

SELECT クエリの監査

データ変更トリガーで記録できるのは書き込みだけです。読み取りアクセスを監査するには、別の方法が必要です。方法の1つは、(特定のコンテキストで PostgreSQL 14 以降がサポートする)AFTER SELECT のステートメントレベルのトリガーです。より一般的なパターンは、機密テーブルをラップする関数またはビューの中で、読み取りを明示的に記録する方法です。

以下の例では、機密テーブルを関数でラップし、結果を返す前にすべての読み取りを記録します。

CREATE OR REPLACE FUNCTION get_patient_record(p_id INT)
RETURNS SETOF patients
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
  -- Log the read access
  INSERT INTO audit_log (db_user, app_user, table_name, operation, row_id)
  VALUES (
    current_user,
    current_setting('app.current_user', true),
    'patients',
    'SELECT',
    p_id
  );

  RETURN QUERY
  SELECT * FROM patients WHERE id = p_id;
END;
$$;

PostgreSQL の組み込みログ機能

PostgreSQL の postgresql.conf には、アプリケーションコードを必要としない強力なサーバー側ログ機能があります。log_min_duration_statement を設定すると、指定したしきい値を超えるクエリが記録されます。log_connections と log_disconnections を設定すると、ログインとログアウトを行ったユーザーが記録されます。

以下のクエリでは、pg_stat_activity システムビューを使用して、現在アクティブなセッションを確認します。これは、軽量なリアルタイムアクセス監視の方法です。

-- See who is currently connected and what they are running
SELECT pid,
       usename        AS db_user,
       application_name,
       client_addr,
       state,
       query_start,
       LEFT(query, 80) AS current_query
FROM   pg_stat_activity
WHERE  datname = current_database()
ORDER  BY query_start DESC;

監査ログの検索

監査ログは効果的に検索できて初めて価値があります。よくある質問には、直近でレコードにアクセスしたユーザーは誰か、時間の経過とともに行の内容がどのように変化したか、過去24時間に機密データの読み取りが何回行われたか、などがあります。

以下のクエリは、特定の患者レコードにアクセスしたすべてのユーザーを、最新のアクセスが先頭になるように並べて取得します。

SELECT event_time,
       db_user,
       app_user,
       operation,
       old_data,
       new_data
FROM   audit_log
WHERE  table_name = 'patients'
  AND  row_id = 42
ORDER  BY event_time DESC
LIMIT  20;

不審なアクセスパターンの検出

監査データを収集したら、異常を検出するクエリを記述できます。たとえば、あるユーザーが突然、通常よりはるかに多くの行を読み取った場合や、同じ機密レコードに短時間で複数回アクセスした場合は、データ持ち出しの試みを示している可能性があります。

以下のクエリは、直近1時間のアプリケーションユーザーごとのSELECTイベント数を数え、100行を超えて読み取ったユーザーを強調します。

SELECT app_user,
       COUNT(*) AS records_accessed
FROM   audit_log
WHERE  operation = 'SELECT'
  AND  event_time >= now() - INTERVAL '1 hour'
GROUP  BY app_user
HAVING COUNT(*) > 100
ORDER  BY records_accessed DESC;

監査ログ自体の保護

改変可能な監査ログは信頼できません。通常のユーザーやアプリケーションロールが行を削除または更新できないよう、アクセスを制限する必要があります。最も安全な方法は、アプリケーションロールにはINSERTのみを付与し、SELECTは専用の監査担当ロールに限定することです。

トリガーを使用して不変性を強制し、監査行を更新または削除しようとした場合に例外を発生させることもできます。

-- Only the app role may insert; nobody may update or delete
REVOKE ALL     ON audit_log FROM PUBLIC;
GRANT  INSERT  ON audit_log TO app_role;
GRANT  SELECT  ON audit_log TO auditor_role;

-- Trigger to block any tampering
CREATE OR REPLACE FUNCTION fn_protect_audit()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  RAISE EXCEPTION 'audit_log rows are immutable';
  RETURN NULL;
END;
$$;

CREATE TRIGGER trg_protect_audit
BEFORE UPDATE OR DELETE ON audit_log
FOR EACH ROW EXECUTE FUNCTION fn_protect_audit();

RLSポリシーによる判定の監査

行レベルセキュリティ(RLS)が有効な場合、PostgreSQLはエラーを発生させずに行を黙って非表示にします。そのため、ユーザーが閲覧を許可されていない行を読み取ろうとしたかどうかを把握するのは困難です。1つの方法として、制限のあるポリシーが行をフィルタリングする前に、常に監査レコードを挿入する許可型ポリシーを追加します。

以下のクエリは、テーブルに存在するRLSポリシーと、それらが適用されるロールを調べる方法を示します。

-- View all RLS policies on the patients table
SELECT polname       AS policy_name,
       polcmd        AS command,
       polroles::TEXT AS applies_to,
       polqual::TEXT  AS using_expression,
       polwithcheck::TEXT AS with_check_expression
FROM   pg_policy
WHERE  polrelid = 'patients'::REGCLASS
ORDER  BY polname;

理解度チェック

SQLにおけるアクセス監査の理解度を確認しましょう。

レッスンのまとめ

このレッスンでは、PostgreSQLで完全なアクセス監査システムを構築する方法を学びました。

  • 監査ログテーブル — 誰が何をいつ行ったかを記録する、JSONBベースのテーブルです。
  • トリガー関数 — TG_TABLE_NAME、row_to_json()、current_userを使用して、関連付けられたテーブルのINSERT、UPDATE、DELETEイベントを自動的に記録します。
  • 読み取りの監査 — 機密テーブルを、データを返す前にSELECTイベントを記録する関数でラップします。
  • 組み込みの監視 — pg_stat_activityは実行中のセッションを表示し、サーバー側のログ記録はコードを変更せずにクエリを記録します。
  • 異常検出 — 監査ログに対する集計クエリによって、通常とは異なるアクセス量を検出できます。
  • 不変性 — 監査テーブルに対するUPDATE/DELETEを取り消し、改変を防ぐブロッキングトリガーを追加します。

適切に設計された監査証跡は、誰が何にアクセスしたかを確認するための最も信頼できる手段であり、コンプライアンス調査やセキュリティ調査の基盤となります。

よくある質問

「アクセスを監査する」レッスンは無料ですか?

はい。「アクセスを監査する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、SQL Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 SQL Academyコースには全4レッスンが含まれています。

「アクセスを監査する」で何を学びますか?

誰が何を閲覧できるかを追跡します。 ブラウザで直接実行するハンズオンコードでSQL Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

SQL Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSQL Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「アクセスを監査する」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSQL Academyレッスンでコードを書いて実行できますか?

はい。すべてのSQL Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ロールと権限
  2. 行レベルセキュリティポリシー
  3. 列レベルの権限
  4. アクセスを監査する
← SQL Academyに戻る