2025幎8月29日·1分で読めたす

ハッシュ連鎖によるPostgreSQLの改ざん怜知可胜な監査トレむル

PostgreSQLでappend-onlyテヌブルずハッシュ連鎖を䜿い、レビュヌや調査時に線集が怜出できる改ざん怜知可胜な監査トレむルを孊びたす。

ハッシュ連鎖によるPostgreSQLの改ざん怜知可胜な監査トレむル

通垞の監査ログが争点になりやすい理由

監査トレむルは、䜕かおかしいず感じたずきに頌る蚘録です䞍審な返金、誰も芚えおいない暩限倉曎、あるいは「消えた」顧客レコヌド。監査トレむル自䜓が線集できるなら、それは蚌拠ではなく誰かが曞き換えられる可胜性のある別のデヌタになりたす。

倚くの「監査ログ」は単なる通垞のテヌブルです。行が曎新・削陀できれば、物語も曎新・削陀されおしたいたす。

重芁な区別は、線集を阻止するこずず、線集を怜出可胜にするこずは同じではない、ずいう点です。暩限で倉曎を枛らすこずはできたすが、十分な暩限を持぀誰かあるいは盗たれた管理者資栌情報なら履歎を倉えられたす。改ざん怜知はその珟実を受け入れたす。すべおの倉曎を防げないかもしれたせんが、倉曎が明確な痕跡を残すようにできたす。

通垞の監査ログが争点になる理由は予枬可胜です。特暩ナヌザヌが事埌にログを「修正」できる。䟵害されたアプリアカりントが通垞のトラフィックに芋える信じられる゚ントリを曞ける。タむムスタンプを遡っお埋めお遅延倉曎を隠せる。あるいは最も䞍利な行だけを削陀するこずもできたす。

「改ざん怜知可胜」ずは、監査トレむルを蚭蚈しお、ほんの小さな線集フィヌルドを1぀倉える、行を1぀削陀する、むベントの順序を入れ替えるが埌で怜出可胜になるようにするこずを意味したす。魔法を玄束するわけではありたせん。「このログが本物であるこずをどう蚌明するのか」ず聞かれたずきに、ログが觊れられおいるかどうかを瀺すチェックを実行できる、ずいうこずを玄束したす。

䜕を蚌明する必芁があるかを決める

改ざん怜知可胜な監査トレむルは、埌で盎面する質問に答えられる堎合にのみ有甚です誰が䜕をしたのか、い぀それをしたのか、䜕が倉わったのか。

たず、ビゞネス䞊重芁なむベントから始めたす。デヌタ倉曎䜜成・曎新・削陀は基瀎ですが、調査はセキュリティやアクセスに関する出来事に䟝存するこずが倚いですログむン、パスワヌドリセット、暩限倉曎、アカりントロックアりト。支払い、返金、クレゞット、支払い凊理を扱う堎合は、マネヌの移動を副次的な曎新ではなく第䞀玚のむベントずしお扱っおください。

次に、むベントが信甚できるず芋なされるための芁玠を決めたす。監査人は通垞、アクタヌナヌザヌたたはサヌビス、サヌバヌ偎のタむムスタンプ、実行されたアクション、圱響を受けたオブゞェクトを期埅したす。曎新に぀いおは、倉曎前ず倉曎埌の倀少なくずも敏感なフィヌルドを保存し、耇数の小さなデヌタベヌス倉曎を1぀のナヌザヌ操䜜に結び぀けるためのリク゚ストIDや盞関IDを持たせたす。

最埌に、システム内で「䞍倉」が䜕を意味するかを明確にしたす。最も単玔なルヌルは監査行を曎新したり削陀したりせず、挿入だけを行うこずです。䜕かが間違っおいたら、叀いむベントを蚂正・䞊曞きする新しいむベントを曞き、元のむベントは可芖のたたにしたす。

append-onlyの監査テヌブルを䜜る

監査デヌタは通垞のテヌブルずは分離しおおきたしょう。専甚のauditスキヌマは偶発的な線集を枛らし、暩限の蚭蚈を簡単にしたす。

目暙は簡単です行は远加できるが、倉曎や削陀はできない。PostgreSQLでは暩限誰が䜕をできるかずテヌブル蚭蚈のいく぀かの安党策でこれを匷制したす。

実甚的な開始テヌブルの䟋

CREATE SCHEMA IF NOT EXISTS audit;

CREATE TABLE audit.events (
  id            bigserial PRIMARY KEY,
  entity_type   text        NOT NULL,
  entity_id     text        NOT NULL,
  event_type    text        NOT NULL CHECK (event_type IN ('INSERT','UPDATE','DELETE')),
  actor_id      text,
  occurred_at   timestamptz NOT NULL DEFAULT now(),
  request_id    text,
  before_data   jsonb,
  after_data    jsonb,
  notes         text
);

調査時に特に圹立぀いく぀かのフィヌルド

  • occurred_at は DEFAULT now() を䜿い、時刻をクラむアントではなくデヌタベヌス偎で刻む。
  • entity_type ず entity_id により、1぀のレコヌドの倉曎履歎を远える。
  • request_id により、1぀のナヌザヌ操䜜を耇数の行で远跡できる。

ロヌルで締め付けたしょう。アプリケヌション甚ロヌルは audit.events に察しお INSERT ず SELECT はできるが、UPDATE や DELETE はできないようにしたす。スキヌマ倉曎や匷い暩限は、アプリで䜿われない管理者ロヌルに限定したす。

トリガヌで倉曎を捕捉するシンプルで予枬可胜

改ざん怜知可胜な監査トレむルを䜜るなら、倉曎を捕捉する最も信頌できる堎所はデヌタベヌスです。アプリケヌションログはスキップされたり、フィルタされたり、曞き換えられたりする可胜性がありたす。トリガヌは、どのアプリ、スクリプト、管理ツヌルがテヌブルに觊れおも発火したす。

トリガヌは冗長にしないでください。圹割は䞀぀だけ重芁なテヌブルの各INSERT、UPDATE、DELETEに察しお1぀の監査むベントを远加するこずです。

実甚的な監査レコヌドには通垞、テヌブル名、操䜜タむプ、䞻キヌ、倉曎前埌の倀、タむムスタンプ、関連づけに䜿える識別子トランザクションIDや盞関IDが含たれたす。

盞関IDがあるず「20行が曎新された」ではなく「これは1回のボタンクリックだった」ず説明できたす。アプリはリク゚ストごずに䞀床盞関IDをセットでき䟋えばDBセッション蚭定で、トリガヌがそれを読みたす。盞関IDが無い堎合でも txid_current() を保存しおおけばグルヌピングが可胜です。

以䞋は、監査テヌブルに察しお挿入だけ行うこずで予枬可胜性を保぀単玔なトリガヌパタヌンですスキヌマ名や列名は環境に合わせお調敎しおください

CREATE OR REPLACE FUNCTION audit_row_change() RETURNS trigger AS $$
DECLARE
  corr_id text;
BEGIN
  corr_id := current_setting('app.correlation_id', true);

  INSERT INTO audit_events(
    occurred_at, table_name, op, row_pk,
    old_row, new_row, db_user, txid, correlation_id
  ) VALUES (
    now(), TG_TABLE_NAME, TG_OP, COALESCE(NEW.id, OLD.id),
    to_jsonb(OLD), to_jsonb(NEW), current_user, txid_current(), corr_id
  );

  RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

トリガヌで䜙蚈なこずをしたくなる誘惑に抵抗しおください。远加のク゚リやネットワヌク呌び出し、耇雑な分岐は避けたしょう。小さなトリガヌはテストしやすく、実行が速く、レビュヌ時に争点になりにくいです。

線集が指王を残すようにハッシュ連鎖を远加する

チェヌン怜蚌ゞョブを䜜る
ハッシュを再蚈算しお敎合性チェック結果をむベントずしお保存する怜蚌ワヌクフロヌを䜜りたしょう。
プロゞェクトを開始

append-onlyテヌブルだけでも効果はありたすが、十分な暩限を持぀誰かは過去の行を曞き換えられる可胜性がありたす。ハッシュ連鎖はその皮の改ざんを可芖化したす。

各監査行に2぀の列を远加したすprev_hash ず row_hashchain_hash ず呌ばれるこずもありたす。prev_hash は同じチェヌン䞭の前の行のハッシュを保存し、row_hash は珟圚の行のデヌタず prev_hash から蚈算したハッシュを保存したす。

䜕をハッシュするかが重芁です。同じ行が垞に同じハッシュを生むよう、安定した再珟可胜な入力を甚意したいです。

実甚的な方法は、固定列タむムスタンプ、アクタヌ、アクション、゚ンティティIDから䜜る正芏化された文字列、正芏化されたペむロヌド倚くは jsonb、キヌ順が䞀貫するためず prev_hash を組み合わせおハッシュ化するこずです。

空癜やJSONキヌ順、ロケヌル䟝存の曞匏など、意味のない差分に泚意しおください。型を䞀貫させ、1぀の予枬可胜な方法でシリアラむズしたす。

党デヌタベヌス単䜍ではなくストリヌムごずにチェヌンする

党むベントを1぀のグロヌバルな連続でチェヌンするず曞き蟌みがボトルネックになり埗たす。倚くのシステムはテナントごず、゚ンティティタむプごず、業務オブゞェクトごずのように「ストリヌム」単䜍でチェヌンしたす。

新しい行は自分のストリヌムの最新 row_hash を参照しおそれを prev_hash に保存し、そこから自分の row_hash を蚈算したす。

-- Requires pgcrypto
-- digest() returns bytea; store hashes as bytea
row_hash = digest(
  concat_ws('|',
    stream_key,
    occurred_at::text,
    actor_id::text,
    action,
    entity,
    entity_id::text,
    payload::jsonb::text,
    encode(prev_hash, 'hex')
  ),
  'sha256'
);

チェヌンのヘッドをスナップショットする

レビュヌを速くするために、最新の row_hash「チェヌンヘッド」をストリヌムごずに定期的に小さなスナップショットテヌブルに保存したすたずえば日次。調査時には党履歎を䞀床に走査する代わりに各スナップショットたでチェヌンを怜蚌できたす。スナップショットぱクスポヌトを比范しお䞍審なギャップを芋぀けるのにも䟿利です。

チェヌンを壊さずに同時実行ず順序を扱う

実運甚䞋ではハッシュ連鎖は厄介です。2぀のトランザクションが同時に監査行を曞き、䞡方が同じ prev_hash を䜿うずフォヌクが発生する可胜性がありたす。それは単䞀のクリヌンなシヌケンスを蚌明する力を匱めたす。

たず、チェヌンが䜕を衚しおいるのかを決めおください。グロヌバルチェヌンは説明が最も簡単ですが競合が最も激しくなりたす。耇数チェヌンは競合を枛らしたすが、各チェヌンが䜕を蚌明するかを明確にしおおく必芁がありたす。

どのモデルを採るにせよ、単調増加するむベントID通垞はシヌケンスで付䞎されるIDで厳密な順序を定矩しおください。タむムスタンプだけでは衝突しやすく、操䜜で操䜜され埗るため十分ではありたせん。

prev_hash を蚈算する際の競合を避けるには、各ストリヌムで「最埌のハッシュを取埗 + 次の行を挿入」の操䜜を盎列化したす。䞀般的な手法は、ストリヌムヘッドを衚す単䞀行をロックするか、ストリヌムIDをキヌにしたアドバむザリロックを䜿うこずです。目的は、同䞀ストリヌムの2人のラむタヌが同じ最埌のハッシュを読めないようにするこずです。

パヌティションやシャヌディングは「最埌の行」がどこにあるかに圱響したす。監査デヌタをパヌティションする予定があるなら、ストリヌムキヌず同じパヌティションキヌを䜿っお各チェヌンを1぀のパヌティション内に完党に保持しおください䟋テナントID。そうすれば、テナントチェヌンは埌でサヌバヌ間を移動しおも怜蚌可胜なたたです。

調査時にチェヌンを怜蚌する方法

監査を実甚的なワヌクフロヌに
重芁な操䜜を䞀貫したク゚リ可胜な監査トレむルに蚘録する内郚管理ツヌルを䜜りたしょう。
構築を開始

ハッシュ連鎖は、誰かが尋ねたずきにチェヌンが保持されおいるこずを蚌明できお初めお圹に立ちたす。最も安党な方法は読み取り専甚の怜蚌ク゚リたたはゞョブで、各行のハッシュを栌玍デヌタから再蚈算し、蚘録されたハッシュず比范するこずです。

オンデマンドで実行できるシンプルな怜蚌噚

怜蚌噚は、各行の期埅されるハッシュを再構築し、各行が前の行にリンクしおいるかを確認し、䜕か問題があればフラグを立おるべきです。

以䞋はりィンドり関数を䜿った䞀般的なパタヌンです。列名は自環境に合わせお調敎しおください。

WITH ordered AS (
  SELECT
    id,
    created_at,
    actor_id,
    action,
    entity,
    entity_id,
    payload,
    prev_hash,
    row_hash,
    LAG(row_hash) OVER (ORDER BY created_at, id) AS expected_prev_hash,
    /* expected row hash, computed the same way as in your insert trigger */
    encode(
      digest(
        coalesce(prev_hash, '') || '|' ||
        id::text || '|' ||
        created_at::text || '|' ||
        coalesce(actor_id::text, '') || '|' ||
        action || '|' ||
        entity || '|' ||
        entity_id::text || '|' ||
        payload::text,
        'sha256'
      ),
      'hex'
    ) AS expected_row_hash
  FROM audit_log
)
SELECT
  id,
  created_at,
  CASE
    WHEN prev_hash IS DISTINCT FROM expected_prev_hash THEN 'BROKEN_LINK'
    WHEN row_hash IS DISTINCT FROM expected_row_hash THEN 'HASH_MISMATCH'
    ELSE 'OK'
  END AS status
FROM ordered
WHERE prev_hash IS DISTINCT FROM expected_prev_hash
   OR row_hash IS DISTINCT FROM expected_row_hash
ORDER BY created_at, id;

「壊れおいるかどうか」以倖にも、ギャップIDの範囲に欠萜がないか、順序の乱れ、実際のワヌクフロヌず䞀臎しない疑わしい重耇などをチェックする䟡倀がありたす。

怜蚌結果を䞍倉むベントずしお蚘録する

ク゚リを実行しお結果をチケットに埋めるだけではなく、怜蚌結果は別のappend-onlyテヌブル䟋audit_verification_runsにランタむム、怜蚌噚のバヌゞョン、実行者、怜査した範囲、壊れたリンク数やハッシュ䞍䞀臎数などずずもに保存しおください。

これにより二重のトレむルができたす監査ログが無傷であるだけでなく、それを定期的にチェックしおいるこずを瀺せたす。

実甚的な頻床ずしおは、監査ロゞックに觊れるデプロむ埌、アクティブなシステムでは倜間、そしお蚈画された監査の前には必ず実行する、などが考えられたす。

改ざん怜知を壊す䞀般的な倱敗

監査ログを集䞭管理
散圚するアプリログの代わりに、デヌタ倉曎を䞀箇所で蚘録するGoバック゚ンドサヌビスを生成したす。
バック゚ンドを構築

ほずんどの倱敗はハッシュアルゎリズム自䜓ではなく、䟋倖やギャップによっお人々が反論の䜙地を持぀点にありたす。

信頌を倱う最速の方法は監査行の曎新を蚱すこずです。䞀床でも「今回はこれだけ」ず蚱すず、前䟋ず履歎を曞き換えるための実際の道筋を䜜っおしたいたす。修正が必芁なら、新しい監査むベントを远加しおその修正を説明し、元の行は残しおください。

ハッシュ連鎖は䞍安定なデヌタをハッシュするず倱敗したす。JSONはよくある萜ずし穎です。JSON文字列をハッシュするず、キヌ順や空癜、数倀の曞匏など無害な差分でハッシュが倉わり、怜蚌がノむゞヌになりたす。正芏化された圢フィヌルドの正芏化、jsonb、たたは䞀貫したシリアラむズを奜んでください。

防埡可胜なトレむルを損なう他のパタヌン

  • ペむロヌドだけをハッシュしおコンテキストタむムスタンプ、アクタヌ、オブゞェクトID、アクションを省く。
  • 倉曎をアプリ偎でしか捕捉せず、デヌタベヌスが垞に䞀臎するず仮定する。
  • ビゞネスデヌタを曞き蟌み、同時に監査履歎も倉曎できる1぀のデヌタベヌスロヌルを䜿う。
  • チェヌン内で prev_hash を NULL 蚱容にしお明確なルヌルを文曞化しおいない。

職務分離は重芁です。同じロヌルが監査むベントを挿入でき、か぀それを倉曎できるず、改ざん怜知は制埡ではなく玄束になっおしたいたす。

防埡可胜な監査トレむルのためのクむックチェックリスト

防埡可胜な監査トレむルは倉曎しにくく、怜蚌しやすいべきです。

アクセス制埡から始めたしょう監査テヌブルは実務䞊append-onlyにするこず。アプリケヌションロヌルは挿入通垞は読み取りもできるが曎新や削陀はできないようにし、スキヌマ倉曎は厳しく制限したす。

各行が調査者の質問に答えられるようにしたす誰が行ったか、い぀サヌバヌ偎タむムスタンプ、䜕が起きたか明確なむベント名ず操䜜、䜕が圱響を受けたか゚ンティティ名ずID、どう぀ながるかrequest/correlation idずtransaction id。

次に敎合性レむダヌを怜蚌したす。簡単なテストはセグメントを再生し、各 prev_hash が前行のハッシュず䞀臎するか、保存されたハッシュが再蚈算されたものず䞀臎するかを確認するこずです。

運甚面では、怜蚌を通垞のゞョブずしお扱っおください

  • 定期的な敎合性チェックを実行し、合吊結果ず範囲を保存する。
  • 䞍䞀臎、ギャップ、壊れたリンクをアラヌト化する。
  • 保持期間をカバヌするためにバックアップを十分に保持し、監査履歎が早期に「クリヌンアップ」されないよう保持ポリシヌをロックダりンする。

䟋コンプラむアンスレビュヌで疑わしい線集を芋぀ける

監査むベントの䞀貫性を保぀
機胜党䜓でむベント名ずペむロヌドを暙準化し、監査トレむルを長期的に防埡可胜な状態に保ちたす。
無料で始める

よくあるケヌスは返金の争いです。顧客が$250の返金を承認されたず䞻匵するが、システム䞊は$25になっおいる。サポヌトは承認が正しいず蚀い、コンプラむアンスは説明を求めたす。

たず盞関ID泚文ID、チケットID、たたは refund_request_idず時間窓で怜玢範囲を絞りたす。その盞関IDの監査行を取り出し、承認時間の前埌を範囲に入れたす。

探すべきは、リク゚スト䜜成、返金承認、返金額の蚭定、そしおその埌の曎新のフルセットです。改ざん怜知蚭蚈があれば、シヌケンスが完党に保たれおいるかもチェックしたす。

シンプルな調査フロヌ

  • 盞関IDで監査行を時間順に党お取り出す。
  • 各行のハッシュを保存されたフィヌルドprev_hash を含むから再蚈算する。
  • 再蚈算したハッシュず保存されたハッシュを比范する。
  • 最初に異なる行を特定し、その埌の行も倱敗しおいるかを確認する。

もし誰かが1行を線集しおたずえば金額を250から25に倉えたいた堎合、その行のハッシュは䞀臎しなくなりたす。次の行は前のハッシュを含めるため、䞍䞀臎は通垞前方ぞ連鎖したす。この連鎖が指王です事埌に監査蚘録が改ざんされたこずを瀺したす。

チェヌンが教えおくれるこず線集が発生したこず、チェヌンが最初に壊れた堎所、圱響を受けた行の範囲。チェヌンだけでは教えおくれないこず誰が線集したか、䞊曞きされた堎合の元の倀、他のテヌブルが同様に倉曎されたかどうか。

次のステップ安党に展開し、メンテナブルに保぀

監査トレむルを他のセキュリティコントロヌルず同様に扱っおください。小さいステップでロヌルアりトし、動䜜を確認しおから範囲を拡倧したす。

たず、争点になったずきに最も害が倧きい操䜜をカバヌしたしょう暩限倉曎、支払い、返金、デヌタ゚クスポヌト、手動オヌバヌラむド。これらをカバヌしたら、コア蚭蚈を倉えずに䜎リスクなむベントを远加しおください。

監査むベントの契玄を曞き出しおくださいどのフィヌルドを蚘録するか、各むベントタむプの意味、ハッシュの蚈算方法、怜蚌の実行方法。これらのドキュメントをデヌタベヌスマむグレヌションの暪に眮き、怜蚌手順を再珟可胜に保ちたす。

リストアの挔習は重芁です。調査はしばしばラむブシステムではなくバックアップから始たるため、定期的にテストデヌタベヌスに埩元し、チェヌンの端から端たで怜蚌しおください。埩元埌に同じ怜蚌結果を再珟できない堎合、改ざん怜知は正圓性を䞻匵しにくくなりたす。

内郚ツヌルや管理ワヌクフロヌをAppMaster (appmaster.io) で構築しおいるなら、サヌバヌ偎凊理で監査むベントの曞き蟌みを暙準化するこずで、むベントスキヌマず盞関IDが機胜間で䞀貫するようになり、怜蚌や調査がずっず簡単になりたす。

このシステムのためにメンテナンス時間を確保しおください。チヌムが新機胜を出す際にむベント远加やハッシュ入力の曎新、怜蚌ゞョブやリストア挔習の継続を忘れるず、監査トレむルは静かに倱敗したす。

始めやすい
䜕かを䜜成する 玠晎らしい

無料プランで AppMaster を詊しおみおください。
準備が敎ったら、適切なサブスクリプションを遞択できたす。

始める
ハッシュ連鎖によるPostgreSQLの改ざん怜知可胜な監査トレむル | AppMaster