২৯ আগ, ২০২৫·6 মিনিট পড়তে

হ্যাশ চেইনিং সহ PostgreSQL-এ টেম্পার-এভিডেন্ট অডিট ট্রেইল

Append-only টেবিল এবং হ্যাশ চেইনিং ব্যবহার করে PostgreSQL-এ টেম্পার-এভিডেন্ট অডিট ট্রেইল কিভাবে তৈরি করবেন জানুন, যাতে পর্যালোচনা ও তদন্তে সম্পাদনা সহজেই সনাক্ত করা যায়।

হ্যাশ চেইনিং সহ PostgreSQL-এ টেম্পার-এভিডেন্ট অডিট ট্রেইল

কেন সাধারণ অডিট লগগুলো বিতর্ক সহজ হয়

অডিট ট্রেইল হলো সেই রেকর্ড যেটার ওপর আপনি ভরসা করেন যখন কিছু সন্দেহজনক দেখা যায়: অদ্ভুত রিফান্ড, এমন একটি পারমিশন পরিবর্তন যা কেউ মনে করতে পারছে না, বা এমন একটি কাস্টমার রেকর্ড যা “অদৃশ্য” হয়ে গেছে। যদি অডিট ট্রেইল সম্পাদনাযোগ্য হয়, তাহলে সেটা সাক্ষ্য হওয়া বন্ধ করে এবং আরেকটা ডাটা হিসেবে পরিণত হয় যাকে কেউ পুনরায় লিখে দিতে পারে।

অনেক “অডিট লগ” আসলে সাধারণ টেবিলই। যদি সারিগুলো আপডেট বা ডিলিট করা যায়, তাহলে কাহিনিকেও আপডেট বা মুছে ফেলা যায়।

একটি গুরুত্বপূর্ণ পার্থক্য: সম্পাদনা আটকানো এবং সম্পাদনা সনাক্তযোগ্য করা একই নয়। আপনি পারমিশনের মাধ্যমে পরিবর্তন কমাতে পারেন, কিন্তু যে কেউ যথেষ্ট অ্যাক্সেস পেলে (বা কোনো অ্যাডমিনের ক্রেডেনশিয়াল চুরি হলে) ইতিহাস বদলে দিতে পারে। টেম্পার-এভিডেন্স সেই বাস্তবতাকে মেনে নেয়। আপনি হয়ত প্রতিটি পরিবর্তন রোধ করতে পারবেন না, কিন্তু আপনি পরিবর্তনগুলোকে একটি স্পষ্ট আঙ্গুলছাপ ফেলে যেতে বাধ্য করতে পারেন।

সাধারণ অডিট লগগুলো পূর্বানুমেয় কারণে বিতর্কে পড়ে। привিলেজড ব্যবহারকারীরা পরে লগ “সঠিক” করে দিতে পারে। একটি কমপ্রোমাইজড অ্যাপ অ্যাকাউন্ট বিশ্বাসযোগ্য এন্ট্রি লিখতে পারে যা সাধারণ ট্র্যাফিকের মতো দেখায়। টাইমস্ট্যাম্পরা পরে ভরাট করে দিয়া বদল লুকাতে পারে। অথবা কোনো ব্যক্তি কেবল সবচেয়ে ক্ষতিকর লাইনগুলো মুছে ফেলতে পারে।

“টেম্পার-এভিডেন্ট” মানে আপনি এমনভাবে অডিট ট্রেইল ডিজাইন করেন যে ছোট একটি সম্পাদনাও (একটি ফিল্ড বদলানো, একটি সারি সরিয়ে ফেলা, ইভেন্টগুলোর পুনরায় সরাসরি করা) পরে সনাক্তযোগ্য হয়ে পড়ে। আপনি যাদু প্রতিশ্রুতি দিচ্ছেন না। আপনি প্রতিশ্রুতি দিচ্ছেন যে যখন কেউ জিজ্ঞেস করবে, “এই লগ আসল কিভাবে?”, তখন আপনি এমন চেক চালাতে পারবেন যা দেখায় লগে স্পর্শ করা হয়েছে কি না।

প্রমাণ করতে আপনি কী কী প্রয়োজন তা ঠিক করুন

একটি টেম্পার-এভিডেন্ট অডিট ট্রেইল শুধুমাত্র তখনই কাজে দেয় যখন সেটা সেই প্রশ্নগুলোর উত্তর দিতে পারে যা পরে উঠবে: কে কী করেছে, সেটা কখন করেছে, এবং কী বদলেছে।

যেসব ইভেন্ট আপনার ব্যবসার জন্য গুরুত্বপূর্ণ সেগুলো দিয়ে শুরু করুন। ডাটা পরিবর্তন (create, update, delete) হলো বেসলাইন, কিন্তু তদন্তগুলো প্রায়ই সিকিউরিটি ও অ্যাক্সেস বিষয়েও নির্ভর করে: লগইন, পাসওয়ার্ড রিসেট, পারমিশন পরিবর্তন, এবং অ্যাকাউন্ট লকআউট। আপনি যদি পেমেন্ট হ্যান্ডেল করেন, তাহলে রিফান্ড, ক্রেডিট, বা পেআউটকে প্রথম শ্রেণির ইভেন্ট হিসেবে বিবেচনা করুন, কোনো আপডেট সারির সাইড-ইফেক্ট হিসেবে নয়।

তারপর ঠিক করুন কোনটি একটি ইভেন্টকে বিশ্বাসযোগ্য করে তোলে। অডিটররা সাধারণত একজন actor (ব্যবহারকারী বা সার্ভিস), সার্ভার-সাইড টাইমস্ট্যাম্প, নেওয়া অ্যাকশন, এবং প্রভাবিত অবজেক্ট আশা করে। আপডেটগুলোর জন্য before এবং after মান সংরক্ষণ করুন (অথবা অন্তত সংবেদনশীল ক্ষেত্রগুলো), এবং একটি request id বা correlation id রাখুন যাতে আপনি অনেক ছোট ডাটাবেস পরিবর্তনকে এক ব্যবহারকারীর ক্রিয়া হিসেবে বাইন্ড করতে পারেন।

শেষে, আপনার সিস্টেমে “অপরিবর্তনীয়” (immutable) বলতে কি বোঝায় তা স্পষ্ট করে লিখুন। সবচেয়ে সরল নিয়ম: কখনই অডিট সারি আপডেট বা ডিলিট করবেন না, কেবলই ইনসার্ট করুন। যদি কিছু ভুল হয়, একটি নতুন ইভেন্ট লিখুন যা পুরনোটি ঠিক বা supersede করে, এবং মূলটি দৃশ্যমান রাখুন।

একটি append-only অডিট টেবিল তৈরি করুন

অডিট ডাটা আপনার সাধারণ টেবিল থেকে আলাদা রাখুন। একটি নিবদ্ধ audit স্কিমা অসাবধানতায় সম্পাদনাগুলি কমায় এবং পারমিশনগুলো বোঝা সহজ করে।

লক্ষ্য সহজ: সারি যোগ করা যাবে, কিন্তু পরিবর্তন বা মুছে ফেলা যাবে না। PostgreSQL-এ এটা আপনি privileges (কে কী করতে পারে) এবং টেবিল ডিজাইনের কিছু সেফটি রেল দিয়ে enforce করবেন।

এখানে একটি ব্যবহারিক প্রারম্ভিক টেবিল আছে:

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_typeentity_id যাতে আপনি একটি রেকর্ডকে পরিবর্তনের জেরে ট্র্যাক করতে পারেন।
  • request_id যাতে এক ব্যবহারকারীর একটি অ্যাকশনের প্রকৃত পরিসরকে একাধিক সারির সাথে ট্রেস করা যায়।

এটি রোল দিয়ে লকডাউন করুন। আপনার অ্যাপ্লিকেশন রোলের উচিত INSERT এবং SELECT করতে পারা audit.events-এ, কিন্তু UPDATE বা DELETE নয়। স্কিমা পরিবর্তন এবং শক্তিশালী পারমিশন রাখুন এমন একটি অ্যাডমিন রোলে যা অ্যাপ দ্বারা ব্যবহৃত হয় না।

ট্রিগার দিয়ে পরিবর্তন ক্যাপচার করুন (পরিষ্কার ও পূর্বানুমেয়)

আপনি যদি একটি টেম্পার-এভিডেন্ট অডিট ট্রেইল চান, তাহলে পরিবর্তন ক্যাপচার করার সবচেয়ে নির্ভরযোগ্য জায়গা হলো ডাটাবেস। অ্যাপ্লিকেশন লগগুলো বাদ পড়তে পারে, ফিল্টার হতে পারে, বা পুনঃলিখিত হতে পারে। একটি ট্রিগার যে কোনো অ্যাপ, স্ক্রিপ্ট, বা অ্যাডমিন টুল টেবিলটিকে টাচ করলেই চলে।

ট্রিগারগুলোকে বিরক্তিকর না রাখুন। তাদের কাজ একটাই হওয়া উচিত: যে টেবিলগুলো গুরুত্বপূর্ণ সেগুলোর প্রতিটি INSERT, UPDATE, এবং DELETE-এর জন্য একটি অডিট ইভেন্ট অ্যাপেন্ড করা।

একটি বাস্তবমুখী অডিট রেকর্ড সাধারণত টেবিল নাম, অপারেশন টাইপ, প্রাইমারি কী, পূর্ব এবং পরে মান, একটি টাইমস্ট্যাম্প, এবং সম্পর্কিত পরিবর্তনগুলোকে গ্রুপ করার জন্য শনাক্তকারী (ট্রানজ্যাকশন আইডি এবং correlation id) অন্তর্ভুক্ত করে।

Correlation id হল সেই পার্থক্য যা “20 সারি আপডেট” এবং “এটা এক বাটন ক্লিক” এর মধ্যে ফারাক করে। আপনার অ্যাপ একটি অনুরোধে একবার correlation id সেট করতে পারে (উদাহরণস্বরূপ, একটি DB session setting-এ), এবং ট্রিগার তা পড়তে পারে। txid_current()-ও স্টোর করুন, যাতে correlation id না থাকলেও আপনি পরিবর্তনগুলোকে গ্রুপ করতে পারেন।

নীচে একটি সরল ট্রিগার প্যাটার্ন আছে যা পূর্বানুমেয় থাকে কারণ এটি কেবল অডিট টেবিলে ইনসার্ট করে (নামগুলো আপনার স্কিমা অনুযায়ী সামঞ্জস্য করুন):

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;

ট্রিগারের ভিতর বেশি করার লোভ সামলান। অতিরিক্ত কুয়েরি, নেটওয়ার্ক কল, বা জটিল শাখায়ড়ি এড়িয়ে চলুন। ছোট ট্রিগারগুলো টেস্ট করা সহজ, দ্রুত চালায় এবং পর্যালোচনায় বিতর্ক করা কঠিন।

হ্যাশ চেইনিং যোগ করুন যাতে সম্পাদনাগুলো আঙ্গুলছাপ রাখে

একটি অডিট-রেডি ব্যাকএন্ড তৈরি করুন
PostgreSQL-এ একটি append-only অডিট স্কিমা মডেল করুন এবং হ্যান্ড-কোডিং ছাড়া প্রোডাকশনে ব্যাকএন্ড জেনারেট করুন।
AppMaster চেষ্টা করুন

একটি append-only টেবিল সহায়ক, কিন্তু যথেষ্ট অ্যাক্সেস থাকা কেউ অতীত সারি এখনও পুনর্লিখন করতে পারে। হ্যাশ চেইনিং সেই ধরনের ট্যাম্পারিংকে দৃশ্যমান করে দেয়।

প্রতি অডিট সারিতে দুটি কলাম যোগ করুন: prev_hash এবং row_hash (কখনও কখনও chain_hash বলা হয়)। prev_hash একই চেইনের পূর্ববর্তী সারির হ্যাশ সংরক্ষণ করে। row_hash বর্তমান সারির হ্যাশ সংরক্ষণ করে, যা সারির ডাটা এবং prev_hash থেকে গণনা করা হয়।

আপনি কি হ্যাশ করবেন তা গুরুত্বপূর্ণ। আপনি একটি স্থির, পুনরাবৃত্তিমূলক ইনপুট চান যাতে একই সারি সবসময় একই হ্যাশ দেয়।

প্র্যাকটিক্যাল পদ্ধতি হল একটি canonical স্ট্রিং হ্যাশ করা, যা নির্দিষ্ট কলাম (টাইমস্ট্যাম্প, actor, অ্যাকশন, entity id), একটি canonical পে-লোড (প্রায়শই jsonb, কারণ কি-গুলো ধারাবাহিকভাবে রাখা হয়), এবং prev_hash থেকে তৈরি।

অর্থহীন পরিবর্তন যেমন whitespace, JSON কি-অর্ডার বা লোকেল-নির্ভর ফরম্যাটিংয়ের প্রতি সতর্ক থাকুন। টাইপগুলো কনসিসটেন্ট রাখুন এবং একইভাবে সিরিয়ালাইজ করুন।

স্ট্রীমভিত্তিক চেইন করুন, পুরো ডাটাবেস নয়

যদি আপনি প্রতিটি অডিট ইভেন্টকে একটি গ্লোবাল সিকোয়েন্সে চেইন করেন, তাহলে লেখাগুলো বটলনেক হতে পারে। অনেক সিস্টেম “স্ট্রীম”-এর মধ্যে চেইন করে, যেমন প্রতিটি টেন্যান্ট, entity type, বা ব্যবসায়িক অবজেক্টভিত্তিক।

প্রতি নতুন সারি তার স্ট্রীমের সর্বশেষ 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 (“চেইন হেড”) মাঝে মাঝে স্ন্যাপশট হিসেবে সংরক্ষণ করুন, যেমন প্রতিদিন। তদন্তকালে আপনি সম্পূর্ণ ইতিহাস স্ক্যান করার বদলে প্রতিটি স্ন্যাপশট পর্যন্ত চেইনটি ভেরিফাই করতে পারেন। স্ন্যাপশটগুলো এক্সপোর্ট তুলনা করা এবং সন্দেহজনক গ্যাপ দেখতে সাহায্য করে।

কনকারেন্সি এবং অর্ডারিং—চেইন ভাঙা ছাড়াই

বাস্তব ট্র্যাফিকের মধ্যে হ্যাশ চেইনিং জটিল হয়ে পারে। যদি দুটি ট্রান্স্যাকশন একই সময়ে অডিট সারি লিখে এবং উভয়ই একই prev_hash ব্যবহার করে, তাহলে আপনি forks পেতে পারেন। এটি আপনার একক, পরিষ্কার সিকোয়েন্স প্রমাণ করার ক্ষমতা দুর্বল করে।

প্রথমে ঠিক করুন আপনার চেইন কী উপস্থাপন করে। একটি গ্লোবাল চেইন ব্যাখ্যা করা সবচেয়ে সহজ, কিন্তু কনটেনশন বেশি। বহু চেইন কনটেনশন কমায়, কিন্তু আপনাকে স্পষ্ট হতে হবে প্রতিটি চেইন কি প্রমাণ করে।

যেকোনো মডেলই হোক, একটি মনোটোনিক ইভেন্ট আইডি দিয়ে কঠোর অর্ডার সংজ্ঞায়িত করুন (সাধারণত একটি sequence-backed id)। টাইমস্ট্যাম্প যথেষ্ট নয় কারণ তারা সংঘর্ষ ঘটাতে পারে এবং বদলানো যেতে পারে।

prev_hash গণনা করার সময় রেস কন্ডিশন এড়াতে, প্রতিটি স্ট্রীমের জন্য “শেষ হ্যাশ নাও + পরের সারি ইনসার্ট করো” অপারেশন সিরিয়ালাইজ করুন। সাধারণ পদ্ধতি হল স্ট্রীম হেডকে প্রতিনিধিত্বকারী একটি একক সারি লক করা, অথবা স্ট্রীম আইডি দ্বারা অ্যাডভাইজরি লক ব্যবহার করা। লক্ষ্য হলো একই স্ট্রীমে দুই লেখক একই last hash পড়তে না পারে।

পার্টিশনিং এবং শার্ডিং যেখানে “শেষ সারি” থাকে তা প্রভাবিত করে। আপনি যদি অডিট ডাটা পার্টিশন করতে চান, প্রতিটি চেইনকে একই পার্টিশন কী দিয়ে সম্পূর্ণভাবে কনটেইন করুন (উদাহরণস্বরূপ, টেন্যান্ট আইডি)। এতে টেন্যান্ট চেইনগুলো ভেরিফায়েবল থাকে এমনকি টেন্যান্ট পরে সার্ভার বদল করলেও।

তদন্তে চেইন কিভাবে ভেরিফাই করবেন

চেইন ভেরিফিকেশন রান তৈরি করুন
একটি ভেরিফিকেশন ওয়ার্কফ্লো তৈরি করুন যা হ্যাশগুলো পুনরায় গণনা করে এবং ইন্টেগ্রিটি চেক ফলাফলগুলো ইভেন্ট আকারে সংরক্ষণ করে।
প্রোজেক্ট শুরু করুন

হ্যাশ চেইনিং কেবল তখনই সাহায্য করে যখন কেউ জিজ্ঞেস করলে আপনি দেখাতে পারেন চেইনটি তখনও অক্ষত আছে। সবচেয়ে নিরাপদ পদ্ধতি হলো একটি রিড-অনলি ভেরিফিকেশন কুয়েরি (অথবা জব) চালানো যা প্রতিটি সারির হ্যাশ পুনরায় গণনা করে এবং সেটাকে রেকর্ডকৃত হ্যাশের সাথে তুলনা করে।

অন-ডিমান্ড চালানো একটি সহজ ভেরিফায়ার

একটি ভেরিফায়ার হওয়া উচিত: প্রত্যেক সারির প্রত্যাশিত হ্যাশ পুনর্নির্মাণ করা, নিশ্চিত করা যে প্রতিটি সারি পূর্ববর্তী সারির সাথে লিঙ্ক করে, এবং কোনো অস্বাভাবিকতা ফ্ল্যাগ করা।

নিচে একটি সাধারণ প্যাটার্ন উইন্ডো ফাংশন ব্যবহার করে দেখানো আছে। কলাম নাম আপনার টেবিল অনুযায়ী সামঞ্জস্য করুন।

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;

“ভাঙ্গা বা না” ছাড়াও, গ্যাপ (এক রেঞ্জে মিসিং আইডি), অর্ডারবহির্ভূত লিঙ্ক, এবং সন্দেহজনক ডুপ্লিকেট পরীক্ষা করা গুরুত্বপূর্ণ যা বাস্তব ওয়ার্কফ্লোয়ের সাথে মেলছে না।

ভেরিফিকেশন ফলাফল immutable ইভেন্ট হিসেবে রেকর্ড করুন

কেবল একটি কুয়েরি চালিয়ে আউটপুট টিকিটে গোপন করে রাখবেন না। ভেরিফিকেশন আউটকামগুলো একটি পৃথক append-only টেবিলে (উদাহরণ: audit_verification_runs) সংরক্ষণ করুন, যাতে রানের সময়, ভেরিফায়ারের ভার্সন, যিনি ট্রিগার করেছেন তিনি কে, চেক করা রেঞ্জ, এবং broken links ও hash mismatches-এর সংখ্যা থাকে।

এতে আপনাকে দ্বিতীয় একটি ট্রেইল দেয়: কেবল অডিট লগ অক্ষত নয়, আপনি দেখাতে পারবেন যে আপনি তা নিয়মিত চেক করছেন।

ব্যবহারিক ক্যাডেন্স হতে পারে: অডিট লজিককে স্পর্শ করে এমন কোনো ডিপ্লয়ের পরে রান করা, সক্রিয় সিস্টেমে রাতের বনিয়াদীভাবে রান করা, এবং পরিকল্পিত অডিটের আগে সর্বদা চালানো।

সাধারণ ভুলগুলো যা টেম্পার-এভিডেন্স ভেঙে দেয়

আত্মবিশ্বাসের সঙ্গে ডিপ্লয় করুন
আপনার অ্যাপকে আপনার ক্লাউডে ডিপ্লয় করুন এবং এনভায়রনমেন্ট ও রিস্টোরগুলোর মাধ্যমে একই অডিট প্যাটার্ন বজায় রাখুন।
ডিপ্লয় করুন

অধিকাংশ ব্যর্থতা হ্যাশ অ্যালগরিদমের কথা নয়। তারা ব্যতিক্রম এবং গ্যাপ নিয়ে, যা লোকেদের বিতর্কের জন্য রুম দেয়।

বিশ্বাস হারানোর দ্রুততম পথ হলো অডিট সারি আপডেট করার অনুমতি দেওয়া। এমনকি যদি এটা “শুধু এই একবার” হয়, আপনি precedence এবং ইতিহাস পুনরায় লেখার একটি কার্যকর পথ তৈরি করে দিয়েছেন। যদি সমন্বয় দরকার হয়, একটি নতুন অডিট ইভেন্ট যোগ করুন যা সংশোধন ব্যাখ্যা করে এবং মূলটি রাখুন।

হ্যাশ চেইনিং גם ব্যর্থ হয় যখন আপনি অস্থির ডাটা হ্যাশ করেন। JSON একটি সাধারণ ফাঁদ। যদি আপনি JSON স্ট্রিং হ্যাশ করেন, কিন্তু খালি কীবোর্ড অর্ডার বা whitespace বা সংখ্যার ফরম্যাটিংয়ের ক্ষুদ্র পার্থক্য হ্যাশ পরিবর্তন করে এবং ভেরিফিকেশন noisy করে তোলে। canonical ফর্ম ব্যবহার করুন: normalized fields, jsonb, বা অন্যconsistent সিরিয়ালাইজেশন।

অন্যান্য প্যাটার্ন যা প্রতিরক্ষামূলক ট্রেইল দুর্বল করে:

  • কেবল পে-লোড হ্যাশ করা এবং প্রসঙ্গ (টাইমস্ট্যাম্প, actor, অবজেক্ট আইডি, অ্যাকশন) ছেড়ে দেওয়া।
  • কেবল অ্যাপ্লিকেশনেই পরিবর্তন ক্যাপচার করা এবং ধরে নেওয়া যে ডাটাবেস সর্বদা মেলে।
  • একটি ডাটাবেস রোল ব্যবহার করা যা ব্যবসায়িক ডাটা লিখতে পারে এবং একই সাথে অডিট ইতিহাসও পরিবর্তন করতে পারে।
  • একটি চেইনে prev_hash-এর জন্য NULL-অনুমতি রেখে একটি পরিষ্কার, ডকুমেন্টেড নিয়ম না থাকা।

দায়িত্ব ভাগ করা গুরুত্বপূর্ণ। যদি একই রোলই অডিট ইভেন্ট ইনসার্ট এবং সেগুলো পরিবর্তন করতে পারে, তাহলে টেম্পার-এভিডেন্স একটি নিয়ম না রেখে কেবল একটি প্রতিশ্রুতি হয়ে যায়।

একটি প্রতিরক্ষামূলক অডিট ট্রেইলের দ্রুত চেকলিস্ট

একটি প্রতিরক্ষামূলক অডিট ট্রেইল পরিবর্তন করা কঠিন এবং ভেরিফাই করা সহজ হওয়া উচিত।

অ্যাক্সেস কন্ট্রোল থেকে শুরু করুন: অডিট টেবিল বাস্তবে append-only হতে হবে। অ্যাপ্লিকেশন রোলটি ইনসার্ট (এবং সাধারণত রিড) করতে পারবে, কিন্তু আপডেট বা ডিলিট করতে পারবেনা। স্কিমা পরিবর্তন কঠোরভাবে সীমাবদ্ধ করুন।

প্রতিটি সারি নিশ্চিত করুন যে তদন্তকারী যে প্রশ্নগুলো করবে সেগুলো উত্তর দেয়: কে করেছে, কখন হয়েছে (সার্ভার-সাইড), কী হয়েছে (সুস্পষ্ট ইভেন্ট নাম ও অপারেশন), কী টাচ করেছে (entity নাম ও id), এবং কীভাবে সংযুক্ত (request/correlation id ও transaction id)।

তারপর ইন্টেগ্রিটি লেয়ার ভ্যালিডেট করুন। একটি দ্রুত পরীক্ষা হলো একটি সেগমেন্ট রেপ্লে করে নিশ্চিত করা যে প্রতিটি prev_hash পূর্ববর্তী সারির হ্যাশের সাথে মেলে এবং প্রতিটি সংরক্ষিত হ্যাশ পুনরায় গণনা করে মেলে।

পরিচালনাগতভাবে, ভেরিফিকেশনকে একটি সাধারণ জব হিসেবে ট্রিট করুন:

  • সময়নিয়মে ইন্টেগ্রিটি চেক চালান এবং পাস/ফেইল ফলাফল ও রেঞ্জ সংরক্ষণ করুন।
  • mismatch, গ্যাপ, এবং broken link-এ অ্যালার্ট সেট করুন।
  • ব্যাকআপ রাখুন পর্যাপ্ত সময় পর্যন্ত যাতে আপনার রিটেনশন উইন্ডো কভার করে, এবং রিটেনশন লকডাউন করুন যাতে অডিট ইতিহাস আগে থেকেই “ক্লিন” করা না যায়।

উদাহরণ: একটি কমপ্লায়েন্স রিভিউ-এ সন্দেহজনক সম্পাদনা খুঁজে বের করা

কমপ্লায়েন্ট অ্যাডমিন প্যানেল শিপ করুন
রোল-ভিত্তিক অ্যাক্সেস এবং সংবেদনশীল টেবিলগুলোর জন্য append-only ইভেন্ট হিস্ট্রি নিয়ে সুরক্ষিত অ্যাডমিন প্যানেল তৈরি করুন।
অ্যাপ তৈরি করুন

একটি সাধারণ টেস্ট কেস হলো একটি রিফান্ড বিতর্ক। একটি কাস্টমার দাবি করছে তারা $250 রিফান্ড অনুমোদিত ছিল, কিন্তু সিস্টেমে এখন $25 দেখাচ্ছে। সাপোর্ট জোর দিয়ে বলে অনুমোদন ঠিক ছিল, এবং কমপ্লায়েন্স একটা উত্তর চায়।

প্রথমে correlation id (অর্ডার আইডি, টিকিট আইডি, বা refund_request_id) এবং একটি সময় উইন্ডো ব্যবহার করে অনুসন্ধানকে সংকীর্ণ করুন। ঐ correlation id-র জন্য অডিট সারিগুলো এবং অনুমোদন সময়কে ঘিরে সারিগুলো টেনে আনুন।

আপনি পুরো ইভেন্ট সেট দেখছেন: রিকোয়েস্ট তৈরি, রিফান্ড অনুমোদিত, রিফান্ড অ্যামাউন্ট সেট, এবং পরের কোনো আপডেট। টেম্পার-এভিডেন্ট ডিজাইনে আপনি চেইনটি অক্ষত আছে কিনা তাও যাচাই করবেন।

সহজ তদন্তের ফ্লো:

  • correlation id অনুযায়ী সময়ক্রমে সব অডিট সারি টানুন।
  • প্রতিটি সারির হ্যাশ তার সংরক্ষিত ফিল্ড থেকে (ও prev_hash সহ) পুনরায় গণনা করুন।
  • গণনা করা হ্যাশগুলোকে সংরক্ষিত হ্যাশের সাথে তুলনা করুন।
  • প্রথম ভিন্ন সারি শনাক্ত করুন এবং দেখুন পরে সারিগুলোও ব্যর্থ করছে কি না।

যদি কেউ একক অডিট সারি সম্পাদনা করে থাকে (উদাহরণ: অ্যামাউন্ট 250 থেকে 25 করা), সেই সারির হ্যাশ আর মিলবে না। কারণ পরের সারি পূর্ববর্তী হ্যাশ অন্তর্ভুক্ত করে, অসামঞ্জস্যটি সাধারণত সামনে পর্যন্ত কাসকেড করবে। সেই কাসকেডই আঙ্গুলছাপ: এটা দেখায় যে অডিট রেকর্ড পরে পরিবর্তিত হয়েছে।

চেইন কী বলতে পারে: একটি সম্পাদনা হয়েছে, চেইন কোথায় প্রথম ভাঙল তা, এবং ক্ষতিগ্রস্ত সারিগুলোর পরিসর। একাই চেইন কী বলতে পারবে না: সম্পাদনাটি কে করেছে, যদি তার পরবর্তী মান ওভাররাইট করা হয়ে থাকে তাহলে আসল মান কি ছিল, অথবা অন্য টেবিলগুলোও কি বদলানো হয়েছে কিনা।

পরবর্তী ধাপ: নিরাপদভাবে রোলআউট করুন এবং মেইনটেইনেবল রাখুন

আপনার অডিট ট্রেইলকে অন্য কোনো সিকিউরিটি কন্ট্রোল মতই ট্রিট করুন। ধাপে ধাপে রোলআউট করুন, কাজ করে তা প্রমাণ করুন, তারপর এক্সপ্যান্ড করুন।

প্রথমে তাদের কাজগুলো অন্তর্ভুক্ত করুন যেগুলো বিতর্ক হলে আপনাকে সবচেয়ে বেশি ক্ষতি করবে: পারমিশন পরিবর্তন, পেআউট, রিফান্ড, ডাটা এক্সপোর্ট, এবং ম্যানুয়াল ওভাররাইড। এগুলো কভার হওয়ার পর, কম-ঝুঁকিপূর্ণ ইভেন্ট যোগ করুন কোনো মূল ডিজাইন বদল না করে।

আপনার অডিট ইভেন্টগুলোর কনট্রাক্ট লিখে রাখুন: কোন ফিল্ডগুলো রেকর্ড হবে, প্রতিটি ইভেন্ট টাইপ কী মানে, হ্যাশ কিভাবে গণনা করা হবে, এবং ভেরিফিকেশন কিভাবে চালাবেন। ঐ ডকুমেন্টেশন ডাটাবেস মাইগ্রেশনগুলোর পাশে রাখুন এবং ভেরিফিকেশন পদ্ধতিটা পুনরাবৃত্তিযোগ্য রাখুন।

রিস্টোর ড্রিল গুরুত্বপূর্ণ কারণ তদন্তগুলো প্রায়ই ব্যাকআপ থেকে শুরু হয়, লাইভ সিস্টেম থেকে নয়। নিয়মিতভাবে ব্যাকআপ রিস্টোর করে টেস্ট ডাটাবেসে চেইনটি এন্ড-টু-এন্ড ভেরিফাই করুন। যদি রিস্টোরের পরে একই ভেরিফিকেশন রেজাল্ট পুনরায় উৎপন্ন করতে না পারেন, আপনার টেম্পার-এভিডেন্স ডিফকাঠিন হয়ে যাবে।

আপনি যদি অভ্যন্তরীণ টুল এবং অ্যাডমিন ওয়ার্কফ্লোগুলো AppMaster (appmaster.io) দিয়ে তৈরি করেন, সার্ভার-সাইড প্রসেসের মাধ্যমে অডিট ইভেন্ট রাইটগুলো স্ট্যান্ডার্ডাইজ করা ইভেন্ট স্কিমা এবং correlation id-গুলো ফিচার জুড়ে একরূপ রাখে, যা ভেরিফিকেশন এবং তদন্তকে অনেক সহজ করে।

এই সিস্টেমটির জন্য মেইনটেন্যান্স সময় নির্ধারণ করুন। টিমগুলো নতুন ফিচার শিপ করলে প্রায়ই তারা অডিট ইভেন্টগুলো যোগ করা, হ্যাশ ইনপুট আপডেট করা, বা ভেরিফিকেশন জব এবং রিস্টোর ড্রিল চালানো ভুলে যায়—এগুলোই সাধারণত সাইলেন্টভাবে ব্যর্থ করে।

শুরু করা সহজ
কিছু আশ্চর্যজনকতৈরি করুন

বিনামূল্যের পরিকল্পনা সহ অ্যাপমাস্টারের সাথে পরীক্ষা করুন।
আপনি যখন প্রস্তুত হবেন তখন আপনি সঠিক সদস্যতা বেছে নিতে পারেন৷

এবার শুরু করা যাক