8 মিনিট

SaaS-এর জন্য PostgreSQL সারি-স্তরের নিরাপত্তা: কার্যকর নীতিগুলো

PostgreSQL-এর সারি-স্তরের নিরাপত্তা (RLS) SaaS-এ টেন্যান্ট বিচ্ছিন্নতা প্রয়োগ করতে সাহায্য করে। কখন ব্যবহার করবেন, কিভাবে পলিসি লিখবেন, এবং কি এড়িয়ে চলবেন তা শিখুন।

SaaS-এর জন্য PostgreSQL সারি-স্তরের নিরাপত্তা: কার্যকর নীতিগুলো

SaaS অ্যাপে RLS যেটা সমাধান করতে চায়

SaaS অ্যাপে সবচেয়ে বিপজ্জনক সিকিউরিটি বাগটা হল এমন একটায় যে স্কেল করলে বেরিয়ে আসে। আপনি সাধারণত একটি সহজ নিয়ম দিয়ে শুরু করেন — “ব্যবহারকারীরা শুধু তাদের টেন্যান্টের ডেটাই দেখতে পারবে” — তারপর দ্রুত একটি নতুন এন্ডপয়েন্ট পাঠান, একটি রিপোর্টিং কুয়েরি যোগ করেন, অথবা এমন একটি জয়েন আসে যা নীরবে চেকটা বাইপাস করে দেয়।

কেবল অ্যাপে ভিত্তিক অনুমোদন চাপ বাড়লে ভেঙে পড়ে কারণ নিয়মগুলো ছড়িয়ে যায়। এক কন্ট্রোলার tenant_id যাচাই করে, অন্যটি সদস্যত্ব চেক করে, একটা ব্যাকগ্রাউন্ড জব ভুলে যায়, এবং একটি “অ্যাডমিন এক্সপোর্ট” পথ মাসের পর মাস “অস্থায়ী” থাকতেই থাকে। এমনকি সাবধানী দলগুলোও একটা জায়গা মিস করে ফেলে।

PostgreSQL সারি-স্তরের নিরাপত্তা (RLS) একটি নির্দিষ্ট সমস্যা সমাধান করে: এটি ডেটাবেসকে প্রয়োগ করায় কোন সারিগুলো একটি অনুরোধের জন্য দৃশ্যমান হবে। মানসিক মডেলটি সহজ: প্রতিটি SELECT, UPDATE, এবং DELETE স্বয়ংক্রিয়ভাবে নীতির দ্বারা ফিল্টার করা হয়, ঠিক যেভাবে প্রতিটি অনুরোধ অথেন্টিকেশন মিডলওয়্যারের দ্বারাও ফিল্টার করা হয়।

“সারি” অংশটা গুরুত্বপূর্ণ। RLS সবকিছু রক্ষা করে না:

  • এটি নিজে কলামগুলি লুকায় না (ভিউ বা কলাম প্রিভিলেজ ব্যবহার করুন)।

  • এটি অনিরাপদ ফাংশনকে নিরাপদ করে না (উন্নত অধিকার নিয়ে চলা একটা ফাংশন এখনও ডেটা ফাঁস করতে পারে)।

  • এটি ব্যবসায়িক নিয়ম যাচাই করে না (উদাহরণ: “শুধু মালিকরাই বিলিং সেটিংস পরিবর্তন করতে পারবে”)।

একটি বাস্তব উদাহরণ: আপনি একটি ড্যাশবোর্ডের জন্য প্রোজেক্ট তালিকা এনডপয়েন্ট যোগ করেন যা ইনভয়েসের সাথে জয়েন করে। কেবল অ্যাপ-ভিত্তিক অথরাইজেশনে, projects-এ টেন্যান্ট ফিল্টার করা সহজ, কিন্তু invoices ফিল্টার করা ভুলে যাওয়া বা এমন কী-তে জয়েন করা যা টেন্যান্ট লঙ্ঘন করে এমনসব ভুল হয়। RLS থাকলে উভয় টেবিল টেন্যান্ট বিচ্ছিন্নতা প্রয়োগ করতে পারে, তাই কুয়েরিটি নিরাপদভাবে ব্যর্থ করে ডেটা ফাঁস না করে।

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

কখন RLS অনুমোদন সহজ করে (আর কখন এটা যন্ত্রণার কারণ)

RLS কষ্টসাধ্য মনে হতে পারে যতক্ষণ না আপনার অ্যাপ কয়েকটি এন্ডপয়েন্ট ছাড়িয়ে wächst। যদি আপনার কঠোর টেন্যান্ট সীমানা থাকে এবং অনেক কুয়েরি-পাথ থাকে (তালিকা স্ক্রিন, সার্চ, এক্সপোর্ট, অ্যাডমিন টুল), ডেটাবেসে নিয়ম রাখলে আপনাকে প্রতিবার একই ফিল্টার যোগ করতে মনে রাখার দরকার পড়ে না।

RLS মিলবে যখন নিয়মটা বিরক্তিকরভাবে সাধারণ এবং সার্বজনীন: “একজন ব্যবহারকারী শুধু তাদের টেন্যান্টের সারিগুলোই দেখতে পারবে” বা “একজন ব্যবহারকারী শুধু তারা যার সদস্য তার প্রোজেক্টগুলোই দেখতে পারবে।” এই সেটআপগুলোতে, নীতিগুলো ভুল হওয়ার সম্ভাবনা কমায় কারণ প্রতিটি SELECT, UPDATE, এবং DELETE একই গেট দিয়ে যায়, এমনকি পরে কোনো কুয়েরি যোগ হলে ও সে ক্ষেত্রে।

এটি রিড-হেভি অ্যাপগুলিতেও সাহায্য করে যেখানে ফিল্টারিং লজিক অভিন্ন থাকে। যদি আপনার API-তে ইনভয়েস লোড করার 15টি ভিন্ন উপায় থাকে (স্ট্যাটাস, তারিখ, কাস্টমার, সার্চ দ্বারা), RLS আপনাকে প্রতিটি কুয়েরিতে টেন্যান্ট ফিল্টার পুনরায় ইমপ্লিমেন্ট করা বন্ধ করে ফিচারের দিকে মনোযোগ দিতে দেয়।

RLS কষ্ট দেয় যখন নিয়মগুলো সারি-ভিত্তিক নয়। প্রতি-ফিল্ড নিয়ম যেমন “তুমি বেতন দেখতে পারবে কিন্তু বোনাস নয়” বা “এই কলাম HR না হলে মাস্ক কর” প্রায়ই জটিল SQL এবং রক্ষণাবেক্ষণে কষ্টদায়ক ব্যতিক্রমে পরিণত হয়।

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

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

যদি আপনি এমন টুলিং ব্যবহার করেন যা ব্যাকএন্ড জেনারেট করে, এটি ডেলিভারি দ্রুত করতে পারে, কিন্তু এটি রোল, টেস্ট, এবং একটি সরল টেন্যান্ট মডেলের প্রয়োজন দূর করে না। (উদাহরণস্বরূপ, Koder.ai Go এবং PostgreSQL ব্যবহার করে জেনারেটেড ব্যাকএন্ড দেয়, এবং তবুও আপনি RLS ইচ্ছাকৃতভাবে ডিজাইন করতে চান, পরে “স্প্রিঙ্কল” করা নয়)।

RLS নীতিগুলো পরিচালনাযোগ্য করে এমন ডেটা মডেল মৌলিকগুলো

RLS সহজ হয় যখন আপনার স্কিমা ইতিমধ্যেই পরিষ্কারভাবে বলে দেয় কে কি মালিক। যদি আপনি ঝাপসা মডেল দিয়ে শুরু করে এবং “নীতিতে ঠিক করে নেব” ভাবেন, আপনি সাধারণত ধীর কুয়েরি ও বিভ্রান্তিকর বাগ পাবেন।

যেখানে প্রযোজ্য সেখানে টেন্যান্ট কী রাখুন

একটি টেন্যান্ট কী (যেমন org_id) পিক করুন এবং ধারাবাহিকভাবে ব্যবহার করুন। বেশিরভাগ টেন্যান্ট-অধিকারভুক্ত টেবিলে এটি থাকা উচিত, এমনকি তারা অন্য টেবিলকেও রেফারেন্স করলেও। এটি পলিসিগুলোর ভিতরে জয়েন এড়ায় এবং USING চেকগুলো সহজ রাখে।

ব্যবহারিক নিয়ম: যদি একটি সারি গ্রাহক ক্যানসেল করলে অদৃশ্য হয়ে যাওয়া উচিত, তাহলে সম্ভবত তাতে org_id থাকা দরকার।

সদস্যত্বগুলি স্পষ্টভাবে মডেল করুন

RLS নীতিগুলো সাধারণত এক প্রশ্নের উত্তর দেয়: “এই ব্যবহারকারী কি এই org-এর সদস্য, এবং তারা কী করতে পারে?” এটি অ্যাড-হক কলাম থেকে প্রায়ই অনুমান করা কঠিন।

কোর টেবিলগুলো ছোট এবং নিরব রাখুন:

  • users (প্রতি ব্যক্তির একটি সারি)
  • orgs (প্রতি টেন্যান্ট একটি সারি)
  • org_memberships (user_id, org_id, role, status)
  • ঐচ্ছিক: per-project access-এর জন্য project_memberships

এর ফলে আপনার নীতিগুলো একটি ইনডেক্স করা লুকআপ দিয়ে সদস্যত্ব চেক করতে পারে।

শেয়ার হওয়া রেফারেন্স ডেটা আলাদা রাখুন

সবকিছুতেই org_id লাগবে না। দেশ, প্রোডাক্ট ক্যাটাগরি, বা প্ল্যান টাইপের মতো রেফারেন্স টেবিলগুলো সাধারণত সব টেন্যান্টের জন্য শেয়ার করা হয়। এগুলোকে বেশিরভাগ রোলে রিড-ওয়ানলি করে রাখুন এবং এক org-এর সাথে বেঁধে রাখবেন না।

টেন্যান্ট-অধিকারভুক্ত ডেটা (প্রোজেক্ট, ইনভয়েস, টিকেট) শেয়ার্ড টেবিল থেকে টেন্যান্ট-নির্দিষ্ট বিবরণ টেনে নিয়ে না করা ভাল। শেয়ার্ড টেবিলগুলোকে মিনিমাল ও স্থিতিশীল রাখুন।

FK, ক্যাসকেড, এবং ইনডেক্সিং

RLS-এও ফোরেন কি কাজ করে, তবে ডিলেটগুলো আপনাকে চমকে দিতে পারে যদি ডিলেট করতে থাকা রোল ডিপেনডেন্ট সারিগুলো “দেখে” না। ক্যাসকেড পরিকল্পনা করে নিন এবং বাস্তব ডিলিট ফ্লো টেস্ট করুন।

যেসব কলামে আপনার নীতিগুলো ফিল্টার করে সেগুলো ইনডেক্স করুন, বিশেষ করে org_id এবং সদস্যত্ব কী। এমন নীতি যা দেখতে “WHERE org_id = ...” তার উচিত মিলিয়ন সারি হলে ফুল-টেবিল স্ক্যান না হওয়া।

RLS নীতিগুলো কীভাবে কাজ করে, ভয় ছাড়া

RLS হল প্রতি-টেবিল একটি সুইচ। একবার এনেব্ল করলে PostgreSQL আপনার অ্যাপ কোডকে টেন্যান্ট ফিল্টার মনে রাখার উপর বিশ্বাস করা বন্ধ করে দেয়। প্রতিটি SELECT, UPDATE, এবং DELETE নীতিগুলোর দ্বারা ফিল্টার হয়, এবং প্রতিটি INSERTUPDATE নীতির মাধ্যমে ভ্যালিডেট হয়।

সবচেয়ে বড় মানসিক পরিবর্তন: RLS চালু হলে যেসব কুয়েরি আগে ডেটা ফিরিয়ে দিত এখন ত্রুটি ছাড়াই শূন্য সারি ফিরিয়ে দিতে পারে। এটা PostgreSQL-এর অ্যাক্সেস কন্ট্রোল।

নীতিগুলো বাস্তবে কী করে

পলিসি হলো টেবিলের সাথে জোড়া ছোট নিয়ম। এগুলো দুইটি চেক ব্যবহার করে:

  • USING হলো রিড ফিল্টার। যদি একটি সারি USING-এ মেলে না, তা SELECT-এর জন্য অদৃশ্য এবং UPDATE বা DELETE-এ টার্গেট করা যাবে না।
  • WITH CHECK হলো রাইট গেট। এটি নির্ধারণ করে কোন নতুন বা পরিবর্তিত সারি INSERT বা UPDATE-এ অনুমোদিত।

একটি সাধারণ SaaS প্যাটার্ন: USING নিশ্চিত করে আপনি শুধু আপনার টেন্যান্টের সারিগুলো দেখেন, এবং WITH CHECK নিশ্চিত করে আপনি অনুমান করে কোনো টেন্যান্ট আইডি লিখে অন্য কারো টেন্যান্টে সারি ঢুকাতে পারবেন না।

PERMISSIVE বনাম RESTRICTIVE, এক বাক্যে

পরে আরও নীতিগুলো যোগ করলে এটার গুরুত্ব বাড়ে:

  • PERMISSIVE (ডিফল্ট): যদি কোনো নীতি কোনও সারিকে অনুমোদন করে, সারিটি অনুমোদিত।
  • RESTRICTIVE: শুধুমাত্র যদি সব restrictive নীতি সেটি অনুমোদন করে তখনই সারিটি অনুমোদিত হবে (permissive আচরণের উপরে)।

যদি আপনি টেন্যান্ট মিল+রোল চেক+প্রোজেক্ট সদস্যত্ব মত লেয়ার করতে চান, restrictive নীতিগুলো উদ্দেশ্যকে স্পষ্ট করতে পারে, কিন্তু ভুল করে একটি শর্ত বাদ দিলে এটি নিজেই আপনাকে আউট করে দিতে পারে।

Postgres কিভাবে জানে কে ব্যবহারকারী

RLS-কে একটি নির্ভরযোগ্য “কে কল করছে” মান দরকার। সাধারণ অপশনগুলো:

  • প্রতি অনুরোধে সেট করা সেশন ভ্যারিয়েবল (উদাহরণ: app.user_id এবং app.tenant_id)।
  • JWT ক্লেইমকে API লেয়ার দ্বারা সেশন সেটিংসে ম্যাপ করা।
  • রোল সোয়িচিং (SET ROLE ...) per request, যা কাজ করতে পারে কিন্তু অপারেশনাল ওভারহেড বাড়ায়।

একটি পদ্ধতি বেছে নিয়ে সারাদিন সব জায়গায় তা প্রয়োগ করুন। পরিষেবা জুড়ে ভিন্ন পরিচয় সূত্র মিশালে দ্রুত ভুল হয়।

ডিবাগে সাহায্যের জন্য নীতি নামকরণ

একটি সম্ভাব্য কনভেনশন ব্যবহার করুন যাতে স্কিমা ডাম্প এবং লগ পাঠযোগ্য থাকে। উদাহরণ: {table}__{action}__{rule}, যেমন projects__select__tenant_match

ধাপে ধাপে: একটি টেবিলে RLS যোগ করুন এবং কাজ করছে প্রমাণ করুন

অর্জিত ক্রেডিট নিয়ে তৈরি চালিয়ে যান
আপনার বিল্ড সম্পর্কে কনটেন্ট তৈরি করে ক্রেডিট উপার্জন করুন এবং আপনার অ্যাপে পুনরায় কাজ চালিয়ে যান।

আপনি যদি RLS-এ নতুন হন, একটি টেবিল দিয়ে শুরু করুন এবং ছোট একটি প্রুফ তৈরি করুন। লক্ষ্য হল পুরো কভারেজ নয়—লক্ষ্য হল ডাটাবেস এমনভাবে কেটে দিয়েছে যে অ্যাপ বাগ হলেও ক্রস-টেন্যান্ট অ্যাক্সেস বন্ধ থাকে।

অনুশীলনের জন্য একটি ছোট টেবিল

ধরুন একটি সরল projects টেবিল। প্রথমে tenant_id এমনভাবে যোগ করুন যাতে লেখা ভেঙে না যায়।

ALTER TABLE projects ADD COLUMN tenant_id uuid;

-- Backfill existing rows (example: everyone belongs to a default tenant)
UPDATE projects SET tenant_id = '11111111-1111-1111-1111-111111111111'::uuid
WHERE tenant_id IS NULL;

ALTER TABLE projects ALTER COLUMN tenant_id SET NOT NULL;

এরপর মালিকানা ও অ্যাক্সেস আলাদা করুন। একটি প্রচলিত প্যাটার্ন: একটি রোল টেবিলগুলোর মালিক হয় (app_owner), আর অন্য রোল API-র জন্য ব্যবহৃত হয় (app_user)। API রোল টেবিল মালিক না হওয়া উচিত, না হলে এটি পলিসি বাইপাস করতে পারে।

ALTER TABLE projects OWNER TO app_owner;
REVOKE ALL ON projects FROM PUBLIC;
GRANT SELECT, INSERT, UPDATE, DELETE ON projects TO app_user;

এখন সিদ্ধান্ত নিন অনুরোধ কিভাবে Postgres-কে বলে কোন টেন্যান্ট সে সার্ভ করছে। এক সহজ পদ্ধতি হলো অনুরোধ-স্কোপড সেটিং। আপনার অ্যাপ ট্রানজেকশন খুলার পর সেটি সেট করে।

-- inside the same transaction as the request
SELECT set_config('app.current_tenant', '22222222-2222-2222-2222-222222222222', true);

RLS চালু করে শুরু করুন রিড অ্যাক্সেস দিয়ে।

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

CREATE POLICY projects_tenant_select
ON projects
FOR SELECT
TO app_user
USING (tenant_id = current_setting('app.current_tenant')::uuid);

দুইটি ভিন্ন টেন্যান্ট চেষ্টা করে এবং সারি গণনা পরিবর্তিত হয় কি না দেখে প্রমাণ করুন এটা কাজ করছে।

লেখার নিয়ম যোগ করা (WITH CHECK)

রিড পলিসি লেখাকে রক্ষা করে না। WITH CHECK যোগ করুন যাতে INSERT এবং UPDATE-এ কেউ ভুল টেন্যান্টে সারি ঢুকিয়ে না দেয়।

CREATE POLICY projects_tenant_write
ON projects
FOR INSERT, UPDATE
TO app_user
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);

ব্যবহার করে দ্রুত যাচাই (সহ ত্রুটি) করার সহজ উপায় হল একটি ছোট SQL স্ক্রিপ্ট রাখা যা আপনি প্রতিটি মাইগ্রেশনের পরে পুনরায় চালাতে পারেন:

  • BEGIN; SET LOCAL ROLE app_user;
  • SELECT set_config('app.current_tenant', '\u003ctenant A\u003e', true); SELECT count(*) FROM projects;
  • INSERT INTO projects(id, tenant_id, name) VALUES (gen_random_uuid(), '\u003ctenant B\u003e', 'bad'); (ফেল হওয়া উচিত)
  • UPDATE projects SET tenant_id = '\u003ctenant B\u003e' WHERE ...; (ফেল হওয়া উচিত)
  • ROLLBACK;

আপনি যদি সেই স্ক্রিপ্ট চালাতে পারেন এবং প্রতিবার একই ফল পান, তাহলে RLS অন্যান্য টেবিলে বিস্তার করার আগে আপনার কাছে নির্ভরযোগ্য বেসলাইন আছে।

SaaS অ্যাপে পুনরায় ব্যবহারযোগ্য পলিসি প্যাটার্নগুলো

অধিকাংশ দল একই অনুমোদন চেক বারবার করতে করতে ক্লান্ত হয়ে RLS গ্রহণ করে। ভালো খবর হলো যে প্রয়োজনীয় পলিসি আকারগুলো সাধারণত ধারাবাহিক।

মালিক সারি বনাম সদস্য সারি

কিছু টেবিল স্বাভাবিকভাবেই একজন ব্যবহারকারীর মালিকানাধীন (নোট, API টোকেন)। অন্যগুলোর মালিকানা টেন্যান্টভিত্তিক যেখানে অ্যাক্সেস সদস্যত্বের উপর নির্ভর করে। এগুলোকে আলাদা প্যাটার্ন হিসেবে দেখুন।

মালিক-শুধু ডেটার জন্য, পলিসিগুলো প্রায়ই চেক করে created_by = app_user_id()। টেন্যান্ট ডেটার জন্য, পলিসিগুলো প্রায়ই চেক করে ব্যবহারকারী কি org-এর একটি সদস্য আছে কি না।

নীতিগুলো পড়তে সহজ রাখতে পরিচয়কে ছোট SQL হেল্পারে কেন্দ্র করে পুনঃব্যবহার করুন:

-- Example helpers
create function app_user_id() returns uuid
language sql stable as $$
  select current_setting('app.user_id', true)::uuid
$$;

create function app_is_admin() returns boolean
language sql stable as $$
  select current_setting('app.is_admin', true) = 'true'
$$;

রিড নিয়মগুলোকে লেখার নিয়ম থেকে আলাদা রাখুন

রিডগুলো প্রায়ই লেখার থেকে বেশি বিস্তৃত হয়। উদাহরণ: যে কেউ org-এর সদস্য হলে প্রোজেক্ট SELECT করতে পারে, কিন্তু শুধু সম্পাদকরা UPDATE পারে, এবং শুধুমাত্র মালিকেরা DELETE করতে পারে।

স্পষ্ট রাখুন: একটি SELECT-এর জন্য এক পলিসি, INSERT/UPDATE-এর জন্য WITH CHECK সহ একটি পলিসি (রোল অনুযায়ী), এবং DELETE-এর জন্য প্রায়ই আনয় মাতৃক কড়া একটি পলিসি।

RLS বন্ধ না করে অ্যাডমিন ওভাররাইড

“অ্যাডমিনের জন্য RLS বন্ধ করে দিন” এড়িয়ে চলুন। বদলে নীতিগুলোর মধ্যে একটি এস্কেপ হ্যাচ যোগ করুন, যেমন app_is_admin() — এতে করে আপনি অবিচ্ছিন্ন সেবা রোলকে দুর্ঘটনাক্রমে পূর্ণ অ্যাক্সেস দেবেন না।

সফট ডিলিট ও স্ট্যাটাস ফ্ল্যাগ

আপনি যদি deleted_at বা status ব্যবহার করেন, তা SELECT পলিসিতে বানানো রাখুন (deleted_at is null)। নাহলে কেউ ফ্ল্যাগগুলো উল্টে উধাও হওয়া সারি “পুনরুজ্জীবিত” করতে পারবে যেগুলো অ্যাপ চেয়েছিল স্থায়ীভাবে শেষ।

UPSERT: WITH CHECK-কে সদয় রাখুন

INSERT ... ON CONFLICT DO UPDATE-এর ক্ষেত্রে WITH CHECK লিখার পরে থাকা সারির জন্য সন্তুষ্ট হতে হবে। যদি আপনার পলিসি চায় created_by = app_user_id() নিশ্চিত করা, তাহলে নিশ্চিত করুন আপসার্টে ইনসার্টের সময় created_by সেট হচ্ছে এবং আপডেটে তা ওভাররাইট করা হচ্ছে না।

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

সাধারণ RLS ফুট-গানস যা ডিবাগিং কষ্টদায়ক করে

RLS দারুণ কাজ করে যতক্ষণ না ছোট একটি বিশদ PostgreSQL-কে “এপস-ভিত্তিকভাবে” ডেটা সাকার বা দেখায় বলে মনে হয়। নীচের ভুলগুলোই সবচেয়ে সময় নষ্ট করে।

নীরব টেন্যান্ট লিক ঘটানোর ফুট-গানস

প্রথম ফাঁদ হল INSERTUPDATE-এ WITH CHECK ভুলে যাওয়া। USING কেবল আপনি কি দেখেন তা নিয়ন্ত্রণ করে, আপনি কি তৈরি করতে পারেন তা নয়। WITH CHECK ছাড়া একটি অ্যাপ বাগ ভুল টেন্যান্টে সারি লিখতে পারে, এবং আপনি তা লক্ষ্য করবেন না কারণ একই ব্যবহারকারী সেটি পড়তে পারবে না।

আরেকটি সাধারণ লিক হল “লিকি জয়েন”। আপনি সঠিকভাবে projects-এ ফিল্টার করেন, তারপর invoices, notes, বা files-এ জয়েন করেন যারা একইভাবে সুরক্ষিত নয়। সমাধানটি কঠোর কিন্তু সরল: টেন্যান্ট ডেটা প্রকাশ করতে পারে এমন প্রতিটি টেবিলের নিজস্ব পলিসি থাকা দরকার, এবং ভিউগুলো কেবল এক টেবিল নিরাপদ হওয়ার ওপর নির্ভর করা উচিত নয়।

সাধারন ব্যর্থতার প্যাটার্নগুলো দ্রুত দেখা যায়:

  • একটি রিড পলিসি আছে, কিন্তু লেখার পলিসি WITH CHECK বাদ আছে।
  • একটি পলিসি এমন একটি জয়েন ব্যবহার করে যা অন্য টেবিলে নিরাপদ নয়।
  • অ্যাক্সেস একটি ভিউতে রক্ষা করা হয়, কিন্তু অধীনস্থ টেবিল এখনও খোলা।
  • আপনি ধরে নেন “অ্যাপ সবসময় tenant_id সেট করে”, এবং একটি ব্যাকগ্রাউন্ড জব ভুলে যায়।
  • আপনি সুপারইউজার রোলে টেস্ট করেন, তাই বাস্তব আচরণ কখনও দেখা যায় না।

আচরণ বিভ্রান্তিকর করে এমন ফুট-গানস

একই টেবিলকে রেফারেন্স করা নীতিগুলো (সরাসরি বা ভিউয়ের মাধ্যমে) রিকারশন করলে বিস্ময়কর ফল দেয়। একটি পলিসি সদস্যত্ব চেক করতে এমন একটি ভিউ কুয়েরি করতে পারে যা আবার সুরক্ষিত টেবিল পড়ে, ফলে এরর, ধীর কুয়েরি, বা এমন একটি পলিসি তৈরি হতে পারে যা কখনই মেলে না।

রোল সেটআপও বিভ্রান্তির উৎস। টেবিল মালিক এবং উর্ধ্বতন রোল RLS বাইপাস করতে পারে, তাই আপনার টেস্টগুলো সঠিক নীচু-অধিকার রোলে চালানো উচিত।

SECURITY DEFINER ফাংশন সম্পর্কে সতর্ক থাকুন। এগুলো ফাংশনের মালিকের অধিকার নিয়ে চলে, তাই একটি হেল্পার যেমন current_tenant_id() ঠিক থাকতে পারে, কিন্তু এমন একটি “সুবিধাজনক” ফাংশন যা ডেটা পড়ে সেটা অনিচ্ছাকৃতভাবে টেন্যান্ট ছাড়িয়ে পড়তে পারে যদি আপনি তা RLS সম্মান করে তৈরি না করেন।

এছাড়া security definer ফাংশনগুলোর ভিতরে নিরাপদ search_path সেট করুন। না করলে ফাংশন সেই সেশনের অবস্থার ওপর নির্ভর করে একই নামে ভিন্ন অবজেক্ট পেয়ে নিতে পারে, এবং আপনার পলিসি লজিক নীরবে ভুল জিনিসের দিকে ইঙ্গিত করতে পারে।

RLS ডিবাগিং: কী ঘটছে তা দেখার ব্যবহারিক উপায়গুলো

মাল্টি-টেন্যান্ট ভিত্তি দ্রুত পাঠান
আপনার টেন্যান্ট মডেলকে বাস্তব টেবিল, রোল, এবং এন্ডপয়েন্টে রূপান্তর করুন — শূন্য থেকে শুরু না করেই।

RLS বাগগুলো সাধারণত অনুপস্থিত প্রসঙ্গজনিত — “খারাপ SQL” নয়। একটি নীতি কাগজে সঠিক হলেও ব্যর্থ হতে পারে কারণ সেশন রোল আপনি ভেবেছেন তার চেয়ে আলাদা, অথবা অনুরোধ কখনই আপনার নীতির জন্য প্রয়োজনীয় টেন্যান্ট/ব্যবহারকারী ভ্যালু সেট করেনি।

একটি নির্ভরযোগ্য উপায় প্রোডuction রিপোর্ট পুনরুত্পাদন করা হল একই সেশন সেটআপ লোকালেও মিরর করা এবং একই কুয়েরিটি চালানো। সাধারণত এর মানে:

  • SET ROLE app_user; (বা প্রকৃত API রোল)
  • SELECT set_config('app.tenant_id', 't_123', true); এবং SELECT set_config('app.user_id', 'u_456', true);
  • আপনার অ্যাপ চালানো একই SQL কুয়েরি চালান (প্যারামিটারসহ)
  • Postgres কি দেখছে তা নিশ্চিত করুন: SELECT current_user, current_setting('app.tenant_id', true), current_setting('app.user_id', true);

যখন আপনি নিশ্চিত না কোন পলিসি প্রয়োগ হচ্ছে, অনুমান না করে ক্যাটালগ চেক করুন। pg_policies প্রতিটি নীতি, কমান্ড, এবং USINGWITH CHECK এক্সপ্রেশন দেখায়। এটিকে pg_class-এর সাথে জোড়া দিন যাতে নিশ্চিত হন টেবিলে RLS চালু আছে এবং বাইপাস করা হয়নি।

পারফরম্যান্স সমস্যা মনে হতে পারে অথরাইজেশন সমস্যা। একটি নীতি যা সদস্যত্ব টেবিলে জয়েন করে বা একটি ফাংশন কল করে সেটা সঠিক হলেও টেবিল বড় হলে ধীর হতে পারে। পুনরুত্পাদিত কুয়েরিতে EXPLAIN (ANALYZE, BUFFERS) চালান এবং সিকোয়েন্সিয়াল স্ক্যান, অপ্রত্যাশিত নেস্টেড লুপ, বা দেরিতে প্রয়োগ হওয়া ফিল্টার খুঁজুন। (tenant_id, user_id) এবং সদস্যত্ব টেবিলগুলোর উপর অনুপস্থিত ইনডেক্স সাধারণ কারণ।

এছাড়া অ্যাপ লেয়ারে প্রতি অনুরোধে তিনটি মান লগ করুন: টেন্যান্ট আইডি, ব্যবহারকারী আইডি, এবং ডাটাবেস রোল। যখন এগুলো আপনি মনে করছেন সেটার সাথে মেলে না, RLS “ভুল” আচরণ করবে কারণ ইনপুটগুলোই ভুল।

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

প্রোডাকশনের জন্য RLS-এর পূর্ব-রিলিজ চেকলিস্ট

RLS-কে ফিচার টগল হিসেবে নয়, সিটবেল্ট হিসেবে বিবেচনা করুন। ছোট ভুলগুলো “সবার ডেটা সবাই দেখতে পাবে” বা “সবকিছু শূন্য সারি ফিরায়” তে পরিণত হতে পারে।

ডেটা মডেল ও নীতি আকার

নিশ্চিত করুন টেবিল ডিজাইন ও নীতি নিয়মগুলো আপনার টেন্যান্ট মডেলের সাথে মেলে:

  • প্রতিটি টেন্যান্ট-অধিকারভুক্ত টেবিলে একটি পরিষ্কার টেন্যান্ট কী থাকা উচিত (সাধারণত tenant_id)। যদি না থাকে, কেন নেই তা নথিভুক্ত করুন (উদাহরণ: গ্লোবাল রেফারেন্স টেবিল)।
  • প্রতিটি টেন্যান্ট-অধিকারভুক্ত টেবিলে RLS চালু করুন, শুধুমাত্র “মুখ্য” টেবিল না। যদি কিছু পথ কখনই বাইপাস করা যাবে না, তখন সেই টেবিলে FORCE ROW LEVEL SECURITY বিবেচনা করুন।
  • রিড ও রাইট নিয়ম আলাদা রাখুন। রিডে USING ব্যবহার করুন। লেখার ক্ষেত্রে WITH CHECK থাকা দরকার যাতে ইনসার্ট ও আপডেট কোনো সারিকে অন্য টেন্যান্টে সরাতে না পারে।
  • পলিসি প্রেডিকেটগুলো ইনডেক্স-ফ্রেন্ডলি রাখুন। যদি নীতিগুলো tenant_id দিয়ে ফিল্টার করে বা সদস্যত্ব টেবিল জয়েন করে, তখন মিলানো ইনডেক্স যোগ করুন।

একটি সরল স্যানিটি সিনারিও: একটি টেন্যান্ট A ব্যবহারকারী তাদের নিজস্ব ইনভয়েস পড়তে পারে, কেবলমাত্র টেন্যান্ট A'র জন্য ইনভয়েস ইনসার্ট করতে পারে, এবং টেন্যান্ট আইডি পরিবর্তন করে ইনভয়েস আপডেট করতে পারবে না।

রোল, পারফরম্যান্স, এবং টেস্ট

RLS কেবল আপনার অ্যাপ যে রোল ব্যবহার করে তার মত শক্তিশালী।

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

উদাহরণ: সদস্যত্ব-ভিত্তিক অ্যাক্সেস সহ মাল্টি-টেন্যান্ট প্রোজেক্টস অ্যাপ

Postgres-ফার্স্ট ব্যাকএন্ড দিয়ে শুরু করুন
RLS ব্যবহার করে লক করা যাবে এমন পরিষ্কার Go API এবং PostgreSQL স্কিমা পান।

একটি B2B অ্যাপ কল্পনা করুন যেখানে কোম্পানি (orgs) প্রোজেক্ট রাখে, এবং প্রোজেক্টে টাস্ক থাকে। ব্যবহারকারীরা একাধিক org-এ থাকতে পারে, এবং একজন ব্যবহারকারী কিছু প্রোজেক্টের সদস্য নাও হতে পারে। RLS-এর জন্য এটি ভাল ফিট কারণ ডাটাবেস API এন্ডপয়েন্ট ভুলে গেলেও টেন্যান্ট বিচ্ছিন্নতা প্রয়োগ করতে পারে।

সরল মডেল: orgs, users, org_memberships (org_id, user_id, role), projects (id, org_id), project_memberships (project_id, user_id), tasks (id, project_id, org_id, ...). tasks-এ থাকা org_id ইচ্ছাকৃত—এটি নীতিগুলো সহজ রাখে এবং জয়েন করার সময় বিস্ময় কমায়।

ক্লাসিক লিকটি ঘটে যখন টাস্কগুলিতে কেবল project_id থাকে এবং আপনার পলিসি অ্যাক্সেস পরীক্ষা করতে projects-এ জয়েন করে। একটি ভুল (প্রজেক্টে permissive পলিসি, জয়েন যা শর্ত হারায়, বা ভিউ যা কনটেক্সট বদলে দেয়) অন্য একটি org-এর টাস্ক এক্সপোজ করতে পারে।

একটি নিরাপদ মাইগ্রেশন পথ যা প্রোডাকশন ট্র্যাফিক ভাঙবে না:

  • প্রথমে স্কিমা পরিবর্তন পাঠান (tasks-এ org_id যোগ করুন, সদস্যত্ব টেবিল যোগ করুন)।
  • projects.org_id থেকে tasks.org_id ব্যাকফিল করুন, তারপর NOT NULL যোগ করুন।
  • পলিসি যোগ করুন কিন্তু স্টেজিং-এ পরীক্ষা করার সময় RLS ডিসেবলই রাখুন।
  • RLS এনাবেল করুন, তারপর ফোর্স করুন, এবং তারপর পুরনো অ্যাপ-সাইড ফিল্টারগুলো সরান।

সাপোর্ট অ্যাক্সেস সাধারণত একটি সংকীর্ণ ব্রেক-গ্লাস রোলে রাখা ভালো, RLS বন্ধ করার বদলে। এটি সাধারণ সাপোর্ট অ্যাকাউন্ট থেকে আলাদা রাখুন এবং যখন ব্যবহার করা হয় তা স্পষ্ট করুন।

নীতিগুলো নথিভুক্ত করুন যাতে ধীরে ধীরে পলিসি ভ্রান্ত না হয়: কোন সেশন ভ্যারিয়েবলগুলো সেট করতে হবে (user_id, org_id), কোন টেবিলে org_id থাকতে হবে, “member” মানে কী, এবং কিছু SQL উদাহরণ যা ভুল org-এ চালালে 0 সারি ফিরিয়ে আনা উচিত।

পরবর্তী ধাপ: RLS নিরাপদে রোলআউট করুন এবং এটি রক্ষণাবেক্ষণযোগ্য রাখুন

RLS সহজ হয়ে যায় যদি আপনি এটাকে পণ্য পরিবর্তন হিসেবে আচরণ করেন। ছোট খণ্ডে রোলআউট করুন, টেস্ট দিয়ে আচরণ প্রমাণ করুন, এবং প্রতিটি নীতির কারণ স্পষ্ট লিখে রাখুন।

একটি কার্যকর রোলআউট প্ল্যান সাধারণত:

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

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

যদি আপনি দ্রুত এগোচ্ছে থাকেন, Koder.ai (koder.ai) মত টুলগুলো Go + PostgreSQL স্টার্টিং পয়েন্ট জেনারেট করতে সাহায্য করতে পারে, তারপর আপনি একই ডিসিপ্লিন ব্যবহার করে RLS পলিসি ও টেস্ট লেয়ার করতে পারবেন।

শেষে, রোলআউটের সময় নিরাপত্তা রেল রাখুন। পলিসি মাইগ্রেশনের আগে স্ন্যাপশট নিন, রোলব্যাক প্র্যাকটিস করুন যতক্ষণ না এটা বিরক্তিকর না লাগে, এবং সাপোর্টের জন্য একটি ছোট ব্রেক-গ্লাস পথ রাখুন যা পুরো সিস্টেমে RLS বন্ধ করে না।

সাধারণ প্রশ্ন

RLS বাস্তবে একটি SaaS অ্যাপে কোন সিকিউরিটি সমস্যা সমাধান করে?

RLS PostgreSQL-কে নিয়ম করে দেয় যে কোন সারিগুলো একটি অনুরোধের জন্য দেখা বা লেখার যোগ্য — সুতরাং টেন্যান্ট বিচ্ছিন্নতা সম্পূর্ণরূপে প্রত্যেক এন্ডপয়েন্টকে সঠিক WHERE tenant_id = ... মনে রাখার ওপর নির্ভর করে না। প্রধান সুবিধা হলো—অ্যাপ বড় হলে একবার মিস করা ফিল্টার থেকে হওয়া বাগগুলো কমানো।

RLS কখন অতিরিক্ত জটিলতাটিকে মূল্যবান করে?

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

RLS আমাকে কোন জিনিস থেকে রক্ষা করে না?

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

আমি নতুন হলে RLS ব্যবহার শুরু করার সবচেয়ে নিরাপদ উপায় কী?

নিম্ন-প্রিভিলেজ রোল বানান যা API ব্যবহার করবে (টেবিল মালিক নয়), RLS চালু করুন, তারপর একটি SELECT পলিসি এবং INSERT/UPDATE-এর জন্য WITH CHECK পলিসি যোগ করুন। অনুরোধ-স্কোপড সেশন ভ্যারিয়েবল (যেমন app.current_tenant) সেট করে যাচাই করুন যে সেটা বদলালে আপনি যে সারিগুলো দেখতে/লেখতে পারেন তা বদলে যাচ্ছে।

আমার অ্যাপ কীভাবে Postgres-কে বলবে কোন টেন্যান্ট ও ব্যবহারকারী অনুরোধ করছে?

একটি সাধারণ পদ্ধতি হল প্রতি অনুরোধে সেশন ভ্যারিয়েবল সেট করা, যেমন app.tenant_id এবং app.user_id। কী জারুরি—সব কোডপাথ (ওয়েব অনুরোধ, জব, স্ক্রিপ্ট) একই ভ্যালুগুলো সেট করবে। যদি না করে, আপনি বিভ্রান্তিকর "শূন্য সারি" আচরণ পাবেন।

RLS পলিসিতে USING এবং WITH CHECK এর মধ্যে পার্থক্য কী?

USING নিয়ন্ত্রণ করে কোন বিদ্যমান সারি দেখা যায় এবং কোন সারিগুলো SELECT, UPDATE, DELETE-এর টার্গেট হতে পারে। WITH CHECK নিয়ন্ত্রণ করে কোন নতুন বা পরিবর্তিত সারি INSERT/UPDATE-এ অনুমোদিত হবে—এটি নিশ্চিত করে যে কেউ ভুল টেন্যান্ট আইডি দিয়ে অন্য টেন্যান্টে লেখা করতে পারবে না।

মানুষরা কেন বারবার বলে “WITH CHECK ভুলে যাবেন না”?

কারণ যদি আপনি কেবল USING যোগ করেন, তাহলে একটি বাগযুক্ত এন্ডপয়েন্ট এখনও ভুল টেন্যান্টে সারি INSERT বা UPDATE করতে পারে, এবং সেটা আপনি খুঁজে পাবেন না কারণ একই ব্যবহারকারী সেই খারাপ সারিটি পড়তে পারবে না। তাই পড়ার নিয়মের সঙ্গে মিল রেখে লেখার জন্য WITH_CHECK সারি যোগ করা জরুরি যাতে খারাপ ডেটা প্রথমে তৈরি না হয়।

আমি কীভাবে স্কিমা গঠন করব যাতে RLS পলিসিগুলো সহজ ও দ্রুত থাকে?

নীতিগুলো সহজ ও দ্রুত রাখার জন্য টেন্যান্ট কী (যেমন org_id) সরাসরি টেন্যান্ট-অধিকারভুক্ত টেবিলগুলোতে রাখুন, এমনকি যদি তারা অন্য টেবিলকে রেফারেন্স করে। স্পষ্ট org_memberships ও (প্রয়োজন হলে) project_memberships রাখুন যাতে পলিসিগুলো একটি ইনডেক্স করা লুকআপে শেষ হয়, জটিল ইনফারেন্সে নয়।

“RLS আমার ডেটা লুকাচ্ছে” ডিবাগ করার সময় আমি কীভাবে অনুমান না করে সমস্যাটি দেখব?

প্রথমে আপনার অ্যাপ যে সেশন কনটেক্সট ব্যবহার করে তা স্থানীয়ভাবে মিলিয়ে পুনরুত্পাদন করুন: একই রোল এবং সেশন সেটিংস সেট করে সঠিক SQL কুয়েরি চালান। তারপর নিশ্চিত করুন RLS চালু আছে ও pg_policies দেখে কোন USINGWITH CHECK এক্সপ্রেশানগুলো প্রযোজ্য—কারণ RLS-এর ভুল প্রায়ই সনাক্ত হয়ে না দেখা যায়নি সেশন ইনপুট ভুল থাকায়।

আমি যদি আমার ব্যাকএন্ড জেনারেট করি (উদাহরণস্বরূপ Koder.ai দিয়ে), তাহলে কি আমাকে এখনও RLS সাবধানে ডিজাইন করতে হবে?

হ্যাঁ—জেনারেট করা কোডকে একটি শুরু হিসেবে নিন, নিরাপত্তার সিস্টেম হিসেবে নয়। যদি আপনি Koder.ai দিয়ে Go + PostgreSQL ব্যাকএন্ড জেনারেট করেন, আপনাকে এখনও আপনার টেন্যান্ট মডেল নির্ধারণ করতে হবে, সেশন আইডেন্টিটি কনসিস্টেন্ট করতে হবে, এবং নতুন টেবিলগুলো সঠিক সুরক্ষার ছাড়া পাঠানো না হয় সেটা নিশ্চিত করতে পলিসি ও টেস্ট যোগ করা দরকার।

Related posts