- PVSM.RU - https://www.pvsm.ru -

Допустим, аппаратная часть NAS собрана [1] и на неё установлена ОС, например, как показано здесь [2]. И сейчас у вас есть работающий сервер с Debian, который загружается, подключен в сеть, и вы имеете к нему полный физический доступ.
Теперь надо спроектировать среду, позволяющую легко и безопасно добавлять, удалять прикладные сервисы, а также управлять их работой.
Вдохновившись статьёй от некоего Cloud Architect [3], я решил сделать систему, в которой большинство служб работают в контейнерах.
Кроме того, сходные методы (например, разделение пространств ввода-вывода путём контейнерной виртуализации) используются в достаточно ответственных системах атомной индустрии [4].
Это очень удобно и безопасно:
Но в его варианте есть и то, что мне не понравилось:
В данной статье предложен вариант, который работает на моём NAS и пока меня вполне устраивает.
С целью повышения удобочитаемости, тема разбита на две статьи: проектирование и реализация (будет описана чуть позже).
Под систему отведено две SSD, причём вторая SSD является зеркалом первой.
Структура данных на SSD:
part_boot — раздел с загрузчиком. Размер = 1GB.part_system — раздел с системой. Размер = 32 GB (Рекомендованный размер: 16 GB * 2).part_slog — раздел с SLOG. Размер = 5 GB.part_system и part_slog зашифрованы в XTS режиме.
В целом, их организация такова:
SSD1: [part_boot] -> [zfs_mirror] <---> SSD2
SSD1: [part_system] -> [crypto_xts] -> [zfs_mirror] <---> SSD2
SSD1: [part_slog] -> [crypto_xts] -> [zfs_zil_mirror] <---> SSD2
Дублирование разделов производится средствами ZFS.
Нижний слой зашифрован, используя XTS режим на случайном ключе.
Содержит два раздела:
part_swap — раздел подкачки. Размер = 48 GB (max RAM * 1.5 = 32 GB * 1.5).part_l2arc — L2ARC. Размер = 196 GB (ARC size * [3..10], ARC size = 0.6 * max RAM size, т.е. 58 — 196 GB, кроме того, с выключенной дедупликацией нужно ~1 GB L2ARC на 1 TB данных).swap и l2arc зашифрованы случайным ключом.
Случайный ключ для раздела подкачки приемлем, т.к. система не будет использовать режим гибернации.
Под L2ARC выделяется всё оставшееся место, его реальная нужность при размере памяти до 32 GB сомнительна.
Размер L2ARC требуется точнее настроить в процессе работы системы по статистике кэш-попаданий.
Организация:
SSD3: | -> [part_swap] -> [crypto_xts] -> [system swap]
| -> [part_l2arc] -> [crypto_xts] -> [l2arc]
Т.к. на первом этапе планировалось использовать 4 диска из 8-ми возможных, все диски в корзине включены в 2 ZFS VDEV.
Каждый диск первым имеет слой шифрования XTS. Поверх него организуется физическое устройство ZFS.
4 физических устройства объединены в один RAIDZ1. Если не жалко дискового пространства, либо устройств больше (например, планируется сразу закупить все диски), рекомендуется сделать RAIDZ2 и один массив.
Отсюда вывод: всё-таки несмотря на "заверения экспертов" располагать ZFS pool лучше поверх LUKS, а не наоборот. LUKS почти не вносит оверхед (с AES-NI). А кэширование записи диска всегда возможно включить вручную (как и подобрать размер блока, который у ZFS, к тому же, переменный).
Полная схема такова:
HDD1: [crypto_xts] -> [zfs_phdev] |
HDD2: [crypto_xts] -> [zfs_phdev] |
HDD3: [crypto_xts] -> [zfs_phdev] | -> [RAIDZ1] -> [tank0]
HDD4: [crypto_xts] -> [zfs_phdev] |
HDD5: [crypto_xts] -> [zfs_phdev] |
HDD6: [crypto_xts] -> [zfs_phdev] |
HDD7: [crypto_xts] -> [zfs_phdev] | -> [RAIDZ1] -> [tank1]
HDD8: [crypto_xts] -> [zfs_phdev] |
NAS будет содержать различные прикладные системы, описание которых приведено ниже.
Каждая система добавляет свой каталог в фиксированные точки структуры каталогов пула, но имена каталогов для каждой системы будут описаны при её проектировании.
Ниже приведена файловая структура пула, общая для всех систем:

@startsalt
{
{T
+/
++ tank0
+++ docker
++++ lib
++++ services
+++ apps
+++ repos
+++ user_data
++++ music
++++ videos
++++ pictures
++++ Хранилища пользователей.
++ tank1
+++ apps
+++ user_data
}
}
@endsalt
На диаграмме:
tank0/docker — данные служб, запущенных в Docker.
lib — служебные файлы докера. Это отдельная ФС, и требуется для того, чтобы докер не засорял /var снэпшотами.services — описание контейнеров (например, файлы dockercompose) и их служебные файлы.tank0/apps — хранилище является корнем, в котором создаются хранилища для пользовательских приложений. Внутри подкаталога для приложения, приложение вольно располагать данные так, как считает нужным.tank0/repos — репозитории. Здесь будут храниться пользовательские данные под версионным контролем. Поскольку я планирую использовать только Git, там будут содержатся непосредственно репозитории. Но вообще, в данном каталоге могут быть подкаталоги для разных систем контроля версий.tank0/user_data — хранилище является корнем, в котором создаются хранилища для пользовательских данных:
tank1/apps — хранилище является корнем, в котором создаются хранилища для пользовательских приложений. Повторяет структуру в tank0.tank1/user_data — хранилище является корнем, в котором создаются хранилища для пользовательских данных. Повторяет структуру в tank0.Хранилище tank1 выделено на перспективу: оно будет реализовано в случае расширения дискового пространства.
Так выглядит NAS в контексте взаимодействующих с ним систем:
@startuml
' -----------------------------------------------------
'left to right direction
scale 0.72
package Internet #efefff {
cloud "Let'snEncrypt" as le {
}
cloud "Cloud DNS" #ffffff {
frame "A-записи" as af {
artifact "system.NAS.cloudns.cc" as d1
artifact "omv.NAS.cloudns.cc" as d2
artifact "ldap.NAS.cloudns.cc" as d3
artifact "ssp.NAS.cloudns.cc" as d4
artifact "cloud.NAS.cloudns.cc" as d5
artifact "git.NAS.cloudns.cc" as d6
artifact "backup.NAS.cloudns.cc" as d7
}
frame "TXT-записи" as tf {
artifact "Domain:_acme-challenge.*.NAS.cloudns.ccnTxt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
cloud "Почтовый сервер" as ms #ffffff
}
'Internet end
package LAN #efffef {
node Роутер as router #ffffff {
component "DNS сервер" {
artifact ".*.nas" as ndns
}
component "NAT" {
artifact "Порт 80" as rop80
artifact "Порт 443" as rop443
artifact "Порт 5022" as rop5022
}
}
node NAS #ffefef {
component "Порты" {
artifact "Порт 22" as nasp22
artifact "Порт 80" as nasp80
artifact "Порт 443" as nasp443
}
package Docker {
component "letsencrypt-dns" as led {
}
component "nginx-reverse-proxy" as nrp {
}
component "nginx-local" as ngl {
}
component "LDAP сервер и его WEB GUI" as ldaps {
}
component "LDAP SSP" as ssp {
}
collections "Прочие сервисыn(Backup, Cloud, DLNA, etc.)" as services
}
artifact "Файлы сертификата" as cert_file
component "WEB интерфейс OMV" as omvwg {
}
component "Демоны OMV" as omv {
}
component "SSH сервер" as sshd {
}
component "NUT" as nut {
}
}
}
'LAN end
node ИБП as ups #ffefef
actor Пользователь as user
user <- af
user <-> router
user <- ms
nut <-- ups : USB
le <-- af
le <-- tf
led .-> tf : Черезnроутер
led <-. le : Черезnроутер
led .> cert_file
nrp <. cert_file
af -> router
ndns -> NAS
rop80 <--> nasp80
rop443 <--> nasp443
rop5022 <--> nasp22
nasp22 <--> sshd
nasp80 <--> nrp
nasp443 <--> nrp
nrp <--> ngl : Сетьndocker0
nrp <--> ldaps : Сетьndocker0
nrp <--> ssp : Сетьndocker0
ldaps <--> ssp : Сетьndocker0
nrp <--> services
ngl <--> omvwg : Сеть хоста
omv <--> omvwg : Сеть хоста
omv .> ms : Черезnроутер
omv ..> sshd : Управлениеnиз WEB GUI
omv ..> nut : Управлениеnиз WEB GUI
@enduml
На этой загромождённой диаграмме отражён состав его служб и большинство взаимодействий.
Далее компоненты, назначение служб и алгоритмы функционирования будут расписаны подробнее.
Система, как было показано выше, установлена на SSD, включённых в зеркало средствами ZFS. В качестве ОС выбран OpenMediaVault [7] — система управления хранилищем и WEB GUI (далее — OMV).
Его достаточно просто установить пакетом, а всё остальное будет подтянуто по зависимостям: ядро, дополнительные репозитории и т.п..
Центральными компонентами являются:
На физической машине работает только OMV, SSH и демоны, к которым не обращается пользователь. Все остальные системы работают внутри Docker-контейнеров.
Аутентификация пользователей производится посредством LDAP. Это сделано потому, чтобы управлять пользователями централизованно, и большинство сервисов поддерживает этот механизм, в отличие от, например RADIUS сервера [10] и подобных пусть и более удобных, новых и легковесных решений.
LDAP сервер работает в контейнере, но обращение к нему доступно также из сети хоста.
Сервисы (gitlab, OMV, облако и т.д.) настраиваются на использование LDAP сервера.
Менять пароли пользователи могут с использованием LDAP Self Service Password [11].
Пользователя при добавлении в систему предварительно нужно зарегистрировать, используя консоль, либо WEB-интерфейс. Я использую PHP LDAP Admin [12].
При желании дать пользователям права на действия с ОС, возможно использовать PAM LDAP [13].
Как видно на диаграмме развёртывания, NAS находится за роутером в локальной сети. В идеале, с целью повышения безопасности сети, неплохо бы изолировать его в DMZ с использованием второго роутера, но это не обязательно.
Пользователь может обращаться к NAS как из сети Интернет, так и из локальной сети. Любое обращение затрагивает роутер.
Т.к. система имеет несколько интерфейсов, к роутеру подключено более одного одновременно (в моём случае два) и организован их бондинг [14].
Во-первых, интерфейсы в таком случае не простаивают без дела.
Во-вторых, это повышает надёжность, а в некоторых режимах и пропускную способность.
Как возможно видеть из диаграммы, в работе системы принимает участие роутер.
В случае доступа из внешней сети, роутер организует проброс портов. Порты 80 и 443 служат для организации доступа к сервисам по HTTP и HTTPS соответственно. Порт 5022 пробрасывается на порт 22 NAS для доступа по SSH. В идеале лучше иметь некую дисциплину назначения номеров портов: например, порты 10001-10999 назначаются для доступа к сервисам хоста в NAS, порты 110001-11999 назначаются для доступа ко второму домашнему серверу и т.п..
При доступе из Интернета нужно иметь возможность привязать несколько доменных имён к своему IP. Это реализуется разными способами, но я использую вариант с облачным DNS, предоставляющим DNS зону. В качестве такового, был применён ClouDNS [15].
В случае доступа из локальной сети, роутер предоставляет DNS сервер. Если перенести DNS сервер с роутера на NAS, система будет полностью автономной. Но это не имеет особого смысла, т.к. без функционирующего роутера, который служит для организации локальной сети (в том числе, и подключения NAS в сеть) и связи с внешними сетями, нормально пользоваться NAS всё-равно не получится.
У его DNS сервера должна быть возможность возвращать определённый IP, если имя попадает под регулярное выражение.
Для NAS там есть две записи: ".*.nas" и ".*.NAS.cloudns.cc", где NAS — зарегистрированная в ClouDNS зона.
В результате, независимо от того, есть ли Интернет, роутер перенаправит все обращения из локальной сети к доменам в зоне NAS.cloudns.cc на NAS.
HTTP запрос, попав на порт NAS 80 или 443, перенаправляется на порт контейнера с nginx-reverse-proxy.
Он возвращает пользователю подписанный сертификат для безопасного доступа по HTTPS. Затем, в зависимости от доменного имени, переадресует запрос контейнеру с нужной службой. Например, запрос на cloud.NAS.cloudns.cc будет перенаправлен контейнеру, в котором работает персональное облако.
Есть два типа служб:
В случае служб второго типа, перенаправление организуется через контейнер nginx-local, содержащий шаблоны доменных имён "железного" хоста.
Процесс прохождения запроса внутри NAS показан на диаграмме ниже. Процесс обновления сертификата, указанный на диаграмме, описан далее.
@startuml
actor Пользователь as user
participant "cloudns" as cld
participant "letsencrypt-dns" as led
participant "Let's Encrypt" as le
participant "nginx-reverse-proxy" as nrp
participant "Приложение" as app
participant "nginx-local" as ngl
user -> cld : Получение IP от Cloudnsn[nas.nas.cloudns.cc]
user <- cld : Возврат IPn[1.2.3.4]
user -> cld : Получение сертификата
user <- cld : Сертификат
group Периодическая генерация сертификата
led -> le : Запрос генерацииnсертификата
led <- le : Контрольное значение
led -> cld : DNS challenge,nиспользуя API
led <- cld : Сертификатnзарегистрирован
led -> le : Проверить доменnи получить сертификат
cld <- le : Проверка принадлежности домена
cld -> le : Успешно
led <- le : Подписанныйncертификат
end
user -> nrp : Запрос по IP [1.2.3.4]nна порты 80, 443,nс именем хоста [nas.nas.cloudns.cc]nидёт на прокси
nrp -> app : Запрос к приложениюnна виртуальном хосте [nas.*]nпо межконтейнернойnсети docker0
group Запрос к приложению не в Docker контейнере
app --> ngl : Проброс в сеть localhost
ngl -> "Приложение на хосте" : Запрос
ngl <- "Приложение на хосте" : Результат
app <-- ngl : Возврат результата
end
nrp <- app : Результат
user <- nrp : Возврат пользователю результата приложения
@enduml
Периодически контейнер с letsencrypt-dns получает сертификат для группы доменов. Получение сертификата реализуется с использованием certbot [17]. Перед получением сертификата, сервис Let's Encrypt производит проверку того, принадлежит ли домен тому, кто запрашивает на него сертификат.
С случае с ClouDNS, это делается с использованием так называемого DNS challenge [18]:
Вставка в TXT запись может производиться вручную, а может с использованием API. Для того, чтобы унифицировать доступ к разным DNS провайдерам, существует библиотека и инструмент Lexicon [19].
К сожалению, у ClouDNS существует минус: его API платное. С учётом того, сертификат я приделал не сразу, и мне не хотелось всё переделывать, я просто купил доступ, который стоит $42 за 2 года (да и зажимать сорок баксов, при совокупной цене NAS более $3000 было бы странно).
При желании возможно найти нормальные сервисы с бесплатным API.
Инфраструктурные сервисы, касающиеся сети, которые требуются для функционирования и обслуживания:
Возможно использовать nginx-proxy-companion для получения сертификатов, но он у меня не заработал.
В качестве ядра системы управления питанием был выбран демон NUT [20], который поддерживается плагином OMV и которому нет серьёзных универсальных альтернатив.
Соответственно, источник бесперебойного питания Eaton [21] был изначально выбран так, чтобы проблем в связке Linux+NUT с ним не возникало.
Eaton-ы, в данном случае вообще очень хорошо поддерживаются [20]. Единственный серьёзный его недостаток — это шум. Но он легко исправляется заменой вентилятора, что было описано в "железной" статье.
Для того, чтобы NAS правильно взаимодействовал и ИБП требуется настроить реакции на события, которые описаны ниже.
При достижении батареей ИБП предела срока службы, выполняется:
При отключении питающей сети на время более 1 минуты, выполняется:
При понижении заряда батареи ниже критического, выполняется:
Здесь приведены только общие меры безопасности, касающиеся именно NAS:
Для того, чтобы понизить вероятность отказа системы, используются:
В случае достижения параметров SMART какого-либо диска критических значений, выполняется:
Управление ПО системы осуществляется через:
Система предоставляет основные сервисы, доступные по HTTPS (в зоне NAS.cloudns.cc из Интернет, либо в зоне nas в локальной сети) :
Дополнительные подсистемы будут добавлять свои интерфейсы, которые описываются таким же образом.
Предварительный состав систем:
Каждая из систем будет описана отдельно.
Т.к. СУБД требуется для большинства систем, на начальном этапе было желание выбрать одну СУБД, на основе возможностей и требований подсистем, и запустить в единственном экземпляре. Но в итоге, выяснилось, что использовать несколько СУБД, в зависимости от реализации подсистемы, проще и не особенно затратно по ресурсам. На этом варианте я пока остановился.
Задачи данной системы:
Состав:
Автор: artiom_n
Источник [31]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/nginx/286953
Ссылки в тексте:
[1] аппаратная часть NAS собрана: https://habr.com/post/353012/
[2] здесь: https://habr.com/post/351932/
[3] статьёй от некоего Cloud Architect: https://habr.com/post/328048/
[4] системах атомной индустрии: http://www.ndexpo.ru/mediafiles/u/files/materials_2017/Vladykin_NIIIS_ZPP_Sinergiya.pdf
[5] OMV Extras: http://omv-extras.org/
[6] Image: https://habrastorage.org/webt/5-/rq/v1/5-rqv1ragvekziaermc8gt7fa4e.png
[7] OpenMediaVault: http://www.openmediavault.org
[8] Nginx proxy: https://github.com/jwilder/nginx-proxy
[9] Docker: https://www.docker.com
[10] RADIUS сервера: https://ru.wikipedia.org/wiki/RADIUS
[11] LDAP Self Service Password: https://ltb-project.org/documentation/self-service-password
[12] PHP LDAP Admin: http://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page
[13] PAM LDAP: https://wiki.debian.org/LDAP/PAM
[14] бондинг: https://ru.wikipedia.org/wiki/%D0%90%D0%B3%D1%80%D0%B5%D0%B3%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BA%D0%B0%D0%BD%D0%B0%D0%BB%D0%BE%D0%B2
[15] ClouDNS: https://www.cloudns.net
[16] Image: https://habrastorage.org/webt/fe/-m/co/fe-mcovzkawnlfrnhxfh2ef_ch8.png
[17] certbot: https://certbot.eff.org/
[18] DNS challenge: https://github.com/Neilpang/acme.sh/blob/master/README.md
[19] Lexicon: https://github.com/AnalogJ/lexicon
[20] NUT: https://networkupstools.org
[21] Eaton: http://powerquality.eaton.ru/Products-services/Backup-Power-UPS/9130.aspx
[22] fail2ban: https://www.fail2ban.org/wiki/index.php/Main_Page
[23] https://nas.nas: https://nas.nas
[24] https://omv.nas: https://omv.nas
[25] https://ssp.nas: https://ssp.nas
[26] https://ldap.nas: https://ldap.nas
[27] документ ФСТЭК: https://fstec.ru/component/attachments/download/289
[28] Microsoft SDL: https://www.microsoft.com/en-us/sdl
[29] уже находили уязвимости: https://www.ixbt.com/news/2017/11/13/intel-management-engine.html
[30] утилита по зачистке IME: https://github.com/corna/me_cleaner
[31] Источник: https://habr.com/post/359344/?utm_campaign=359344
Нажмите здесь для печати.