در فضای مدرن SaaS، چندمستأجره بودن تنها یک استراتژی استقرار نیست؛ بلکه یک الزام اساسی معماری است. اگرچه پایگاههای داده اشتراکی کارایی هزینهای قابل توجهی و پشتیبانگیری سادهتری ارائه میدهند، اما یک چالش امنیتی حیاتی را ایجاد میکنند: جداسازی دادهها. چگونه تضمین میکنید که مستأجر A هرگز دادههای مستأجر B را نمیبیند، حتی اگر یک باگ در برنامه رخ دهد یا توسعهدهنده اشتباهی مرتکب شود؟ پاسخ در امنیت سطح سطر (RLS) نهفته است.
امنیت سطح سطر (RLS) یک ویژگی قدرتمند در پایگاههای داده رابطهای پیشرفته مانند PostgreSQL، Azure SQL و Oracle در دسترس است. این ویژگی به موتور پایگاه داده اجازه میدهد تا سیاستهای کنترل دسترسی را اعمال کند و بار امنیتی را از لایه برنامه به لایه داده منتقل میکند. در این مقاله، بررسی خواهیم کرد که چگونه RLS را به طور مؤثر پیادهسازی کنیم تا جداسازی قوی را بدون قربانی کردن عملکرد یا تجربه توسعهدهنده تضمین نماییم.
چرا RLS بر فیلتر کردن در سطح برنامه برتری دارد
رویکرد سنتی برای چندمستأجره بودن، شامل افزودن یک ستون tenant_id به هر جدول و اطمینان از اینکه هر کوئری شامل عبارت WHERE tenant_id = current_user_tenant_id باشد، است. اگرچه این روش کار میکند، اما کاملاً به درستی کد برنامه وابسته است. اگر توسعهدهنده فیلتر را فراموش کند یا اگر یک سازنده کوئری پویای پیچیده نتواند شرط را تزریق کند، یک نقض امنیتی بزرگ رخ میدهد.
RLS این خطر را با اعمال محدودیتها در سطح اجرای SQL از بین میبرد. حتی اگر یک مدیر به کاربر دسترسیهای گستردهای اعطا کند، سیاست RLS به عنوان یک فیلتر نامرئی عمل کرده و نتایج را فقط به سطرهایی که کاربر مجاز به دسترسی به آنها است، محدود میکند. این استراتژی دفاع در عمق برای چارچوبهای انطباق مانند GDPR، HIPAA و SOC2 حیاتی است.
پیادهسازی RLS در PostgreSQL
بیایید یک پیادهسازی عملی را با استفاده از PostgreSQL، استاندارد صنعتی برای ویژگیهای پیشرفته RLS بررسی کنیم. مفهوم اصلی شامل ایجاد تابعی است که زمینه مستأجر فعلی را تعیین میکند و تعریف سیاستهایی است که این زمینه را اعمال میکنند.
مرحله ۱: ایجاد زمینه مستأجر
اولین قدم نیاز به روشی برای شناسایی مستأجر فعلی است. این کار معمولاً با تنظیم یک متغیر جلسه یا استفاده از یک ادعا (claim) از یک توکن احراز هویت (مانند JWT) انجام میشود. برای این مثال، از یک پارامتر جلسه سفارشی استفاده خواهیم کرد.
-- فعالسازی زمینه امنیتی
-- در یک برنامه واقعی، این مورد پس از احراز هویت تنظیم میشود
SET app.current_tenant_id = 'tenant_001';
-- تابع کمکی برای بازیابی شناسه مستأجر
CREATE OR REPLACE FUNCTION get_current_tenant_id()
RETURNS UUID AS $$
BEGIN
RETURN current_setting('app.current_tenant_id')::UUID;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
مرحله ۲: تعریف سیاستهای امنیتی
اکنون، سیاستها را روی جداول خاص اعمال میکنیم. در اینجا یک مثال برای یک جدول استاندارد orders (سفارشات) آورده شده است.
-- فعالسازی RLS روی جدول
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- سیاست: کاربران فقط میتوانند دادههای خود را ببینند
CREATE POLICY tenant_isolation_policy ON orders
USING (tenant_id = get_current_tenant_id());
-- اختیاری: سیاست برای کاربران مدیر (superusers)
-- اگر نیاز دارید مدیران همه دادهها را ببینند، ممکن است نیاز به ایجاد یک سیاست دوم باشد
CREATE POLICY admin_override_policy ON orders
FOR ALL
TO admin_role
USING (true);
تفاوت بین عبارت USING و عبارت WITH CHECK را در نظر بگیرید. عبارت USING کنترل میکند که کدام سطرها در طول یک SELECT بازگردانده میشوند. عبارت WITH CHECK کنترل میکند که کدام سطرها میتوانند درج یا بهروزرسانی شوند، اطمینان حاصل میکند که کاربران نمیتوانند به طور تصادفی (یا مخرب) رکوردهای متعلق به مستأجران دیگر را تغییر دهند.
مرحله ۳: مدیریت هویت
در بسیاری از برنامهها، شما همچنین نیاز دارید که رد کنید چه کسی یک رکورد را ایجاد کرده است. اگر جدول کاربران شما شامل یک user_id باشد، میتوانید سیاست را گسترش دهید تا اطمینان حاصل کنید که کاربران فقط به دادههایی که شخصاً ایجاد کردهاند، علاوه بر دادههای مستأجر خود دسترسی دارند.
-- سیاست دقیقتر: کاربران فقط میتوانند سفارشات خود را ببینند
CREATE POLICY user_isolation_policy ON orders
FOR SELECT
USING (
tenant_id = get_current_tenant_id()
AND user_id = current_user_id()
);
ملاحظات عملکردی
اگرچه RLS امن است، اما اگر به درستی نمایهسازی (Index) نشود، میتواند بر عملکرد تأثیر بگذارد. موتور پایگاه داده باید سیاست را برای هر سطر دسترسیشده ارزیابی کند. بنابراین، بسیار حیاتی است که ستون tenant_id شما نمایهسازی شده باشد. بدون یک نمایه مناسب، پایگاه داده ممکن است برای هر کوئری یک اسکن متوالی انجام دهد که منجر به افزایش قابل توجه تأخیر با افزایش حجم دادههای شما میشود.
علاوه بر این، به یاد داشته باشید که سیاستهای RLS سربار را به مرحله برنامهریزی کوئری اضافه میکنند. کوئریهای خود را تحت بار کاری بنچمارک کنید تا اطمینان حاصل کنید که سربار امنیتی از بودجههای عملکردی شما فراتر نمیرود. برای بارهای کاری با خواندن زیاد، در نظر بگیرید که از کپیهای خواندن (Read Replicas) یا استراتژیهای کش برای کاهش تأثیر ارزیابی سیاست بر عملیات نوشتن استفاده کنید.
نتیجهگیری
پیادهسازی امنیت سطح سطر یکی از مؤثرترین روشها برای ایمنسازی یک معماری چندمستأجره است. این کار امنیت را از یک مدل منطق برنامهای «اعتماد کن اما بررسی کن» به یک مدل پایگاه داده «هرگز اعتماد نکن، همیشه بررسی کن» منتقل میکند. با بهرهگیری از RLS، مهندسان پایگاه داده میتوانند یک پایه امنیتی قوی فراهم کنند که با کسبوکار مقیاسپذیر است، دادههای حساس را محافظت میکند و انطباق را با حداقل سربار در سطح برنامه حفظ مینماید.
هنگام ادغام RLS در لایههای خود، به یاد داشته باشید که موارد حاشیهای را آزمایش کنید، نمایهسازی مناسب را تضمین کنید و سیاستهای امنیتی خود را به وضوح مستندسازی کنید. در دنیای چندمستأجره بودن، امنیت یک ویژگی نیست؛ بلکه پایه و اساس است.