Введение
Базы данных — это Святой Грааль для хакеров, поэтому их необходимо защищать с особой тщательностью. Это первая из серии статей, в которых мы дадим обзор best practice в обеспечении безопасности баз данных. Мы начнем с одной из самых популярных СУБД с открытым исходным кодом, PostgreSQL, и рассмотрим несколько уровней безопасности, о которых стоит задуматься:
Безопасность на сетевом уровне
Безопасность на транспортном уровне
Безопасность на уровне базы данных
Безопасность на сетевом уровне
Файрволы
В идеале ваш сервер PostgreSQL должен быть полностью изолированным и не допускать никаких входящих подключений, SSH или psql. К сожалению, PostgreSQL не поддерживает стандартную настройку такого типа.
Следующий способ, который можно предпринять для повышения безопасности сервера базы данных, — это заблокировать доступ к узлу, на котором работает база данных, на уровне порта, с помощью брандмауэра. По умолчанию, PostgreSQL прослушивает TCP-порт 5432. В зависимости от операционной системы существуют разные способы блокировки других портов. В Linux можно использовать iptables
- наиболее доступную утилиту для управления файрволом:
# Make sure not to drop established connections.
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow SSH.
iptables -A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT
# Allow PostgreSQL.
iptables -A INPUT -p tcp -m state --state NEW --dport 5432 -j ACCEPT
# Allow all outbound, drop everything else inbound.
iptables -A OUTPUT -j ACCEPT
iptables -A INPUT -j DROP
iptables -A FORWARD -j DROP
Примечание. При обновлении правил iptables рекомендуется использовать iptables-apply, который автоматически откатывает изменения в случае, если вы случайно заблокируете себя.
Приведенное выше правило PostgreSQL позволит любому подключаться к порту 5432. Его можно сделать более строгим, принимая подключения только с определенных IP-адресов или подсетей:
# Only allow access to PostgreSQL port from the local subnet.
iptables -A INPUT -p tcp -m state --state NEW --dport 5432 -s 192.168.1.0/24 -j ACCEPT
Возвращаясь к нашему идеальному сценарию, возможность полностью предотвратить входящие подключения к порту 5432 потребует своего рода локального агента, который поддерживает постоянное исходящее соединение с клиентским узлом (-ами) и имеет возможность проксировать трафик на локальный экземпляр PostgreSQL.
Такой метод называется «обратным туннелированием». Его можно продемонстрировать при помощи удаленного перенаправления портов. Вы можете открыть обратный туннель, выполнив следующую команду с узла, на котором работает база данных PostgreSQL:
ssh -f -N -T -R 5432:localhost:5432 user@<client-host>
Конечно, <client-host>
должен быть доступным из узла PostgreSQL и иметь запущенного SSH-демона. Команда перенаправит порт 5432 на сервере базы данных на порт 5432 на клиентском компьютере, и вы сможете подключиться к базе данных через туннель:
psql "host=localhost port=5432 user=postgres dbname=postgres"
Прослушивание адресов
Хорошей практикой является ограничение адресов, которые прослушивает сервер для клиентских подключений, с помощью параметра listen_addresses
файла конфигурации. Если узел, на котором работает PostgreSQL, имеет несколько сетевых интерфейсов, используйте этот параметр, чтобы убедиться, что сервер прослушивает только те интерфейсы, через который клиенты будут подключаться к нему:
listen_addresses = 'localhost, 192.168.0.1'
Если клиенты, подключающиеся к базе данных, всегда находятся на одном и том же узле (или, скажем, совместно расположены в одном поде Kubernetes с PostgreSQL, работающим в качестве дополнительного контейнера), отключение прослушивания сокетов TCP может полностью исключить сеть из рассматриваемой картины. Запись пустой строки в параметр прослушиваемых адресов заставляет сервер принимать только соединения сокетов домена Unix:
listen_addresses = ''
Безопасность на транспортном уровне
Поскольку большая часть всемирной паутины переходит на HTTPS, найдется мало оправданий тому, чтобы не использовать надежное шифрование для соединений с базой данных. PostgreSQL поддерживает TLS (который по-прежнему называется SSL в документации, конфигурации и CLI по legacy-причинам) и позволяет использовать его для аутентификации как сервера, так и клиента.
Серверный TLS
Для аутентификации сервера сначала необходимо получить сертификат, который сервер будет предоставлять подключающимся клиентам. Let's Encrypt упрощает получение бесплатных сертификатов X.509, например, с помощью инструмента CLI certbot:
certbot certonly --standalone -d postgres.example.com
Учтите, что по умолчанию certbot использует вызов HTTP-01 ACME для проверки запроса сертификата, который требует действительного DNS для запрошенного домена, указывающего на узел, и порт 80, который должен быть открыт.
Если по какой-то причине вы не можете использовать Let's Encrypt и хотите сгенерировать все сертификаты локально, то можно использовать openssl CLI:
# Make a self-signed server CA.
openssl req -sha256 -new -x509 -days 365 -nodes \
-out server-ca.crt \
-keyout server-ca.key
# Generate server CSR. Put the hostname you will be using to connect to
# the database in the CN field.
openssl req -sha256 -new -nodes \
-subj "/CN=postgres.example.com" \
-out server.csr \
-keyout server.key
# Sign a server certificate.
openssl x509 -req -sha256 -days 365 \
-in server.csr \
-CA server-ca.crt \
-CAkey server-ca.key \
-CAcreateserial \
-out server.crt
Конечно, в производственной среде необходимо убедиться, что эти сертификаты обновлены вовремя.
Клиентский TLS
Аутентификация клиента по сертификату позволяет серверу проверить личность подключающегося, подтверждая, что сертификат X.509, представленный клиентом, подписан доверенным центром сертификации (CA).
Рекомендуется использовать разные центры сертификации для выдачи сертификатов клиенту и серверу, поэтому давайте создадим клиентский CA и воспользуемся им для подписи сертификата клиента:
# Make a self-signed client CA.
openssl req -sha256 -new -x509 -days 365 -nodes \
-out client-ca.crt \
-keyout client-ca.key
# Generate client CSR. CN must contain the name of the database role you
# will be using to connect to the database.
openssl req -sha256 -new -nodes \
-subj "/CN=alice" \
-out client.csr \
-keyout server.key
# Sign a client certificate.
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 для приема соединений 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'
# This setting is on by default but it’s always a good idea to
# be explicit when it comes to security.
ssl_prefer_server_ciphers = on
# TLS 1.3 will give the strongest security and is advised when
# controlling both server and clients.
ssl_min_protocol_version = 'TLSv1.3'
Последняя часть настройки — обновить файл host-based аутентификации сервера PostgreSQL (pg_hba.conf
), чтобы требовать TLS для всех подключений и аутентифицировать клиентов с помощью сертификатов X.509:
# TYPE DATABASE USER ADDRESS METHOD
hostssl all all ::/0 cert
hostssl all all 0.0.0.0/0 cert
Теперь пользователи, подключающиеся к серверу, должны будут предоставить действительный сертификат, подписанный клиентским CA:
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.
Чтобы уменьшить размер команд и не вводить пути к сертификатам TLS каждый раз, когда вы хотите подключиться к базе данных, вы можете использовать файл службы подключения PostgreSQL. Он позволяет группировать параметры подключения в «службы», на которые затем можно ссылаться в строке подключения через параметр «служба».
Создайте ~/.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 от неавторизованных сетевых подключений, использовать шифрование и убедиться, что сервер и клиенты могут доверять друг другу с помощью взаимной аутентификации TLS. Еще одна часть головоломки — выяснить, что пользователи могут делать и к чему у них есть доступ после подключения к базе данных и подтверждения своей личности. Обычно такая процедура называется авторизацией.
PostgreSQL имеет комплексную систему прав пользователя, основанную на концепции ролей. В современных версиях PostgreSQL (8.1 и новее) «роль» является синонимом «пользователя», поэтому любое имя учетной записи базы данных, которое вы используете, скажем, с 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» в Linux, что никогда не являлось хорошей идеей. Вместо этого давайте создадим непривилегированную роль и при необходимости назначим ей права доступа, следуя принципу наименьших привилегий.
Вместо того, чтобы назначать привилегии/роли каждому новому пользователю индивидуально, можно создать «групповую роль» и предоставить другим ролям (сопоставление с отдельными пользователями) членство в этой группе. Скажем, вы хотите разрешить вашим разработчикам, Алисе и Бобу, просматривать список серверов, но не изменять его:
-- Create a group role that doesn't have ability to login by itself and
-- grant it SELECT privileged on the server inventory table.
CREATE ROLE developer;
GRANT SELECT ON server_inventory TO developer;
-- Create two user accounts which will inherit "developer" permissions upon
-- logging into the database.
CREATE ROLE alice LOGIN INHERIT;
CREATE ROLE bob LOGIN INHERIT;
-- Assign both user account to the "developer" group role.
GRANT developer TO alice, bob;
Теперь при подключении к базе данных и Алиса, и Боб унаследуют привилегии групповой роли «разработчик» и смогут выполнять запросы к таблице инвентаризации сервера.
Привилегия SELECT
распространяется на все столбцы таблицы по умолчанию, хотя так быть не должно. Скажем, вы всего лишь хотели разрешить своим стажерам просматривать общую информацию об инвентаризации серверов, не позволяя им подключаться, скрывая IP-адрес:
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 является безопасность на уровне строк, которая позволяет вам предоставлять привилегии подмножеству строк в таблице. Сюда входят как строки, которые могут быть запрошены с помощью оператора SELECT
, так и строки, которые могут быть результатами операторов INSERT
, UPDATE
и DELETE
.
Чтобы начать использовать безопасность на уровне строк, вам нужны две вещи: включить ее для таблицы и определить политику, которая будет контролировать доступ на уровне строк.
Основываясь на предыдущем примере, предположим, что вы хотите разрешить пользователям обновлять только свои собственные серверы. Сначала включите RLS в таблице:
ALTER TABLE server_inventory ENABLE ROW LEVEL SECURITY;
Без какой-либо определенной политики PostgreSQL по умолчанию использует политику «запретить», что означает, что никакая роль (кроме владельца таблицы, который обычно является ролью, создавшей таблицу) не имеет к ней доступа.
Политика безопасности строк — это логическое выражение, которое PostgreSQL будет применять для каждой строки, которая должна быть возвращена или обновлена. Строки, возвращаемые оператором SELECT
, проверяются на соответствие выражению, указанному в разделе USING, в то время как строки, обновленные операторами INSERT
, UPDATE
или DELETE
, проверяются на соответствие с WITH CHECK
выражением.
Давайте определим пару настроек, которые позволяют пользователям видеть все серверы, но обновлять только свои собственные, определенные полем «владелец»:
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);
Обратите внимание, что только владелец таблицы может создавать или обновлять для нее политику безопасности строк.
Аудит
До сих пор мы в основном говорили о превентивных мерах безопасности. Следуя краеугольным принципам безопасности — defence in depth, мы рассмотрели, как они накладываются друг на друга, чтобы замедлить продвижение гипотетического злоумышленника через систему.
Ведение подробного контрольного журнала — одна из опций безопасности системы, которое часто упускается из виду. Мониторинг доступа на уровне сети или на уровне узла для вашего сервера базы данных выходит за рамки этой статьи, но давайте посмотрим, какие у нас есть возможности, когда дело касается самого сервера PostgreSQL.
Самое простое, что можно сделать для улучшения видимости того, что происходит в базе данных, — это включить подробное ведение журнала. Добавьте следующие параметры в файл конфигурации сервера, чтобы включить ведение журнала всех попыток подключения и всех выполненных операторов SQL:
; Log successful and unsuccessful connection attempts.
log_connections = on
; Log terminated sessions.
log_disconnections = on
; Log all executed SQL statements.
log_statement = all
К сожалению, это практически всё, что можно сделать с помощью стандартной self-hosted установки PostgreSQL из коробки. Конечно, это лучше, чем ничего, но данный метод не масштабируется дальше нескольких серверов баз данных и простого «grepping».
Для более продвинутого аудита PostgreSQL вы можете использовать сторонние решения, такие как pgAudit . Если вы используете self-hosted экземпляр PostgreSQL, вам придется установить расширение вручную. Некоторые размещенные версии, такие как AWS RDS, поддерживают его прямо «из коробки», поэтому вам просто нужно включить его.
pgAudit привносит большую структуризацию и детализацию в используемые операторы. Однако имейте в виду, что он по-прежнему основан на журналах, что затрудняет использование, если вы хотите отправить свои журналы аудита в структурированном формате во внешнюю SIEM систему для подробного анализа.
Заключение
Как и в случае с любой системой, разработанной с учетом требований безопасности, надлежащая защита доступа к экземпляру базы данных требует принятия защитных мер на нескольких уровнях стека.
В этой статье мы рассмотрели best practices в обеспечении безопасности базы данных PostgreSQL на нескольких уровнях, начиная с сетевой и транспортной безопасности, и изучили, как использовать гибкую систему привилегий пользователей PostgreSQL.
Дата-центр ITSOFT — услуги размещения и аренды серверов и стоек в двух дата-центрах в Москве. За последние годы UPTIME 100%. Размещение GPU-ферм и ASIC-майнеров, аренда GPU-серверов, лицензии связи, SSL-сертификаты, администрирование серверов и поддержка сайтов.