PostgreSQL সারি-স্তরের নিরাপত্তা প্যাটার্ন — মাল্টি-টেন্যান্ট অ্যাপস
টেন্যান্ট আইসোলেশন ও রোল-রুলস সহ বাস্তব প্যাটার্ন শিখুন যাতে অ্যাক্সেস ডাটাবেসে প্রযোজ্য হয়, শুধু অ্যাপে নয়।

কেন ডাটাবেস-প্রযোজিত অ্যাক্সেস গুরুত্বপূর্ণ ব্যবসায়িক অ্যাপে
বিজনেস অ্যাপগুলো সাধারণত এ রকম নিয়ম থাকে: “ব্যবহারকারীরা কেবল তাদের কোম্পানির রেকর্ডই দেখবে” অথবা “কেবল ম্যানেজাররা রিফান্ড অনুমোদন করতে পারবে।” অনেক দল এই নিয়মগুলো UI বা API-তে লাগিয়ে দিয়ে ভাবেন এটাই যথেষ্ট। সমস্যা হল, ডাটাবেসে যে কোনো অতিরিক্ত পথ ডেটা লিক হওয়ার সুযোগ বাড়ায়: একটি অভ্যন্তরীণ অ্যাডমিন টুল, ব্যাকগ্রাউন্ড জব, অ্যানালিটিক্স কুয়েরি, ভুলেও থাকা একটি এন্ডপয়েন্ট, বা এমন একটি বাগ যা এক চেক বাইপাস করে।
টেন্যান্ট আইসোলেশন মানে এক গ্রাহক (টেন্যান্ট) কখনো অন্য গ্রাহকের ডেটা পড়বে বা বদলাবে না, এমনকি ভুল করে হলেও নয়। রোল-ভিত্তিক অ্যাক্সেস মানে একই টেন্যান্টের ভেতর বিভিন্ন লোকের ভিন্ন ক্ষমতা থাকবে, যেমন(agent) এজেন্ট, ম্যানেজার, ফাইনান্স ইত্যাদি। এই নিয়মগুলো বর্ণনা করা সহজ, কিন্তু যখন এগুলো বহু জায়গায় ছড়িয়ে থাকে তখন সেগুলো নিখুঁতভাবে ধরে রাখা কঠিন।
PostgreSQL row-level security (RLS) এমন একটি ডাটাবেস ফিচার যা সিদ্ধান্ত নেয় কোন সারি কোনো রিকোয়েস্ট দেখতে বা বদলাতে পারবে। আপনার অ্যাপে প্রতিটি কুয়েরি সঠিক WHERE ক্লজ মনে রাখবে—এই আশায় না থেকে, ডাটাবেস নিজেই পলিসিগুলো প্রয়োগ করে।
RLS সবকিছুর জন্য জাদুকরি ঢাল নয়। এটি আপনার স্কিমা ডিজাইনের পরিবর্তে আসবে না, অথেনটিকেশন বদলাবে না, অথবা কাউকে যে ইতিমধ্যেই শক্তিশালী ডাটাবেস রোল (যেমন superuser) আছে তার থেকে রক্ষা করবে না। এটি এমন লজিক ভুলগুলোও নিজে থেকে ধরে রাখবে না যেমন “কেউ একটি সারি আপডেট করতে পারে যেটি সে সিলেক্ট করতে পারছে না”—এমনটা হলে আপনাকে পাঠ্য এবং লেখার উভয় রোলের জন্য পলিসি লিখতে হবে।
কিন্তু যা পাবেন তা একটি শক্ত ভিত:
- প্রতিটি কোড পাথে এক সেট নিয়ম
- নতুন ফিচার শিপ হলে কম “উফস” মুহূর্ত
- SQL-এ দৃশ্যমান হওয়ায় অডিট পরিষ্কার
- যদি API বাগ ফেলে সেক্ষেত্রে ভালো প্রতিরোধ
মোটামুটি কিছু সেটআপ খরচ আছে। ডাটাবেসে “এই ইউজার কে” এবং “কোন টেন্যান্ট” হচ্ছে তা ধারাবাহিকভাবে পাস করার উপায় দরকার, এবং অ্যাপ বড় হলে পলিসিগুলো রক্ষা করতে হবে। SaaS ও অভ্যন্তরীণ টুলের জন্য যেখানে সংবেদনশীল কাস্টমার ডেটা আছে, লাভ অনেক বড়।
সারি-স্তরের নিরাপত্তার মূল ধারণা সহজ ভাষায়
Row-Level Security (RLS) স্বয়ংক্রিয়ভাবে সিদ্ধান্ত নেয় কোন সারিগুলো কোনো কুয়েরি দেখতে বা বদলাতে পারবে। প্রতিটি স্ক্রিন, API এন্ডপয়েন্ট বা রিপোর্টকে নিয়ম “মনে রাখার” উপর নির্ভর না করে, ডাটাবেস নিজেই তা প্রয়োগ করে।
PostgreSQL RLS-এ আপনি পলিসি লিখেন যা প্রতিটি SELECT, INSERT, UPDATE, এবং DELETE-এ চেক হয়। যদি পলিসি বলে “এই ইউজার কেবল টেন্যান্ট A-এর সারিগুলোই দেখতে পারবে,” তাহলে ভুলে থাকা একটি অ্যাডমিন পেজ, নতুন কুয়েরি, বা তাড়াহুড়োতেই করা হটফিক্সও একই গার্ডরেইল পাবে।
RLS GRANT/REVOKE থেকে আলাদা। GRANT নির্ধারণ করে একটি রোল টেবিলে সম্পূর্ণভাবে অ্যাক্সেস পাবে কিনা (অথবা নির্দিষ্ট কলাম)। RLS নির্ধারণ করে টেবিলের ভিতরের কোন সারিগুলো অনুমোদিত। বাস্তবে আপনি প্রায়ই উভয়ই ব্যবহার করবেন: টেবিলে কার toegang সীমাবদ্ধ করতে GRANT, এবং কী অ্যাক্সেস সীমাবদ্ধ করতে RLS।
এটি বাস্তব জটিলতায়ও টেকসই। ভিউসমূহ সাধারণত RLS মেনে চলে কারণ আন্ডারলাইনিং টেবিল অ্যাক্সেস এখনও পলিসি ট্রিগার করে। জয়েন ও সাবকুয়েরিওগুলোও ফিল্টার হয়, তাই একজন ব্যবহারকারী “জয়েন করে” অন্য কারোর ডেটায় ঢুকতে পারবে না। এবং পলিসি যেকোন ক্লায়েন্টের কুয়েরি হলে প্রয়োগ হবে: অ্যাপ কোড, SQL কনসোল, ব্যাকগ্রাউন্ড জব বা রিপোর্টিং টুল—সবখানেই।
RLS উপযুক্ত যখন আপনার শক্ত টেন্যান্ট আইসোলেশন দরকার, একই ডেটা বিভিন্নভাবে কুয়েরি করা হয়, বা অনেক রোল একই টেবিল শেয়ার করে (SaaS ও অভ্যন্তরীণ টুলে সাধারণ)। এটি ছোট অ্যাপ যেখানে একটি বিশ্বাসযোগ্য ব্যাকএন্ড আছে, অথবা এমন ডেটার জন্য যা সংবেদনশীল নয় এবং কখনোই একটি একক নিয়ন্ত্রিত সার্ভিস ছাড়ে না—সেগুলোর জন্য অতিরিক্ত হতে পারে। যখন একাধিক এন্ট্রি পয়েন্ট থাকে (অ্যাডমিন টুল, এক্সপোর্ট, BI, স্ক্রিপ্ট), RLS সাধারণত নিজের খরচ ফেরত দেয়।
শুরু করুন: টেন্যান্ট, রোল ও ডেটা মালিকানা ম্যাপ করে
একটি পলিসি লেখার আগে পরিষ্কার করুন কার মালিকানা কী। PostgreSQL RLS তখনই সবচেয়ে ভালো কাজ করে যখন আপনার ডেটা মডেল ইতিমধ্যেই টেন্যান্ট, রোল ও মালিকানা প্রতিফলিত করে।
টেন্যান্ট থেকে শুরু করুন। বেশিরভাগ SaaS অ্যাপে সহজ নিয়ম হল: গ্রাহক-সম্পর্কিত প্রতিটি শেয়ারড টেবিলে একটি tenant_id থাকে। এতে ইনভয়েসের মতো “স্পষ্ট” টেবলগুলো থাকে, কিন্তু যেগুলো মানুষ ভুলে যায়—অ্যাটাচমেন্ট, কমেন্ট, অডিট লগ, ব্যাকগ্রাউন্ড জব—ওগুলোও شامل।
এরপর বাস্তবে যে রোলগুলো ব্যবহার হয় সেগুলো নামকরণ করুন। সেট ছোট ও মানবরকম রাখুন: owner, manager, agent, read-only। এগুলো ব্যবসায়িক রোল যেগুলো পরে পলিসি চেকের সাথে ম্যাপ হবে (এগুলো ডাটাবেস রোলের সমান নেই)।
তারপর নির্ধারণ করুন রেকর্ডগুলি কিভাবে মালিকানাধীন। কিছু টেবিল একক ব্যবহারকারীর মালিকানাধীন (উদাহরণ: একটি প্রাইভেট নোট)। অন্যগুলো টিম-উপযোগী (উদাহরণ: একটি শেয়ারড ইনবক্স)। পরিকল্পনা ছাড়া দুইটি ধরনের মিশ্রণ করলে পলিসি পড়তে কঠিন এবং বাইপাস করা সহজ হবে।
প্রতিটি টেবিলের জন্য একই প্রশ্নগুলোর উত্তর দিলে ডকুমেন্ট করা সহজ হয়:
- টেন্যান্ট সীমা কি (কোন কলাম এটি এনফোর্স করে)?
- কে সারি পড়তে পারে (রোল এবং মালিকানার ভিত্তিতে)?
- কে সারি তৈরি বা আপডেট করতে পারে (কোন শর্তে)?
- কে সারি ডিলিট করতে পারে (সাধারণত সবচেয়ে কঠোর নিয়ম)?
- কোন এক্সেপশনগুলো অনুমোদিত (সাপোর্ট স্টাফ, অটোমেশন, এক্সপোর্ট)?
উদাহরণ: “Invoices” হয়তো ম্যানেজারদের সমস্ত টেন্যান্ট ইনভয়েস দেখার অনুমতি দেয়, এজেন্টদের তাদের অ্যাসাইন করা কাস্টমারের ইনভয়েস দেখতে দেয়, আর read-only ইউজারদের কেবল দেখা অনুমতি দেয় কিন্তু এডিট করতে পারবে না। পূর্বেই ঠিক করে নিন কোন নিয়ম কঠোর (টেন্যান্ট আইসোলেশন, ডিলিট) এবং কোনগুলো নমনীয় হতে পারে (ম্যানেজারদের অতিরিক্ত ভিজিবিলিটি)। যদি আপনি AppMaster-র মতো কোনো নো-কোড টুলে গড়ে তোলেন, এই ম্যাপিং UI-র প্রত্যাশা এবং ডাটাবেস নিয়মগুলিকে মিলিয়ে রাখতে সাহায্য করবে।
মাল্টি-টেন্যান্ট টেবিলের ডিজাইন প্যাটার্ন
মাল্টি-টেন্যান্ট RLS তখনই ভালো কাজ করে যখন আপনার টেবিলগুলি প্রত্যাশিত রূপে থাকে। যদি প্রতিটি টেবিল টেন্যান্ট আলাদাভাবে সংরক্ষণ করে, তাহলে পলিসিগুলো ধাঁধা হয়ে যাবে। একটি সঙ্গত আকার PostgreSQL RLS কে পড়া, টেস্ট করা এবং সময়ের সাথে সঠিক রাখা সহজ করে।
প্রতিটি যায়গায় একটি টেন্যান্ট শনাক্তকারী নিতে শুরু করুন। UUID সাধারণত প্রচলিত কারণ এগুলো অনুমান করা কঠিন এবং অনেক সিস্টেমে তৈরি করা সহজ। ইন্টিজারও ঠিক আছে, বিশেষ করে অভ্যন্তরীণ অ্যাপের জন্য। স্লাগ (যেমন "acme") মানুষের পক্ষে বন্ধুত্বপূর্ণ, কিন্তু এগুলো পরিবর্তিত হতে পারে, তাই এগুলোকে ডিসপ্লে ফিল্ড হিসেবে বিবেচনা করুন, কোর কী হিসেবে নয়।
টেন্যান্ট-স্কোপড ডেটার জন্য, প্রতিটি টেবিলে একটি tenant_id কলাম যোগ করুন এবং সম্ভব হলে সেটি NOT NULL রাখুন। কোনো সারি টেন্যান্ট ছাড়া থাকতে পারবে—সেটি সাধারণত এক ধরনের ঘ্রাণ: এটি প্রায়শঃই বোঝায় আপনি একই টেবিলে গ্লোবাল এবং টেন্যান্ট ডেটা একসাথে মিশিয়েছেন, যা RLS পলিসি কঠিন এবং ভঙ্গুর করে।
ইন্ডেক্সিং সাধারণ কিন্তু গুরুত্বপূর্ণ। বেশিরভাগ SaaS কুয়েরি প্রথমে tenant_id দিয়ে ফিল্টার করে, তারপর ব্যবসায়িক ফিল্ড যেমন status বা date দিয়ে। একটি ভালো ডিফল্ট হল tenant_id-তে ইন্ডেক্স, এবং উচ্চ-ট্রাফিক টেবিলগুলোর জন্য একটি কম্পোজিট ইন্ডেক্স যেমন (tenant_id, created_at) বা (tenant_id, status) আপনার কমন ফিল্টার অনুযায়ী।
আগেই ঠিক করে নিন কোন টেবিলগুলি গ্লোবাল এবং কোনগুলো টেন্যান্ট-স্কোপড। সাধারণ গ্লোবাল টেবিলগুলির মধ্যে থাকতে পারে countries, currency codes, বা plan definitions। টেন্যান্ট-স্কোপড টেবিলগুলির মধ্যে থাকতে পারে customers, invoices, tickets, এবং টেন্যান্ট যেগুলো মালিকানাধীন।
আপনি যদি এমন নিয়ম চান যা রক্ষণাবেক্ষণযোগ্য থাকে, সেটি সরল রাখুন:
- টেন্যান্ট-স্কোপড টেবিল:
tenant_id NOT NULL, RLS সক্ষম, পলিসিগুলো সবসময়tenant_idচেক করবে। - গ্লোবাল রেফারেন্স টেবিল:
tenant_idনেই, টেন্যান্ট পলিসি নেই, বেশিরভাগ রোলে রিড-ওনলি। - শেয়ারড-কিন্তু-কন্ট্রোলড টেবিল: প্রতিটি কনসেপ্টের জন্য আলাদা টেবিল (গ্লোবাল ও টেন্যান্ট রো এক টেবিলে মিশাবেন না)।
আপনি যদি AppMaster-র মতো টুল দিয়ে তৈরি করেন, এই ধারাবাহিকতা ডেটা মডেলেও ফল দেয়। একবার tenant_id স্ট্যান্ডার্ড ফিল্ড হলে, আপনি একই প্যাটার্নগুলো মডিউলের মধ্যে পুনরায় ব্যবহার করতে পারবেন।
ধাপে ধাপে: আপনার প্রথম টেন্যান্ট পলিসি তৈরি
PostgreSQL RLS-এ একটি ভালো প্রথম জয় হল একটি টেবিল যেখানে কেবল ভর্তুকি বর্তমান টেন্যান্টের ভেতরেই পড়া যাবে। উদ্দেশ্যটা সহজ: কেউ API-তে WHERE ক্লজ ভুলে গেলেও ডাটাবেস অন্য টেন্যান্টের সারি ফিরিয়ে দিতে অস্বীকার করবে।
একটি টেবিল নিয়ে শুরু করুন যাতে tenant_id কলাম আছে:
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
RLS চালু করলে ডিফল্ট আচরণ অনেককে অবাক করে দেয়: যদি একটি রোল RLS-এর অধীন হয় এবং মিল আছে এমন কোন পলিসি না থাকে, SELECT শূন্য সারি ফেরাবে (এবং লেখাগুলো ব্যর্থ হবে)। শুরুতে এটিই আপনি চান।
এখন একটি ন্যূনতম রিড পলিসি যোগ করুন। এই উদাহরণ ধরে নেয় আপনার অ্যাপ লগইনের পরে একটি সেশন ভ্যারিয়েবল app.tenant_id সেট করে:
CREATE POLICY invoices_tenant_read
ON invoices
FOR SELECT
USING (tenant_id = current_setting('app.tenant_id')::uuid);
এরপর, রাইট রুল যোগ করুন। RLS-এ USING কন্ট্রোল করে আপনি কোন বিদ্যমান সারিগুলোকে টাচ করতে পারবেন, আর WITH CHECK কন্ট্রোল করে নতুন মানগুলো আপনি লিখতে পারবেন কি না।
CREATE POLICY invoices_tenant_insert
ON invoices
FOR INSERT
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
CREATE POLICY invoices_tenant_update
ON invoices
FOR UPDATE
USING (tenant_id = current_setting('app.tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
CREATE POLICY invoices_tenant_delete
ON invoices
FOR DELETE
USING (tenant_id = current_setting('app.tenant_id')::uuid);
পলিসিগুলো ডিফল্টভাবে PERMISSIVE, অর্থাৎ যেকোনো একটি পলিসি অনুমোদন করলে অ্যাক্সেস মিলে যাবে। আপনি চাইলে RESTRICTIVE ব্যবহার করতে পারেন যখন সবকটি শর্তই পাশ করতে হবে (উদাহরণ: “কেবল active accounts” এর মত একটি দ্বিতীয় গার্ড)।
পলিসিগুলো ছোট ও রোল-কেন্দ্রিক রাখুন। অনেক OR-এর বড় একটি রুলের বদলে আলাদা পলিসি তৈরি করুন প্রতিটি দর্শকদের জন্য (উদাহরণ: invoices_tenant_read_app_user এবং invoices_tenant_read_support_agent)। এটি টেস্ট করা সহজ, রিভিউ করা সহজ, এবং পরে বদলানো নিরাপদ।
নিরাপদভাবে টেন্যান্ট ও ইউজার কনটেক্সট পাস করা
PostgreSQL RLS কাজ করার জন্য ডাটাবেসকে জানতে হবে “কে কল করছে” এবং “তাঁর টেন্যান্ট কে”। RLS পলিসি কেবল সেই মানগুলোর সঙ্গে সারিগুলো তুলনা করতে পারে যা কুয়েরি সময়ে ডাটাবেস পড়তে পারে, তাই সেই কনটেক্সট সেশন-এ পাস করতে হবে।
একটি প্রচলিত প্যাটার্ন হলো অথেন্টিকেশনের পরে সেশন ভ্যারিয়েবল সেট করা, তারপর পলিসিগুলো current_setting() দিয়ে তা পড়বে। অ্যাপ পরিচয় প্রমাণ করে (উদাহরণ: JWT ভ্যালিডেশন), তারপর ডাটাবেস কানেকশনে টেন্যান্ট ও ইউজার আইডি লিখে।
-- Run once per request (or per transaction)
SELECT set_config('app.tenant_id', '3f2a0c3e-9c7b-4d3f-9c5c-3c5e9c5d1a11', true);
SELECT set_config('app.user_id', '8d9c6b1a-6b6d-4e32-9c0d-2bfe6f6c1111', true);
SELECT set_config('app.role', 'support_agent', true);
-- In a policy
-- tenant_id column is a UUID
USING (tenant_id = current_setting('app.tenant_id', true)::uuid);
তৃতীয় আর্গুমেন্ট true ব্যবহার করলে সেটি বর্তমান ট্রানজ্যাকশনের জন্য লোকাল হয়। যদি আপনি কানেকশন পুলিং ব্যবহার করেন এটি গুরুত্বপূর্ণ: একটি পুলকৃত কানেকশন অন্য অনুরোধে পুনরায় ব্যবহৃত হতে পারে, তাই আপনি চাইবেন না আগের অনুরোধের টেন্যান্ট কনটেক্সট অন্য কোনো অনুরোধে লেগে থাকা।
JWT ক্লেইম থেকে কনটেক্সট পূরণ
যদি আপনার API JWT ব্যবহার করে, ক্লেইমগুলোকে ইনপুট হিসেবে বিবেচনা করুন, বাস্তবতা হিসেবে নয়। টোকেনের সিগনেচার এবং মেয়াদ যাচাই করুন, তারপর কেবল প্রয়োজনীয় ফিল্ডগুলো (tenant_id, user_id, role) সেশন সেটিংসে কপি করুন। ক্লায়েন্টকে সরাসরি এই মানগুলো হেডার বা কুয়েরি প্যারামি হিসেবে পাঠাতে দেবেন না।
ভুল বা অনুপস্থিত কনটেক্সট: ডিফল্টভাবে প্রত্যাখ্যান
এমনভাবে পলিসি ডিজাইন করুন যাতে অনুপস্থিত সেটিংস শূন্য রেকর্ড দেয়।
current_setting('app.tenant_id', true) ব্যবহার করুন যাতে অনুপস্থিত মানগুলো NULL ফেরায়। সঠিক টাইপে কাস্ট করুন (যেমন ::uuid) যাতে ভুল ফরম্যাট দ্রুত ব্যর্থ হয়। এবং যদি টেন্যান্ট/ইউজার কনটেক্সট সেট করা না যায়, তখন অনুরোধ ব্যর্থ করুন—কোনো অনুমান করবেন না।
এভাবে কনটেক্সট বাদ দিয়ে কুয়েরি চালালে বা নতুন এন্ডপয়েন্ট যোগ করলে অ্যাক্সেস কন্ট্রোল ধারাবাহিক থাকে।
ব্যবহারযোগ্য রোল প্যাটার্নগুলো যা রক্ষণযোগ্য থাকে
PostgreSQL RLS পলিসিগুলো পড়তে সহজ রাখতে সবচেয়ে সহজ উপায় হল পরিচয় (identity) ও অনুমতিতে (permissions) আলাদা রাখা। একটি শক্ত ভিত্তি হলো একটি users টেবিল এবং একটি memberships টেবিল যা একটি ইউজারকে একটি টেন্যান্টে ও একটি রোলে (বা একাধিক রোলে) যুক্ত করে। তারপর আপনার পলিসি একটি প্রশ্নের উত্তর দিতে পারে: “এই বর্তমান ইউজারের কি এই সারির জন্য সঠিক মেমবারশিপ আছে?”
রোল নামগুলো বাস্তব কার্যক্রমের সাথে যুক্ত রাখুন, চাকরির টাইটেলের সাথে নয়। যেমন “invoice_viewer” এবং “invoice_approver” সময়ের সাথে ভালো থাকে কারণ পলিসি সরল শব্দে লেখা যায়।
কিছু রোল প্যাটার্ন যা বড় হলে সহজ থাকে:
- Owner-only: সারিটিতে
created_by_user_id(অথবাowner_user_id) থাকে এবং এক্সেস সেই মিল চেক করে। - Team-only: সারিটিতে
team_idথাকে, এবং পলিসি চেক করে ইউজার ঐ টিমের সদস্য কি না একই টেন্যান্টে। - Approved-only: রিড কেবল তখন অনুমোদিত যখন
status = 'approved', এবং রাইট শুধুমাত্র অনুমোদকরা করতে পারে। - Mixed rules: প্রথমে কড়াকড়ি রাখুন, তারপর ছোট উপেক্ষা যোগ করুন (উদাহরণ: “support টিম পড়তে পারবে, কিন্তু কেবল টেন্যান্টের ভেতরেই”)।
ক্রস-টেন্যান্ট অ্যাডমিনরা অনেক দলের জন্য ঝামেলার কারণ। তাদের স্পষ্টভাবে হ্যান্ডেল করুন, একটি লুকানো “superuser” শর্টকাট হিসেবে নয়। একটি আলাদা ধারণা তৈরি করুন যেমন platform_admin (গ্লোবাল) এবং পলিসিতে ইচ্ছাকৃত চেক বাধ্যতামূলক করুন। আরো ভালো হবে—ক্রস-টেন্যান্ট অ্যাক্সেস ডিফল্টভাবে রিড-ওনলি রাখুন এবং রাইটসের জন্য উচ্চতর ব্যারিং প্রয়োজন করুন।
ডকুমেন্টেশন মনে보다 বেশি গুরুত্বপূর্ণ। প্রতিটি পলিসির উপরে একটি সংক্ষিপ্ত মন্তব্য রাখুন যা উদ্দেশ্য ব্যাখ্যা করে, SQL নয়। “Approvers status বদলাতে পারে. Viewers কেবল approved invoices পড়বে.” ছয় মাস পরে সেই মন্তব্য পলিসি বদলানোর সময় নিরাপদ রাখে।
আপনি যদি AppMaster-র মতো নো-কোড টুল ব্যবহার করেন, এই প্যাটার্নগুলো একইভাবে প্রযোজ্য। UI ও API দ্রুত বদলালেও ডাটাবেস নিয়মগুলো স্থিতিশীল থাকে কারণ এগুলো মেমবারশিপ ও পরিষ্কার রোল ব্যাবহারের ওপর নির্ভর করে।
উদাহরণ পরিস্থিতি: ইনভয়েস ও সাপোর্ট সহ একটি সিম্পল SaaS
ধরা যাক একটি ছোট SaaS যা একাধিক কোম্পানিকে সার্ভ করে। প্রতিটি কোম্পানি একটি টেন্যান্ট। অ্যাপে ইনভয়েস (টাকা) এবং সাপোর্ট টিকিট (দিন-দিন সাহায্য) আছে। ইউজাররা হতে পারে এজেন্ট, ম্যানেজার বা সাপোর্ট।
ডেটা মডেল (সরলীকৃত): প্রতিটি ইনভয়েস ও টিকিট রো-তে tenant_id আছে। টিকিটে assignee_user_idও আছে। অ্যাপ লগইনের পরে ডাটাবেস সেশনে বর্তমান টেন্যান্ট ও ইউজার সেট করে।
PostgreSQL RLS দৈনন্দিন ঝুঁকি কিভাবে বদলে দেয় তা এভাবে দেখুন।
টেন্যান্ট A-র একজন ইউজার ইনভয়েস স্ক্রীন খুলে টেন্যান্ট B-র একটি ইনভয়েস আইডি আন্দাজ করে (অথবা UI ভুল করে সেটি পাঠায়)। কুয়েরিটি চললে ও চলে, কিন্তু ডাটাবেস শূন্য সারি ফেরায় কারণ পলিসি চায় invoice.tenant_id = current_tenant_id। সেখানে কোনো “access denied” লিক নেই, কেবল একটি খালি রেজাল্ট।
একই টেন্যান্টের ভিতরে রোলগুলো আরো সংক্ষিপ্ত করে এক্সেস সীমিত করে। একটি ম্যানেজার তাদের টেন্যান্টের সব ইনভয়েস ও টিকিট দেখবে। একটি এজেন্ট কেবল তাদেরকে অ্যাসাইন করা টিকিটগুলোই দেখতে পারবে, সম্ভবত তাদের নিজের ড্রাফটও। API-তে ফিল্টার ঐচ্ছিক হলে এখানে টিমগুলো প্রায়ই ভুল করে।
সাপোর্ট একটি বিশেষ কেস। তারা কাস্টমারকে সাহায্য করতে ইনভয়েস দেখতে পারে, কিন্তু সংবেদনশীল ফিল্ড যেমন amount, bank_account, বা tax_id পরিবর্তন করা উচিত নয়। একটি ব্যবহারিক প্যাটার্ন:
- সাপোর্ট রোলকে ইনভয়েসে
SELECTঅনুমোদন দিন (টেন্যান্ট-স্কোপড)। UPDATEকেবল একটি “safe” পাথ দিয়ে অনুমোদিত করুন (উদাহরণ: একটি ভিউ যা শুধুমাত্র এডিটেবল কলামগুলো প্রকাশ করে, অথবা একটি কঠোর update পলিসি যা সংরক্ষিত ফিল্ড বদলাতে অস্বীকার করে)।
এখন “অযচ্ছাকৃত API বাগ” পরিস্থিতি: একটি এন্ডপয়েন্ট একটি রিফ্যাক্টরের সময় ভুলে গিয়েছে টেন্যান্ট ফিল্টার লাগাতে। RLS না থাকলে এটি ক্রস-টেন্যান্ট ইনভয়েস লিক করতে পারে। RLS থাকলে ডাটাবেস সেশন টেন্যান্টের বাইরের সারি ফিরিয়ে দিতে অস্বীকার করবে, তাই বাগটি একটি ভাঙা স্ক্রীনে পরিণত হবে, ডেটা ব্রিচ-এ নয়।
আপনি যদি AppMaster-এ এই ধরনের SaaS বানান, তবুও এই নিয়মগুলো ডাটাবেসে থাকা উচিত। UI চেকগুলো সহায়ক, কিন্তু যখন কিছু পিছলে যায় তখন ডাটাবেসের নিয়মগুলোই দাঁড় করিয়ে রাখে।
সাধারণ ভুল এবং কিভাবে এড়িয়ে চলবেন
PostgreSQL RLS শক্ত—but ছোটখাট ভুলগুলো সহজে “সিকিউর” কে “অবাক করে দেয়া” বানিয়ে দিতে পারে। বেশিরভাগ সমস্যা তখনই আসে যখন নতুন টেবিল যোগ হয়, একটি রোল বদলে যায়, বা কেউ ভুল ডাটাবেস ইউজার দিয়ে টেস্ট করে।
একটি সাধারণ ব্যর্থতা হল নতুন টেবিলে RLS চালু করা ভুলে যাওয়া। আপনি মূল টেবিলগুলোর জন্য সতর্ক পলিসি লিখে ফেলেছেন, তারপর পরে “notes” বা “attachments” টেবিল যোগ করে তা পূর্ণ অ্যাক্সেস সহ শিপ করে দেন। অভ্যাস করুন: নতুন টেবিল মানে RLS চালু করা, এবং কমপক্ষে একটি পলিসি যোগ করা।
আরেকটি ফাঁদ হল অ্যাকশনের উপর অসমন্বিত পলিসি। একটি পলিসি যা INSERT দেয় কিন্তু SELECT ব্লক করে তৈরি হবার পরে “ডেটা অদৃশ্য হয়ে যাওয়া” মনে হতে পারে। বিপরীতটাও কষ্টদায়ক: ইউজাররা এমন সারি পড়তে পারে যা তারা তৈরি করতে পারেনা, ফলে UI-তে ওরা ওয়ার্কঅ্যারাউন্ড করে। ফ্লো হিসেবে চিন্তা করুন: “create then view,” “update then re-open,” “delete then list.”
SECURITY DEFINER ফাংশন নিয়ে সাবধান থাকুন। এগুলো ফাংশনের মালিকের প্রিভিলেজ নিয়ে চলে, যা RLS বাইপাস করতে পারে যদি আপনি সতর্ক না হন। এগুলো ছোট রাখুন, ইনপুট যাচাই করুন, এবং ডায়নামিক SQL এড়ান না হয় যদি সত্যিই প্রয়োজন না হয়।
অ্যাপ-সাইড ফিল্টারিং-এ ভর করা আরেকটি ঝুঁকি। API প্রথমদিকে সঠিক হলেও নতুন এন্ডপয়েন্ট, ব্যাকগ্রাউন্ড জব, অ্যাডমিন স্ক্রিপ্ট যোগ হতে থাকবে। যদি ডাটাবেস রোল সবকিছুই পড়তে পারে, তবে একদিন কিছু লিক করবে।
সমস্যা ধরতে বাস্তবসম্মত চেক রাখুন:
- আপনার প্রোডাকশন অ্যাপ যে DB রোল ব্যবহার করে সেটিই ব্যবহার করে টেস্ট করুন, আপনার ব্যক্তিগত admin ইউজার নয়।
- প্রতিটি টেবিলের জন্য একটি নেগেটিভ টেস্ট যোগ করুন: অন্য টেন্যান্টের একজন ইউজার শূন্য সারি দেখতে পাবে।
- নিশ্চিত করুন প্রতিটি টেবিল প্রত্যাশিত অ্যাকশনগুলো সাপোর্ট করে:
SELECT,INSERT,UPDATE,DELETE। SECURITY DEFINERব্যবহারের রেকর্ড রাখুন এবং কেন লাগছে তা ডকুমেন্ট করুন।- প্রতিটি নতুন টেবিলে RLS সক্ষমতার চেক এবং মাইগ্রেশন চেকলিস্টে অন্তর্ভুক্ত করুন।
উদাহরণ: যদি একটি সাপোর্ট এজেন্ট একটি ইনভয়েস নোট তৈরি করে কিন্তু পরে সেটি পড়তে না পারে, সাধারণত এটা INSERT পলিসি আছে কিন্তু SELECT পলিসি মিসিং বা সেশন কনটেক্সট সেট হচ্ছে না—এই কারণে ঘটে।
RLS সেটআপ যাচাই করার দ্রুত চেকলিস্ট
Row-Level Security রিভিউতে সঠিক মনে হলেও বাস্তব ব্যবহারে ব্যর্থ হতে পারে। যাচাই পড়ার চেয়ে চেষ্টা করে ভাঙ্গতে করা বেশি কার্যকর। আপনার অ্যাপ যেভাবে ব্যবহার হবে সেভাবে টেস্ট করুন, আশার মতো নয়।
প্রথমে একটি ছোট সেট টেস্ট আইডেন্টিটিস তৈরি করুন। কমপক্ষে দুটি টেন্যান্ট (Tenant A এবং Tenant B) ব্যবহার করুন। প্রতিটি টেন্যান্টে একটি সাধারণ ইউজার এবং একটি অ্যাডমিন/ম্যানেজার যোগ করুন। যদি আপনি “support agent” বা “read-only” রোল সাপোর্ট করেন, তাদেরও যোগ করুন।
তারপর PostgreSQL RLS চাপ দিয়ে পরীক্ষা করুন এই ছোট, পুনরাবৃত্তি পরীক্ষা সেট দিয়ে:
- প্রতিটি রোলে মূল অপারেশনগুলো চালান: সারির তালিকা, একটি সিঙ্গেল রো id দিয়ে ফেচ, ইনসার্ট, আপডেট, ডিলিট। প্রতিটি অপারেশনের জন্য “অনুমোদিত” এবং “অন্তর্ঙ্গভাবে ব্লক করা” কেস ট্রাই করুন।
- টেন্যান্ট সীমানা প্রমাণ করুন: Tenant A থেকে Tenant B-র ডেটা পড়ে বা পরিবর্তন করার চেষ্টা করুন—শূন্য সারি বা পারমিশন এরর পাওয়া উচিত, কখনো “কিছু সারি” নয়।
- জয়েনগুলোতে লিক আছে কি না টেস্ট করুন: প্রোটেক্টেড টেবিলগুলোকে অন্য টেবিলে জয়েন করুন (রিলেটেড টেবিল সহ)। নিশ্চিত করুন একটি জয়েন ফোরেন কি বা ভিউয়ের মাধ্যমে আরেক টেন্যান্টের সম্পর্কিত সারি টেনে আনতে পারে না।
- অনুপস্থিত বা ভুল কনটেক্সট অ্যাক্সেস প্রত্যাখ্যান করে কি না দেখুন: আপনার অ্যাপ যেখানে প্রতিটি অনুরোধে সেশন কনটেক্সট সেট করে—সেটা ক্লিয়ার করে পুনরায় চেষ্টা করুন। “কোন কনটেক্সট” থাকলে বন্ধ হওয়া উচিত। একইভাবে একটি ভুল টেন্যান্ট আইডি দিয়ে চেষ্টা করুন।
- প্রাথমিক পারফরম্যান্স নিশ্চিত করুন: কুয়েরি প্ল্যান দেখুন এবং নিশ্চিত করুন ইন্ডেক্সগুলো আপনার টেন্যান্ট ফিল্টার প্যাটার্নকে সমর্থন করে (সাধারণত
tenant_idএবং যেটাতে আপনি সর্ট বা সার্চ করেন)।
কোনো টেস্ট আপনাকে অবাক করলে, আগে পলিসি বা কনটেক্সট-সেটিং ঠিক করুন। UI বা API-তে প্যাচ দেবেন না এবং আশা করবেন ডাটাবেস নিয়মগুলো “প্রায় সবসময়” ধরে রাখবে না।
পরবর্তী ধাপ: নিরাপদ করে রোলআউট এবং ধারাবাহিকতা রাখা
PostgreSQL RLS-কে একটি সেফটি সিস্টেম হিসাবে বিবেচনা করুন: এটি সাবধানে চালু করুন, নিয়মিত যাচাই করুন, এবং নিয়মগুলো সহজ রাখুন যাতে আপনার দল এগুলো মেনে চলে।
ছোট থেকে শুরু করুন। সেসব টেবিল বেছে নিন যেখানে লিক ক্ষতিকর হবে (payments, invoices, HR data, customer messages) এবং সেগুলোতে প্রথমে RLS চালু করুন। প্রথম সাফল্য বড় রোলআউটের চাইতে ভালো।
একটি বাস্তবসম্মত রোলআউট অর্ডার সাধারণত এই রকম হয়ে থাকে:
- প্রথমে মূল "owned" টেবিল (যেখানে সারি স্পষ্টভাবে একটি টেন্যান্টের)
- ব্যক্তিগত ডেটা (PII) থাকা টেবিল
- টেন্যান্ট দ্বারা ফিল্টার করা শেয়ারড টেবিল (রিপোর্ট, অ্যানালিটিক্স)
- জয়েন টেবিল ও এজ কেস (many-to-many সম্পর্ক)
- বাকি সবকিছু যখন বেসিক স্থিতিশীল থাকে
টেস্টিং অবশ্যই বাধ্যতামূলক রাখুন। অটোমেটেড টেস্টগুলো বিভিন্ন টেন্যান্ট ও রোল হিসেবে একই কুয়েরিগুলো চালিয়ে নিশ্চিত করবে কি পরিবর্তন হয়েছে। “অনুমোদন করা উচিত” এবং “প্রত্যাখ্যাত হওয়া উচিত” উভয় চেকই রাখুন, কারণ সবচেয়ে ব্যয়বহুল বাগগুলো হলো নির্বিচারে অতিরিক্ত অনুমতি।
আপনার অনুরোধ প্রবাহে একটি স্পষ্ট জায়গা রাখুন যেখানে সেশন কনটেক্সট সবকিছুর আগে সেট হবে। tenant id, user id, এবং role একবার, দ্রুত প্রয়োগ করুন এবং পরে অনুমান করবেন না। যদি আপনি ট্রানজ্যাকশনের মাঝখানে কনটেক্সট সেট করেন, একদিন একটি কুয়েরি চালবে যেখানে মান নেই বা স্টেল হবে।
যখন আপনি AppMaster দিয়ে বানান, তখন আপনার জেনারেটেড ব্যাকএন্ড API এবং PostgreSQL পলিসিগুলোর মধ্যে সামঞ্জস্যের পরিকল্পনা করুন। সেশন ভ্যারিয়েবল প্রতিটি এন্ডপয়েন্টে একই ভাবে পাস করার স্ট্যান্ডার্ড রাখুন যাতে পলিসিগুলো সব জায়গায় একইভাবে আচরণ করে। যদি আপনি AppMaster at appmaster.io ব্যবহার করেন, RLS-কে টেন্যান্ট আইসোলেশনের চূড়ান্ত অথরিটি হিসেবে বিবেচনা করা এখনও জরুরি, যদিও UI-তে আপনি এক্সেস গেট করতে পারেন।
শেষে, ফেইলগুলো মনিটর করুন। অথরাইজেশন ব্যর্থতা ভূমিকা হিসেবে উপকারী সিগন্যাল দেয়, বিশেষ করে রোলআউটের পরে। বারবারের ডিনায়ালগুলো ট্র্যাক করুন এবং তা একটি বাস্তব আক্রমণ, ভাঙা ক্লায়েন্ট ফ্লো, নাকি অত্যন্ত কঠোর পলিসি—এইগুলো শনাক্ত করুন।
একটি ছোট অভ্যাস তালিকা যা RLS-কে সুস্থ রাখে:
- ডিফল্ট-অস্বীকৃত মানসিকতা, প্রয়োজনীয় ব্যতিক্রম ইচ্ছাকৃতভাবে যোগ করা
- পরিষ্কার পলিসি নাম (টেবিল + অ্যাকশন + দর্শক)
- পলিসি পরিবর্তনগুলো কোড পরিবর্তনের মতো রিভিউ করা
- রোলআউটে ডিনায়াল লগ করা এবং রিভিউ করা
- প্রতিটি নতুন টেবিলে একটি ছোট টেস্ট সেট যোগ করা


