مسعود سلطانی راد
مسعود سلطانی راد
خواندن ۱۳ دقیقه·۳ ماه پیش

مقدمه‌ای بر امنیت PostgreSQL

پایگاه‌داده‌ها مانند جام مقدس برای هکرها هستند و به همین دلیل باید با دقت بسیار بالا محافظت شوند. در این سری از مقالات، به بررسی بهترین روش‌های امنیتی برای محافظت از پایگاه‌داده‌ها خواهیم پرداخت.

ما با یکی از محبوب‌ترین پایگاه‌های داده منبع‌باز، PostgreSQL، شروع می‌کنیم و چندین سطح از امنیت را که باید به آنها فکر کنید، بررسی خواهیم کرد:

  • امنیت در سطح شبکه
  • امنیت در سطح انتقال
  • امنیت در سطح پایگاه‌داده
۱. امنیت در سطح شبکه برای PostgreSQL

دیوارهای آتش

در یک دنیای ایده‌آل، سرور PostgreSQL شما باید به‌طور کامل ایزوله باشد و هیچ اتصالی از بیرون، چه SSH یا psql، اجازه ورود نداشته باشد. متأسفانه، چنین تنظیمات ایزوله‌ای به‌طور پیش‌فرض توسط PostgreSQL پشتیبانی نمی‌شود.

گام بعدی که می‌توانید برای بهبود امنیت سرور پایگاه‌داده خود انجام دهید، بستن دسترسی‌های پورت به نودی است که پایگاه‌داده روی آن اجرا می‌شود. به‌طور پیش‌فرض، PostgreSQL روی پورت TCP 5432 گوش می‌دهد. بسته به سیستم‌عامل، ممکن است راه‌های مختلفی برای بستن پورت‌های دیگر وجود داشته باشد. اما با استفاده از ابزار فایروال iptables که در اکثر توزیع‌های لینوکس موجود است، می‌توانید به شکل زیر عمل کنید:

# اطمینان حاصل کنید که اتصالات برقرارشده از قبل قطع نشوند.

iptables -A INPUT -m state –state ESTABLISHED,RELATED -j ACCEPT

# اجازه اتصال SSH.

iptables -A INPUT -p tcp -m state –state NEW –dport 22 -j ACCEPT

# اجازه اتصال به PostgreSQL.

iptables -A INPUT -p tcp -m state –state NEW –dport 5432 -j ACCEPT

# اجازه تمام اتصالات خروجی و بستن بقیه ورودی‌ها.

iptables -A OUTPUT -j ACCEPT

iptables -A INPUT -j DROP

iptables -A FORWARD -j DROP

نکته: هنگام به‌روزرسانی قوانین iptables، بهتر است از ابزار iptables-apply استفاده کنید که در صورت اشتباه، تغییرات را به‌طور خودکار بازگرداند.

قانون PostgreSQL بالا به همه اجازه می‌دهد تا به پورت ۵۴۳۲ متصل شوند. می‌توانید آن را محدودتر کرده و فقط از برخی آدرس‌های IP یا زیرشبکه‌ها اجازه اتصال بدهید:

# فقط اجازه دسترسی به پورت PostgreSQL از زیرشبکه محلی.

iptables -A INPUT -p tcp -m state –state NEW –dport 5432 -s 192.168.1.0/24 -j ACCEPT

در سناریوی ایده‌آل ما، جلوگیری از تمام اتصالات ورودی به پورت ۵۴۳۲ نیازمند یک عامل محلی است که یک اتصال پایدار به نود مشتری برقرار کرده و توانایی پراکسی کردن ترافیک به نمونه PostgreSQL محلی را داشته باشد.

این تکنیک به “تونل‌سازی معکوس” معروف است و می‌توان آن را با استفاده از ویژگی پورت فرواردینگ SSH به نمایش گذاشت. شما می‌توانید یک تونل معکوس با اجرای دستور زیر از نودی که پایگاه‌داده PostgreSQL روی آن در حال اجراست باز کنید:

ssh -f -N -T -R 5432:localhost:5432 user@<client-host>

البته، <client-host> باید از نود PostgreSQL قابل دسترسی باشد و SSH daemon روی آن اجرا شود. این دستور پورت ۵۴۳۲ روی سرور پایگاه‌داده را به پورت ۵۴۳۲ روی ماشین مشتری فروارد می‌کند و شما می‌توانید از طریق تونل به پایگاه‌داده متصل شوید:

psql “host=localhost port=5432 user=postgres dbname=postgres”

تنظیم آدرس‌های شنیداری PostgreSQL با استفاده از listen_addresses

بهتر است آدرس‌هایی را که سرور برای اتصالات مشتری گوش می‌دهد، با استفاده از دستورالعمل listen_addresses در فایل پیکربندی محدود کنید. اگر نود PostgreSQL چندین رابط شبکه دارد، از این تنظیمات استفاده کنید تا مطمئن شوید سرور فقط روی رابط‌هایی که مشتری‌ها به آن متصل می‌شوند، گوش می‌دهد:

listen_addresses = ‘localhost, 192.168.0.1’

اگر مشتری‌ها همیشه روی همان نود قرار دارند (یا مثلاً در همان پاد Kubernetes با PostgreSQL به‌عنوان یک کانتینر جانبی قرار دارند)، غیرفعال کردن گوش دادن سوکت TCP می‌تواند شبکه را به‌طور کامل از تصویر حذف کند. تنظیم آدرس‌های گوش دادن به یک رشته خالی، باعث می‌شود سرور فقط اتصالات سوکت دامنه یونیکس را قبول کند:

listen_addresses = ”

۲. امنیت در سطح انتقال برای PostgreSQL

با حرکت به سمت جهانی که اکثر وب به سمت HTTPs می‌رود، دلیلی برای استفاده نکردن از رمزگذاری قوی برای اتصالات پایگاه‌داده وجود ندارد. PostgreSQL به‌طور بومی از TLS (که به‌دلایل میراثی همچنان به‌عنوان SSL در مستندات، پیکربندی و CLI نامیده می‌شود) پشتیبانی می‌کند و روش‌هایی برای استفاده از آن برای احراز هویت سرور و مشتری ارائه می‌دهد.

TLS سرور

برای احراز هویت سرور، ابتدا باید یک گواهی دریافت کنید که سرور به مشتری‌های متصل ارائه می‌دهد. Let’s Encrypt به‌راحتی می‌تواند به‌صورت رایگان گواهی‌های X.509 را فراهم کند، مثلاً با استفاده از ابزار CLI certbot:

certbot certonly –standalone -d postgres.example.com

به خاطر داشته باشید که به‌طور پیش‌فرض certbot از چالش HTTP-01 ACME برای اعتبارسنجی درخواست گواهی استفاده می‌کند که نیاز به یک DNS معتبر برای دامنه درخواست‌شده دارد و باید به نود اشاره کرده و پورت ۸۰ باز باشد.

اگر به‌دلیلی نمی‌توانید از Let’s Encrypt استفاده کنید و می‌خواهید تمام رازها را به‌صورت محلی تولید کنید، می‌توانید این کار را با استفاده از ابزار CLI openssl انجام دهید:

# ایجاد یک CA سرور خودامضا شده.

openssl req -sha256 -new -x509 -days 365 -nodes \

-out server-ca.crt \

-keyout server-ca.key

# تولید CSR سرور. نام میزبان که برای اتصال به پایگاه‌داده استفاده خواهید کرد را در فیلد CN قرار دهید.

openssl req -sha256 -new -nodes \

-subj “/CN=postgres.example.com” \

-out server.csr \

-keyout server.key

# امضای گواهی سرور.

openssl x509 -req -sha256 -days 365 \

-in server.csr \

-CA server-ca.crt \

-CAkey server-ca.key \

-CAcreateserial \

-out server.crt

البته، در محیط تولید باید اطمینان حاصل کنید که این گواهی‌ها قبل از تاریخ انقضا به‌روز شوند.

TLS مشتری

هنگام اتصال به یک پایگاه‌داده Postgres، می‌توانید از طیف وسیعی از روش‌های احراز هویت استفاده کنید. ما توصیه می‌کنیم از گواهی‌های مشتری استفاده کنید. احراز هویت با گواهی مشتری به سرور اجازه می‌دهد تا هویت یک مشتری متصل را با اعتبارسنجی گواهی X.509 ارائه‌شده توسط مشتری که توسط یک مرجع صدور گواهی معتبر امضا شده است، تأیید کند.

بهتر است از مراجع صدور گواهی مختلف برای صدور گواهی‌های مشتری و سرور استفاده کنید، بنابراین اجازه دهید یک CA مشتری ایجاد کنیم و از آن برای امضای یک گواهی مشتری استفاده کنیم:

# ایجاد یک CA مشتری خودامضا شده.

openssl req -sha256 -new -x509 -days 365 -nodes \

-out client-ca.crt \

-keyout client-ca.key

# تولید CSR مشتری. فیلد CN باید شامل نام نقش پایگاه‌داده‌ای باشد که  برای اتصال به پایگاه‌داده استفاده می‌کنید.

openssl req -sha256 -new -nodes \

-subj “/CN=alice” \

-out client.csr \

-keyout server.key

# امضای گواهی مشتری.

openssl x509 -req -sha256 -days 365 \

-in client.csr \

-CA client-ca.crt \

-CAkey client-ca.key \

-CAcreateserial \

-out client.crt

توجه داشته باشید که فیلد CommonName (CN) گواهی مشتری باید شامل نام حساب کاربری پایگاه‌داده‌ای باشد که مشتری به آن متصل می‌شود. سرور PostgreSQL از آن برای تعیین هویت مشتری استفاده خواهد کرد.

پیکربندی TLS

حالا که همه موارد مورد نیاز را دارید، می‌توانید سرور PostgreSQL خود را در فایل پیکربندی postgresql.conf برای پذیرش اتصالات TLS پیکربندی کنید:

ssl = on

ssl_cert_file = ‘/path/to/server.crt’

ssl_key_file = ‘/path/to/server.key’

ssl_ca_file = ‘/path/to/client-ca.crt’

# این تنظیم به صورت پیش‌فرض فعال است اما بهتر است برای امنیت بیشتر آن را به صورت صریح تنظیم کنید.

ssl_prefer_server_ciphers = on

# TLS 1.3 بالاترین سطح امنیت را فراهم می‌کند و برای زمانی که کنترل هر دو سرور و مشتری را دارید، توصیه می‌شود.

ssl_min_protocol_version = ‘TLSv1.3’

یک بخش دیگر از پیکربندی باقی مانده است که باید فایل احراز هویت مبتنی بر میزبان سرور PostgreSQL، یعنی pg_hba.conf، را به‌روزرسانی کنید تا برای همه اتصالات TLS را اجباری کند و مشتری‌ها را با استفاده از گواهی‌های X.509 تأیید هویت کند:

# TYPE  DATABASE        USER            ADDRESS                 METHOD

hostssl all             all             ::/۰                    cert

hostssl all             all             ۰٫۰٫۰٫۰/۰               cert

اکنون مشتریانی که به سرور پایگاه داده متصل می‌شوند باید یک گواهی معتبر ارائه دهند که توسط مراجع صدور گواهی مشتری امضا شده باشد:

psql “host=postgres.example.com \

user=alice \

dbname=postgres \

sslmode=verify-full \

sslrootcert=/path/to/server-ca.crt \

sslcert=/path/to/client.crt \

sslkey=/path/to/client.key”

به یاد داشته باشید که به صورت پیش‌فرض psql تأیید گواهی سرور را انجام نمی‌دهد، بنابراین sslmode باید به verify-full یا verify-ca تنظیم شود، بسته به این که آیا شما به سرور PostgreSQL با همان نام میزبان که در فیلد CN گواهی X.509 آن رمزگذاری شده است متصل می‌شوید یا خیر.

برای کاهش verbosity دستور و جلوگیری از وارد کردن مسیرهای محرمانه TLS هر بار که می‌خواهید به پایگاه داده متصل شوید، می‌توانید از یک فایل سرویس اتصال PostgreSQL استفاده کنید. این فایل به شما امکان می‌دهد که پارامترهای اتصال را به “سرویس‌ها” گروه‌بندی کنید که سپس می‌توانید از طریق یک پارامتر “service” در رشته اتصال به آن‌ها ارجاع دهید.

فایل ~/.pg_service.conf را با محتوای زیر ایجاد کنید:

[example]

host=postgres.example.com

user=alice

sslmode=verify-full

sslrootcert=/path/to/server-ca.crt

sslcert=/path/to/client.crt

sslkey=/path/to/client.key

حالا، وقتی که می‌خواهید به یک پایگاه داده متصل شوید، فقط کافی است نام سرویس و نام پایگاه داده‌ای که می‌خواهید به آن متصل شوید را مشخص کنید:

psql “service=example dbname=postgres”

امنیت در سطح پایگاه داده

مروری بر نقش‌ها

تا اینجا ما نحوه محافظت از سرور پایگاه داده PostgreSQL از اتصالات غیرمجاز شبکه، استفاده از رمزگذاری قوی برای انتقال و اطمینان از این که سرور و مشتری‌ها می‌توانند به هویت یکدیگر اعتماد کنند را بررسی کردیم. یک بخش دیگر از پازل این است که تعیین کنیم کاربران پس از اتصال به پایگاه داده و تأیید هویتشان چه کاری می‌توانند انجام دهند و به چه چیزهایی دسترسی دارند. این معمولاً به عنوان مجوز یا کنترل دسترسی شناخته می‌شود.

PostgreSQL یک سیستم جامع مجوز کاربران دارد که بر اساس مفهوم نقش‌ها ساخته شده است. در نسخه‌های مدرن PostgreSQL (نسخه ۸.۱ و جدیدتر)، یک “نقش” معادل با “کاربر” است، بنابراین هر نام حساب پایگاه داده‌ای که استفاده می‌کنید، مثلاً با psql (مثلاً “user=alice”)، در واقع یک نقش با ویژگی LOGIN است که به آن اجازه می‌دهد به پایگاه داده متصل شود. در واقع، دستورات SQL زیر معادل هستند:

CREATE USER alice;

CREATE ROLE alice LOGIN;

علاوه بر توانایی ورود به سیستم، نقش‌ها می‌توانند ویژگی‌های دیگری داشته باشند که به آن‌ها اجازه می‌دهد تمام بررسی‌های مجوز را نادیده بگیرند (SUPERUSER)، پایگاه داده‌ها را ایجاد کنند (CREATEDB)، نقش‌های دیگر را ایجاد کنند (CREATEROLE) و غیره.

علاوه بر ویژگی‌ها، نقش‌ها می‌توانند مجوزهایی دریافت کنند که می‌توان آن‌ها را به دو دسته تقسیم کرد: عضویت در نقش‌های دیگر و امتیازات اشیاء پایگاه داده. بیایید ببینیم که این موارد چگونه در عمل کار می‌کنند.

اعطای مجوزهای نقش

برای مثال فرضی ما، ما فهرست سرورها را دنبال خواهیم کرد:

CREATE TABLE server_inventory (

id            int PRIMARY KEY,

description   text,

ip_address    text,

environment   text,

owner         text,

);

به‌صورت پیش‌فرض، نصب PostgreSQL شامل یک نقش فوق‌العاده کاربر (معمولاً به نام “postgres”) است که برای راه‌اندازی اولیه پایگاه داده استفاده می‌شود. استفاده از این نقش برای همه عملیات‌های پایگاه داده معادل با استفاده همیشگی از ورود “root” در لینوکس است که هرگز توصیه نمی‌شود. در عوض، بهتر است یک نقش بدون امتیاز ایجاد کنیم و مجوزها را طبق نیاز به آن تخصیص دهیم و اصل حداقل مجوز را رعایت کنیم.

به جای اختصاص مجوزها به هر کاربر/نقش جدید به صورت جداگانه، می‌توانید یک “نقش گروهی” ایجاد کنید و به دیگر نقش‌ها (که به کاربران فردی نقشه‌بندی می‌شوند) عضویت در این گروه را اعطا کنید. فرض کنید می‌خواهید به توسعه‌دهندگان خود، آلیس و باب، اجازه دهید فهرست سرور را مشاهده کنند ولی نتوانند آن را تغییر دهند:

# ایجاد یک نقش گروهی که به تنهایی توانایی ورود ندارد و به آن مجوز SELECT روی جدول فهرست سرورها را بدهید.

CREATE ROLE developer;

GRANT SELECT ON server_inventory TO developer;

# ایجاد دو حساب کاربری که با ورود به پایگاه داده  امتیازات “developer” را به ارث می‌برند.

CREATE ROLE alice LOGIN INHERIT;

CREATE ROLE bob LOGIN INHERIT;

#هر دو حساب کاربری را به گروه نقش “developer” اضافه کنید.

GRANT developer TO alice, bob;

حالا، وقتی که به پایگاه داده متصل شوند، هر دو آلیس و باب امتیازات گروه نقش “developer” را به ارث می‌برند و قادر خواهند بود تا کوئری‌هایی روی فهرست سرور اجرا کنند.

به طور پیش‌فرض، امتیاز SELECT به همه ستون‌های جدول اعمال می‌شود، اما این الزام‌آور نیست. فرض کنید شما فقط می‌خواهید به کارآموزان خود اجازه دهید اطلاعات عمومی فهرست سرورها را مشاهده کنند بدون اینکه آن‌ها بتوانند به IP address دسترسی پیدا کنند:

CREATE ROLE intern;

GRANT SELECT(id, description) ON server_inventory TO intern;

CREATE ROLE charlie LOGIN INHERIT;

GRANT intern TO charlie;

سایر امتیازات اشیاء پایگاه داده که بیشتر استفاده می‌شوند عبارتند از INSERT، UPDATE، DELETE و TRUNCATE که معادل دستورات SQL مربوطه هستند، اما شما همچنین می‌توانید مجوزهایی برای اتصال به پایگاه‌های داده خاص، ایجاد طرحواره‌های جدید یا اشیاء درون طرحواره، اجرای توابع و غیره اعطا کنید. برای مشاهده فهرست کامل به بخش Privileges در مستندات PostgreSQL مراجعه کنید.

امنیت سطح سطر در PostgreSQL

یکی از ویژگی‌های پیشرفته سیستم مجوز PostgreSQL امنیت سطح سطر است که به شما اجازه می‌دهد مجوزها را برای یک زیرمجموعه از سطرهای یک جدول اختصاص دهید. این شامل سطرهایی است که با دستور SELECT کوئری می‌شوند، و همچنین سطرهایی که با دستورات INSERT، UPDATE و DELETE به‌روزرسانی می‌شوند.

برای شروع استفاده از امنیت سطح سطر، به دو چیز نیاز دارید: فعال‌کردن آن برای یک جدول و تعریف یک سیاست که دسترسی سطح سطر را کنترل می‌کند.

با ادامه‌ی مثال قبلی، فرض کنید که می‌خواهید کاربران فقط بتوانند سرورهای خود را به‌روزرسانی کنند. ابتدا امنیت سطح سطر را برای جدول فعال کنید:

ALTER TABLE server_inventory ENABLE ROW LEVEL SECURITY;

بدون تعریف هیچ سیاستی، PostgreSQL به‌صورت پیش‌فرض سیاست “رد” را اعمال می‌کند، به این معنا که هیچ نقشی (به‌جز مالک جدول که معمولاً نقشی است که جدول را ایجاد کرده است) به آن دسترسی ندارد.

یک سیاست امنیتی سطر یک عبارت بولی است که PostgreSQL برای هر سطری که قرار است بازگردانده یا به‌روزرسانی شود، آن را ارزیابی می‌کند. سطرهای بازگردانده شده توسط دستورات SELECT با عبارت مشخص شده در بند USING بررسی می‌شوند، در حالی که سطرهای به‌روزرسانی شده توسط دستورات INSERT، UPDATE یا DELETE با عبارت WITH CHECK بررسی می‌شوند.

بیایید چند سیاست تعریف کنیم که به کاربران اجازه می‌دهد تا همه سرورها را مشاهده کنند ولی فقط سرورهای خود را به‌روزرسانی کنند، همانطور که توسط فیلد “owner” جدول تعیین می‌شود:

CREATE POLICY select_all_servers

ON server_inventory FOR SELECT

USING (true);

CREATE POLICY update_own_servers

ON server_inventory FOR UPDATE

USING (current_user = owner)

WITH CHECK (current_user = owner);

توجه داشته باشید که تنها مالک جدول می‌تواند سیاست‌های امنیتی سطر را برای آن ایجاد یا به‌روزرسانی کند.

حسابرسی  (Auditing)

تا اینجا بیشتر در مورد اقدامات امنیتی پیشگیرانه صحبت کرده‌ایم. با پیروی از یکی از اصول اساسی امنیت، یعنی دفاع در عمق، بررسی کردیم که چگونه این اقدامات بر روی یکدیگر لایه‌بندی می‌شوند تا به کاهش پیشرفت فرضی یک مهاجم در سیستم کمک کنند.

حفظ یک مسیر دقیق و دقیق از سوابق یکی از ویژگی‌های امنیتی سیستم است که اغلب نادیده گرفته می‌شود. نظارت بر دسترسی‌های سطح شبکه یا سطح گره به سرور پایگاه داده شما خارج از حوزه این پست است، اما بیایید نگاهی به گزینه‌های موجود در مورد خود سرور PostgreSQL بیندازیم.

ساده‌ترین کاری که می‌توانید برای افزایش دید در مورد اتفاقاتی که در داخل پایگاه داده می‌افتد انجام دهید، فعال‌کردن لاگ‌برداری مفصل است. دستورات زیر را به فایل پیکربندی سرور اضافه کنید تا لاگ‌برداری از تمام تلاش‌های اتصال و تمام دستورات SQL اجرا شده فعال شود:

#ثبت تلاش‌های موفق و ناموفق اتصال.

log_connections = on

# ثبت نشست‌های خاتمه یافته.

log_disconnections = on

#ثبت تمام دستورات SQL اجرا شده.

log_statement = all

متأسفانه، این تقریباً تمام کاری است که می‌توانید با نصب استاندارد PostgreSQL به‌صورت خودمیزبان انجام دهید. البته این بهتر از هیچ است، اما به‌خوبی در مقیاس بزرگتر از چند سرور پایگاه داده و استفاده از “grep” ساده برای جستجوی لاگ‌ها کار نمی‌کند.

برای یک راهکار پیشرفته‌تر حسابرسی PostgreSQL، می‌توانید از افزونه شخص ثالثی مثل pgAudit استفاده کنید. اگر از یک نمونه PostgreSQL خودمیزبان استفاده می‌کنید، باید افزونه را به صورت دستی نصب کنید. برخی نسخه‌های میزبانی شده مانند AWS RDS از آن به‌صورت پیش‌فرض پشتیبانی می‌کنند، بنابراین فقط نیاز به فعال‌کردن آن دارید.

pgAudit ساختار و جزئیات بیشتری به دستورات لاگ شده اضافه می‌کند. با این حال، به یاد داشته باشید که همچنان مبتنی بر لاگ‌ها است، که استفاده از آن را در صورتی که بخواهید لاگ‌های حسابرسی خود را به صورت ساختارمند به یک سیستم SIEM خارجی برای تجزیه و تحلیل دقیق ارسال کنید، چالش‌برانگیز می‌کند.


مشاوره postgresqlpostgresqlپایگاه دادهخدمات postgresqlمسعود سلطانی راد
چند سالی هست در حوزه داده ها ( نگهداری و تحلیل آنها) فعالیت دارم و همیشه سعی کردم آموخته هایم رو به اشتراک بگذارم soltanirad@artarad.ir www.artarad.ir
شاید از این پست‌ها خوشتان بیاید