Введение

MongoDB — одна из самых популярных баз данных с открытым исходным кодом. К сожалению, как следствие мы имеем огромное количество неправильно настроенных и незащищенных разверток MongoDB по всему миру. Только за последние пару лет мы стали свидетелями нескольких крупных взломов, обнаживших уязвимости тысяч баз данных MongoDB в сети, что сделало их легкой добычей для злоумышленников.

Однако все может быть по-другому. Есть множество мер, которые вы можете предпринять для обеспечения безопасности ваших данных в MongoDB — от защиты периметра сети до включения Strict-Transport-Security, чтобы использовать такие фичи, как расширенное управление пользователями в MongoDB и систему контроля доступа на основе ролей (Role-Based Access Control — RBAC).

В этой статье мы рассмотрим некоторые из наиболее популярных способов защиты кластера MongoDB.

Смените порт по умолчанию

Начнем с самого простого. Как и любая другая база данных, по умолчанию MongoDB прослушивает клиентские подключения на порту 27017, что ни для кого не является секретом. Сканирование портов (port scanning) — излюбленная техника хакеров (и к тому же достаточно простая), поэтому замена порта прослушивания по умолчанию на какой-либо другой — всегда хорошая идея.

Изменить этот порт можно в параметре конфигурации net.port:

net:
  port: 38128

Такая простая предосторожность, вероятно, предотвратила бы значительное количество вышеупомянутых нарушений безопасности данных.

Ограничьте прослушиваемые интерфейсы 

Начиная с версии 3.6, MongoDB по умолчанию привязывается к localhost. Эту настройку по умолчанию, вероятнее всего, вам придется изменить, если только все ваши клиенты не подключаются с того же узла.

Используйте параметр конфигурации net.bindIp, чтобы указать IP-адрес(-а), которые ваши клиенты будут использовать для подключения к серверу, или DNS-имена, которые сделают конфигурацию устойчивой к изменениям IP и пригодятся для создания наборов реплик и сегментированных кластеров:

net:
  bindIp: localhost,192.168.0.1,mongo-1.internal

Не используйте параметр net.bindIpAll и 0.0.0.0 в качестве IP привязки — это заставит сервер прослушивать все доступные интерфейсы.

Используйте сокеты домена Unix

Если ваши клиенты все-таки подключаются с того же узла, в Unix-подобных системах можно полностью отключить прослушивание TCP-сокетов и перевести клиентов на подключение через сокеты домена Unix.

Чтобы это сделать, задайте путь к сокету в bindIp и настройте права доступа к сокету, чтобы разрешить вашим клиентам к нему подключаться:

net:
  bindIp: /tmp/mongodb-27017.sock
  unixDomainSocket:
    filePermissions: 0660

Вы можете использовать команду netstat, чтобы убедиться, что сервер MongoDB не прослушивает никаких интерфейсов с этой конфигурацией:

sudo netstat -lpten | grep mongo

Ограничьте доступ к сети с помощью файрвола

Однако чаще всего вашим клиентам необходимо подключаться к серверам MongoDB по сети. После изменения порта по умолчанию и ограничения адресов прослушивания, следующий уровень защиты, который вы можете (и должны) использовать, — это защита доступа к сети.

В Linux вы можете использовать утилиту iptables, чтобы заблокировать на машине, на которой работает ваш сервер MongoDB, все порты кроме тех, которые требуются вашим клиентам, и разрешить соединения только с доверенных IP-адресов:

# Сохраняем установленные соединения.
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Открываем порт SSH для локальной подсети.
# Стоит также переместить SSH с его дефолтного порта 22 - это распространенная практика.
iptables -A INPUT -p tcp -m state --state NEW --dport 42422 -s 192.168.0.0/24 -j ACCEPT

# Открываем порт MongoDB для локальной подсети.
iptables -A INPUT -p tcp -m state --state NEW --dport 38128 -s 192.168.0.0/24 -j ACCEPT

# Разрешаем весь исходящий трафик, и отбрасывать весь остальной входящий.
iptables -A OUTPUT -j ACCEPT
iptables -A INPUT -j DROP
iptables -A FORWARD -j DROP

Используйте DROP вместо REJECT для всего остального входящего трафика — это усложнит работу злоумышленника, если он решит просканировать порты на вашем сервере, чтобы увидеть, какое программное обеспечение на нем работает.

Также, когда вам приходится иметь дело с iptables, вместо того чтобы перезагружать цепочки напрямую лучше использовать инструмент iptables-apply, чтобы не заблокировать себя в случае ошибки в правилах.

Используйте переадресацию портов SSH

Конфигурацию файрвола можно изолировать еще больше, разрешив только один входящий порт SSH, если вы используете возможность протокола SSH перебрасывать порты (port forwarding).

Чтобы сделать это, установите перенаправление локального порта перед подключением к серверу MongoDB:

ssh -N -L 27017:127.0.0.1:38128 -p 42422 <mongodb-server-user>@<mongodb-server-host>

Эта команда установит соединение с сервером MongoDB через SSH по порту 42422 и начнет перенаправлять порт 27017 на локальном компьютере на порт 38128 на сервере, чтобы ваши локальные клиенты (например, mongo shell) могли просто подключаться к localhost:27017.

Эту конфигурацию можно сделать постоянной, используя файл конфигурации SSH (~ /.ssh/config):

Host mongodb
    Hostname <mongodb-server-host>
    User <mongodb-server-user>
    LocalForward 27017 127.0.0.1:38128

С этой конфигурацией, переадресация локального порта будет устанавливаться всякий раз, когда вы выполняете ssh mongodb.

Используйте обратное SSH-туннелирование 

Обратное туннелирование, также известное как переадресация удаленного порта (remote port forward), — еще одна возможность SSH, которая может пригодиться при ограничении сетевого доступа к серверу базы данных.

Обратной стороной медали является то, что сервер должен иметь возможность связаться с клиентской машиной для формирования туннеля. Однако положительным моментом этой технологии является то, что она позволяет отключить все входящие подключения к серверу из ненадежных сетей.

Следующая команда, запущенная с сервера MongoDB, создаст обратный туннель к клиентскому хосту и начнет переадресацию порта 27017 клиентского хоста на локальный порт 38128:

ssh -f -N -T -R 27017:127.0.0.1:38128 <client-user>@<client-host>

Чтобы это работало, клиентский хост должен быть доступен с сервера, иметь запущенный демон SSH и позволить <client-user> войти в систему.

Подключайтесь с хоста-бастиона

Использование бастиона или jump-сервера — это обычная практика для ограничения доступа к вашей внутренней инфраструктуре. Бастион действует как клиентский хост - вы никогда не подключаетесь к своему серверу MongoDB (или любому другому) напрямую со своего компьютера.

В сочетании с переадресацией локального порта SSH, хост-бастион может использоваться без шифрования соединения, поэтому вам даже не нужен SSH при подключении к нему:

Host mongodb
    Hostname <bastion-host>
    User <bastion-user>
    LocalForward 27017 <mongodb-server-host>:38128

Другое преимущество использования хоста-бастиона заключается в том, что он позволяет вам установить более строгую политику файрвола на серверах MongoDB, разрешая клиентские соединения только с бастиона.

Используйте X.509 аутентификацию сервера

Теперь же, когда мы прошлись по основным способам защиты сетевого периметра вашего сервера MongoDB, давайте более подробно рассмотрим, как сделать вашу клиент-серверную (и сервер-серверную в случае использования наборов реплик) связь более защищенной.

Когда-то давным-давно дистрибутивы MongoDB не поддерживали TLS по умолчанию, поэтому вам пришлось бы самостоятельно скомпилировать сервер с openssl, чтобы получить ее. К счастью, те времена давно прошли, и любая более-менее актуальная версия MongoDB может похвастаться встроенной поддержкой TLS.

Настройка TLS-аутентификации сервера позволяет подключенным клиентам проверять подлинность сервера. Для начала вам нужно получить сертификат TLS, который ваш сервер будет предоставлять клиентам, и сертификат CA (Certification Authority — удостоверяющий центр), который клиенты будут использовать для проверки того, что представленный сертификат подписан доверенным центром.

Вы можете использовать certbot для получения бесплатных сертификатов TLS от Let's Encrypt или просто создать самоподписанный CA и подписать сертификат локально с помощью openssl команд.

Создайте самоподписанный серверны CA:

openssl req -sha256 -new -x509 -days 365 -nodes </span>

    -out server-ca.crt </span>

    -keyout server-ca.key

Сгенерируйте CSR для сервера. Введите имя хоста, который вы будете использовать для подключения к базе данных, в поле Common Name (CN). Также не забудьте добавить хотя бы один из атрибутов "Organization" (O) или "Organizational Unit" (OU). Их значение не важно, если оно одинаково для всех членов кластера — оно будет использоваться для аутентификации члена кластера, о чем мы поговорим позже:

openssl req -sha256 -new -x509 -days 365 -nodes \
    -out server-ca.crt \
    -keyout server-ca.key

Подпишите сертификат сервера:

openssl req -sha256 -new -nodes \
    -subj "/CN=mongo-1.internal/O=ACME" \
    -out mongo-1.csr \
    -keyout mongo-1.key

В многоузловых кластерах нужно подписывать сертификат для каждого узла. MongoDB требует, чтобы сертификат и ключ находились в одном файле, поэтому объедините их в один PEM-файл:

cat mongo-1.crt mongo-1.key > mongo-1.pem

Теперь вы можете обновить конфигурацию сервера, чтобы включить TLS. Также неплохо было бы отключить устаревшие версии TLS и заставить клиентов использовать TLS 1.3:

net:
  tls:
    mode: requireTLS
    certificateKeyFile: /path/to/mongo-1.pem
    disabledProtocols: TLS1_0,TLS1_1,TLS1_2

Все клиенты должны будут подключаться с использованием TLS:

mongo --tls --tlsCAFile /path/to/server-ca.crt ...

Используйте X.509 аутентификацию клиентов 

Сертификат подлинности клиента позволяет серверу подтвердить подлинность подключающихся клиентов посредством проверки, что X.509 сертификат, представленный клиентом, подписан доверенным центром сертификации.

Как и в случае с серверным TLS, сначала создайте CA для клиентских сертификатов и подпишите им сертификат пользователя, которого мы для примера назовем Элис (Alice).

Создайте самоподписанный клиентский CA:

openssl req -sha256 -new -x509 -days 365 -nodes \
    -out client-ca.crt \
    -keyout client-ca.key

Сгенерируйте клиентский CSR. Поле «Subject» идентифицирует вашего пользователя MongoDB:

openssl req -sha256 -new -nodes \
    -subj "/CN=alice" \
    -out alice.csr \
    -keyout alice.key

Подпишите сертификат клиента:

openssl x509 -req -sha256 -days 365 \
    -in alice.csr \
    -CA client-ca.crt \
    -CAkey client-ca.key \
    -CAcreateserial \
    -out alice.crt

Как и для сервера, объедините сертификат и ключ Элис в PEM-файл:

cat alice.crt alice.key > alice.pem

Обратите внимание, что при использовании X.509 аутентификации MongoDB считает именем пользователя все поле Subject сертификата клиента, поэтому созданный выше сертификат будет идентифицировать пользователя MongoDB как CN=alice (а не просто alice).

Пользователи X.509 также должны быть созданы в $external базе данных аутентификации MongoDB, поэтому подключитесь к своему серверу и создайте Элис с помощью команды createUser:

db.getSiblingDB("$external").runCommand(
  {
    createUser: "CN=alice"
  }
)

Наконец, включите X.509 аутентификацию, предоставив серверу клиентский CA-файл, который он будет использовать для проверки сертификатов, представленных клиентами:

net:
  tls:
    mode: requireTLS
    certificateKeyFile: /path/to/mongo-1.pem
    disabledProtocols: TLS1_0,TLS1_1,TLS1_2
    CAFile: /path/to/client-ca.crt

Обратите внимание, что после включения этой опции каждый клиент должен будет представить для подключения действительный клиентский сертификат:

mongo --tls --tlsCAFile /path/to/server-ca.crt --tlsCertificateKeyFile /path/to/alice.pem ...

Вы можете использовать параметр net.tls.allowConnectionsWithoutCertificates, чтобы позволить подключаться клиентам, использующим другие формы аутентификации (например, пароль).

Используйте X.509 аутентификацию участников

 Участники в наборах реплик или шардированные кластеры (sharded clusters) также могут аутентифицировать друг-друга с помощью клиентских сертификатов, что является лучшей альтернативой использования файлов-ключей.

MongoDB накладывает несколько ограничений на сертификаты, которые могут использоваться для аутентификации участников:

  • Каждый сертификат участника должен быть подписан одним и тем же удостоверяющим центром.

  • Common Name (CN) или Subject Alternative Names (SAN) должны совпадать с именем хоста сервера.

  • По крайней мере один из атрибутов Organization (O), Organizational Unit (OU) или Domain Component (DC) не должен быть пустым и быть одинаковым для всех участников.

  • Extended Key Usage должно либо включать clientAuth, либо отсутствовать.

  • Сертификат, очевидно, не может быть просроченным.

Сертификаты, которые вы сгенерировали для аутентификации сервера выше, удовлетворяют этим требованиям, поэтому все, что вам нужно сделать, это включить X.509 аутентификацию участников в конфигурации:

security:
  clusterAuthMode: x509

Чтобы проверить, что она работает, поищите сообщения об аутентификации участников в логах MongoDB, которые будут выглядеть примерно так:

{"t":{"$date":"2021-07-15T05:57:35.661+00:00"},"s":"I",  "c":"ACCESS",   "id":20427,   "ctx":"conn11","msg":"Authenticate","attr":{"db":"$external","command":"{ authenticate: 1, mechanism: \"MONGODB-X509\", user: \"CN=mongo-2,O=ACME\", $db: \"$external\" }"}}
{"t":{"$date":"2021-07-15T05:57:35.662+00:00"},"s":"I",  "c":"ACCESS",   "id":20429,   "ctx":"conn11","msg":"Successfully authenticated","attr":{"user":"CN=mongo-2,O=ACME","db":"$external","client":"172.18.0.3:57382"}}

Ограничьте IP участников

Помимо включения X.509 аутентификации участников, вы также можете ограничить соединения до участников только из авторизованных сетей.

Параметр security.clusterIpSourceWhitelist позволяет указать IP и диапазоны CIDR для приема подключений от участников набора реплик или шардированных кластеров:

security:
  clusterIpSourceWhitelist:
    - 127.0.0.1
    - ::1
    - 192.168.0.0/24

Если IP подключающегося участника (mongod или mongos) отсутствует в списке или диапазоне CIDR, он не будет аутентифицирован.

Используйте управление доступом на основе ролей

Проверка подлинности клиента, которую вы настроили ранее, гарантирует, что ваш сервер MongoDB может проверить подлинность подключающегося клиента. Теперь нужно подумать, что собственно может делать этот самый клиент после подключения.

Авторизация

MongoDB включает автоматическую авторизацию клиента, когда включена аутентификация участника (через файл-ключ или X.509). Если вы по какой-то причине решили не использовать внутреннюю аутентификацию участников, включите авторизацию явно:

security:
  authorization: enabled

Встроенные роли

Чтобы начать использовать RBAC, вашим пользователям MongoDB нужно назначить роли. Роль предоставляет разрешения на выполнение определенных действий (actions) с определенными ресурсами (resources).

Ресурс может быть коллекцией, базой данных или кластером (для действий, требующих разрешений на уровне кластера), а действия определяют операции, которые пользователь может выполнять с данным ресурсом.

MongoDB предоставляет целый ряд встроенных ролей, которые служат хорошей отправной точкой. Вы можете ознакомиться с полным списком встроенных ролей здесь но наиболее распространенными являются:

  • read: позволяет read-only доступ к несистемным коллекциям в базе данных

  • readWrite: расширяет read-only роли возможностью изменения данных в несистемных коллекциях

  • userAdmin: предоставляет разрешения на управление пользователями и ролями

  • dbAdmin: позволяет выполнять административные действия с базой данных, такие как создание или удаление коллекций и индексов, но не включает доступ к чтению.

Эти роли доступны в каждой базе данных MongoDB и привязаны к конкретной базе данных.

База данных «admin», помимо прочего, содержит роли, которые предоставляют те же разрешения, но для всего кластера: readAnyDatabase, readWriteAnyDatabase, userAdminAnyDatabase и dbAdminAnyDatabase.

Наконец, встроенная роль root обеспечивает полный доступ ко всем ресурсам и должна использоваться очень-очень редко (или лучше вообще никогда).

Давайте предоставим пользователю Элис, созданному выше, полный доступ к одной конкретной базе данных и позволим ей читать все остальное:

db.getSiblingDB("$external").runCommand(
  {
    // Используйте команду updateUser для обновления существующего пользователя.
    createUser: "CN=alice",
    roles: [
      { role: "readWrite", db: "example" },
      { role: "userAdmin", db: "example" },
      { role: "readAnyDatabase", db: "admin" }
    ]
  }
)

Роли пользователей

Иногда вам может потребоваться немного большей гибкости; например, для создания более детализированных ролей или расширения одной из встроенных ролей парой-тройкой дополнительных разрешений. Для подобных ситуаций MongoDB поддерживает определяемые пользователем роли.

Чтобы создать свою собственную роль, определите, какие действия она разрешит и над какими ресурсами. Как обсуждалось выше, ресурс - это база данных, коллекция или кластер:

// Задает коллекцию "metrics" в базе данных "example".
{ db: "example", collection: "metrics" }

// Определяет все несистемные коллекции в базе данных "example".
{ db: "example", collection: "" }

// Определяет коллекции "metrics" для всех баз данных.
{ db: "", collection: "metrics" }

// Определяет все несистемные коллекции во всех базах данных.
{ db: "", collection: "" }

// Определяет разрешения для всего кластера.
{ cluster: true }

MongoDB предоставляет множество действий, которые позволяют пользователям запрашивать документы (find), изменять данные (insert, update, remove) или пользователей и роли (createUser, createRole и т. д.), выполнять администрирование сервера и управление набором реплик и практически все остальное, что может потребоваться пользователю или администратору MongoDB. Полный список можете посмотреть здесь.

Документ полномочий (privilege document) объединяет определение ресурса со списком действий:

// Разрешить read-only доступ к конкретной базе данных.
{ resource: { db: "example", collection: "" }, actions: [ "find" ] }

// Разрешить доступ для чтения и записи к определенной коллекции.
{ resource: { db: "example", collection: "metrics" }, actions: [ "find", "update", "insert", "remove" ] }

// Разрешить пользователю останавливать сервер.
{ resource: { cluster: true }, actions: [ "shutdown" ] }

Теперь давайте создадим роль для пользователя «metrics writer», которому разрешено вносить записи только в одну конкретную коллекцию. Кстати, ваша кастомная роль также может расширять существующую роль, чтобы унаследовать ее разрешения:

db.createRole(
  {
    role: "metricsWriter",
    privileges: [
      { resource: { db: "", collection: "metrics" }, actions: [ "insert" ] }
    ],
    roles: [
      { role: "readWrite", db: "metrics" }
    ]
  }
)

Пользовательские роли также могут накладывать ограничения аутентификации для подключающихся клиентов, проверяя, что IP клиента разрешен:

db.createRole(
  {
    role: "metricsWriter",
    privileges: [ ... ],
    roles: [ ... ],
    authenticationRestrictions: [
      {
        clientSource: [ "127.0.0.1", "192.168.0.0/24" ]
      }
    ]
  }
)

Это функция безопасности очень полезна в сочетании с правильной конфигурацией вашего файрвола, что позволит вам более гибко разделять клиентов в сети.

Реализуйте логирование доступа и жрунал запросов

Ведение подробного контрольного журнала является неотъемлемой частью усиления безопасности любой системы. К сожалению, MongoDB не предоставляет встроенного функционала логирования в своей версии с открытым исходным кодом. 

Система логирования MongoDB является компонентной. В целях безопасности вас больше всего интересуют следующие компоненты:

  • network для отражения попыток подключения

  • accessControl для отражения информации аутентификации

  • command, query и write для отражения активности команды

Включите это в конфигурацию:

systemLog:
  component:
    accessControl:
      verbosity: 1
    network:
      verbosity: 1
    command:
      verbosity: 1
    query:
      verbosity: 1
    write:
      verbosity: 1

Журналы MongoDB представляют собой структурированные JSON-документы, поэтому вы также можете отправлять их во внешнюю систему, такую ​​как Elasticsearch, для индексации и анализа с помощью таких инструментов, как Logstash.

Используйте Teleport для безопасного доступа

Teleport Database Access — это продукт с открытым исходным кодом, который реализует многие из лучших практик, обсуждаемых в этой статье, и может использоваться для безопасного доступа к вашим кластерам MongoDB.

В частности, Database Access был разработан с учетом следующих принципов безопасности:

  • Ваши базы данных MongoDB никогда не окажутся в публичном доступе - они подключаются к плоскости управления Teleport через устойчивые обратные туннели.

  • Пользователи проходят аутентификацию с помощью вашего поставщика идентификации и подключаются к базе данных с помощью краткосрочных клиентских сертификатов.

  • Настраиваемые политики управления доступом на основе ролей позволяют сопоставлять пользователей вашего IdP с разрешенными пользователями MongoDB.

  • Все авторизации к базе данных и действия команд фиксируются в логе, который можно отправлять в сторонние SIEM-системы.

Начать работу с Database Access можно, изучив документацию и руководство для MongoDB.

Заключение

Простота использования MongoDB делает его популярным выбором среди команд любого размера, которым требуется какое-нибудь NoSQL решение, и привлекательной целью для злоумышленников.

В этой статье мы рассмотрели некоторые из лучших практик и методов усиления защиты кластера MongoDB, от защиты периметра сети до использования аутентификации и авторизации для управления доступом к базе данных.

Мы надеемся, что обсуждаемые темы помогут вам сделать ваши развертки MongoDB более защищенными.


Материал подготовлен в рамках курса «MongoDB».

Всех желающих приглашаем на demo-занятие «Map-reduce & Aggregation Framework». На занятии рассмотрим:

- концепция map-reduce;
- зачем нужен map-reduce в MondoDB;
- концепция aggegation pipeline;
- структура и синтаксис агрегации;
- $match;
- стадия $group;
- стадия $lookup.

В результате вы научитесь строить сложные запросы и писать свои функции.
>> РЕГИСТРАЦИЯ

Комментарии (1)


  1. Undiabler
    08.11.2021 22:07

    Для тех кто будет пользоваться данной статьей как мануалом: Обратите прежде свое внимание на saas версию кластера от разработчиков mongodb. Возможно для вашего проекта дешевле будет взять saas версию и получить весь набор функционала mongodb с хорошей защитой из коробки, чем делать эти все махинации вручную и в последствии их поддерживать и обслуживать.