Синопсис
Не секрет, что у многих из нас остался 1 Тб свободного места на MRU-Облаке со времён его бета-теста. Объём приличный по нынешним меркам — но что с ним делать? Фотографии и своё видео туда просто так заливать не очень хочется — взломы аккаунтов случаются нередко, да и в любом случае — облако это облако, и нельзя сбрасывать со счетов тот простой факт, что любое облачное хранилище принадлежит коммерческой компании, в интересах которой использовать его для собственной выгоды. А значит, нужен дополнительный слой защиты, например, EncFS. Полистаем хабр и гитхаб, вроде бы имеется решение о шифровании данных в своём облаке. Но есть неочевидное, но весьма важное неудобство, о котором в исходной статье не упоминается — для того, чтобы синхронизироваться, нужно локально хранить 600Гб шифрованных фотографий. Для скромных локальных хранилищ, в которых и нешифрованные 600Гб фотографий едва умещаются, это слишком много.
Идея
Поэтому был придуман честолюбивый план — написать FUSE-файловую систему для Облака, а уже поверх неё смонтировать EncFS (для лучшей безопасности стоит сгенерить ключ GPG и использовать ecryptfs, т.к. современные компьютерные мощности уже позволяют взломать EncFS). В некотором роде это будет похоже на то, как пользуясь облаком MEGA мы перетаскиваем файл в окошко браузера — применяется Е2Е-шифрование и файл отправляется в облако, ничего не занимая локально. Назвать было решено "MARC-FS" — MAail.Ru Cloud FileSystem. С её помощью мы уходим от указанной проблемы локального хранения зашифрованных данных — копируя данные в каталог EncFS мы будем точно так же автоматически шифровать их и отправлять в облако. На самом деле уровнем пониже мы будем вызывать довольно длинную цепочку преобразований вида cp -> kernel FUSE module -> userspace libfuse hook -> EncFS -> FUSE kernel -> libfuse hook ->MARC-FS -> kernel network stack, но упрощённая схема работы будет выглядеть примерно так:
+---------------+
| |
| Local data |
| |
| /media/DATA/ |
| |
+---------------+
|
|
|
|
+-------v-------+
| |
| EncFS |
| |
| ~/remote-enc |
| |
+---------------+
|
| FUSE
| encryption
|
+-------v-------+
| |
| MARC-FS |
| |
| ~/remote |
| |
+---------------+
|
| FUSE
| networking
|
+-------v-------+
| |
| |
| cloud.mail.ru |
| |
| |
+---------------+
Реализация
План амбициозный, но на деле пришлось столкнуться со многими ограничениями как со стороны FUSE и ядра, так и со стороны Облака. Самое, наверное, тревожащее — то, что в Облаке применяется дедупликация данных. То есть файлы, имеющие одинаковые хэши и размеры, хранятся в некоем общем пуле, а пользователям после закачки просто добавляется "хардлинк" на них. Понятное дело, ваши зашифрованные файлы будут уникальны, т.к. зашиты вашим ключом, в связи с чем есть опасение как бы Mail.ru не обратило внимание на такие личные облака и не предъявило рекламацию по этому поводу. Будем надеяться, пользователей OS X/Linux, посмевших при этом ещё и бета-тестировать Mail.ru, было не очень много.
Некоторые другие интересные детали:
- Облако не поддерживает закачку с
Transfer-Encoding: chunked
, а в интерфейсе FUSE нет возможности узнать заранее размер файла перед тем, как он будет записан. Приходится сперва загружать файл в память, и только потом, зная размер, отправлять по сети. - EncFS требует random read/write для своей работы, поскольку разбивает файлы на куски, которые позже шифрует. Для сетевых файловых систем (NFS, например) это страх и ужас, потому что в протоколах нет инструкций типа "записать 4096 байт вот по такому смещению от начала файла". Поэтому для корректной работы EncFS приходится держать опять-таки файл в памяти на время работы с ним (Несколько позднее я добавил возможность указать кэш-каталог, в котором будут храниться промежуточные данные, подробнее см. README.md в репозитории — примеч. автора).
- Связь с облаком устанавливается по https, а это, внезапно, добавляет довольно большой оверхед по памяти для инициализации SSL-движка. Причём не совсем ясно, как бороться с этим, и другие сетевые файловые системы с таким также сталкиваются.
- Хоть размер хранилища и терабайт, у Облака имеется ограничение на размер загружаемого одним запросом файла — в 2 Гб, и только платные услуги дают возможность заливать больше. Для библиотеки фотографий или музыки это подходит, но вот свою коллекцию HD-фильмов залить уже не получится, поэтому позже я добавил прозрачный сплит файлов — если заливать или скачивать файлы более 2Гб, ФС разобьёт или склеит их сама.
- В MacOS используется мультиплексирование файловых дескрипторов, в этом её отличие от Linux, поэтому пришлось линковать проект не с системно присутствующей
libfuse
, а сosxfuse
, доустановленной с помощью Homebrew Cask. В ней прописаны соответствующие обходные пути. Ещё, из возможных проблем могу отметить то, что монтирование одной FUSE-файловой системы в другую на MacOS по умолчанию отключено из-за проблем с рекурсиями, но это обходится с помощью опций монтирования вручную.
В итоге (а может, тем не менее) получился жизнеспособный вариант, с помощью которого удалось залить всю коллекцию музыки и фото и аниме в облако в виде шифрованных файлов. Результат работы над MARC-FS представлен здесь, там же можно найти инструкции по настройке связки с EncFS. Работа ведётся и фронт улучшений широкий, пулл-реквесты и предложения, само собой, приветствуются.
Пример шифрованного каталога:
Локально дешифрованный:
На облаке:
Пара заметок на полях
Не смог найти никакого описания API Облака, пришлось выдёргивать из смежных статей и проектов. Но надо отдать должное Mail.Ru — даже при работе в 25 потоков и сотнях запросов getattr/read/write в секунду только изредка проскакивает 500 Internal Server error в ответ. Я не так часто его вижу, но если эту статью читает кто-то из команды Облака и нужны детали — я могу вытащить несколько таких репортов.
libfuse при написании файловой системы, да и вообще сама архитектура FUSE по своему концепту напомнила о userspace client-server drivers, например в GNU/Hurd или в Plan 9 From Bell Labs. Приятно видеть, что интересные идеи, которые были заброшены с потерей актуальности этих систем, получили новую жизнь.
Многопоточность для ФС пришлось придумывать с нуля и на коленке, так что это был интересный вызов. Нет чёткой уверенности, что всё получилось в лучшем виде (и без багов), но это работает — и работает довольно быстро. Разве что есть сомнения по поводу того, как само Облако отнесётся к одновременной заливке и скачиванию файлов.
Следует предупредить, что я не тестировал подробно MARC-FS на Mac OS X. Мы с другом добились стабильной сборки и пару раз покопировали музыку в терминале, но не более. У меня нет Apple-железа под рукой и я не могу эффективно протестировать работу ФС, так что если кто-то сможет поддержать работу ФС на этой платформе, буду рад.
Дисклеймеры
Я публикую код и эту статью в надежде на то, что они могут кому-то пригодиться, но не предоставляю гарантии товарного вида или пригодности для использования. Внутреннее API Облака может измениться в любую секунду, и возможность оказаться с разбитым корытом довольно высока. Используйте на свой страх и риск.
Я знаю, что в лицензионном соглашении Mail.Ru есть строчки про запрет автоматических средств взаимодействия, но никто из legal_dep не смог понять моего вопроса в письмах и объективно прояснить ситуацию. В конце-концов, мы все используем почтовые клиенты, которые точно так же программно взаимодействуют с Mail.ru. Меня два раза редиректили на Д. А., который честно отвечал, что юридические консультации — это не его стезя.
Ссылки
P.S. Прежде чем меня предупредят о возможных последствиях написания файловых систем — я уже в курсе.
Комментарии (49)
bano-notit
13.05.2017 14:11А у меня вопрос есть, тупой скорее всего, но всё же. В Readme сказано, что нужно сначала отмонтировать прошлую encfs, из этого я делаю вывод, что работать с несколькими примонтированными системами я не могу. Это я делаю правильный вывод или нет?
Kanedias
13.05.2017 14:14Почему, можете примонтировать, скажем, одну фс в ~/remote-1, encfs для него в ~/remote-decrypted-1, вторую фс в ~/remote-2, encfs в ~/remote-decrypted-2. В README имелось в виду, что нужно отмонтировать encfs, которая смотрит на ту фс, которую вы хотите отключить.
bano-notit
13.05.2017 14:17Окей, тогда понятно. Просто подумал, что в реализации что-то не так у Вас)
Тогда, возвращаясь к балансировке, можно установить балансировщик на локальной машине, который будет балансировать между 2 marcfs.
NoRegrets
13.05.2017 23:20Когда я в одном предложении встречаю слова «облако» и EncFS, то сразу вспоминаю о проведенном аудите encfs https://defuse.ca/audits/encfs.htm, пункт 2.2… В двух словах, если злоумышленник может получить несколько версий зашифрованных файлов, задача взлома значительно облегчается. Что, на мой взгляд, делает encfs полностью бесполезной для использования в облаке.
Arqwer
14.05.2017 00:19А почему бы просто не смонтировать облако mail.ru в davFS, и EncFS создать внутри точки монтирования davFS? Mail.ru не поддерживает webdav? Или encFS так просто внутри davFS не работает?
Kanedias
14.05.2017 00:20Mail.ru не поддерживает webdav?
Нет, к сожалению. Анонс был, но… на этом всё.
Или encFS так просто внутри davFS не работает?
Тоже легко может быть. Как и говорил, encFS требует поддержки read/write блоками по файлу, что не в каждой ФС есть.
ivan386
14.05.2017 09:37То есть файлы, имеющие одинаковые хэши и размеры, хранятся в некоем общем пуле, а пользователям после закачки просто добавляется "хардлинк" на них.
Хеш считается уже в облаке или на локальном клиенте?
Если на клиенте то нет смысла передавать в облако файл который там уже есть. А в таком случае можно "загрузить" в облако файл от которого у тебя только хеш и размер и забрать его оттуда.
bano-notit
14.05.2017 10:11Скорее всего на сервере, иначе можно было бы украсть файл, не имея содержимое, а имея только его хеш...
ivan386
14.05.2017 12:05Копирование это не кража. Но на клиенте же выгодней. Кроме хеша и размера ничего передавать тогда не надо.
bano-notit
14.05.2017 12:17Для майла конечно же выгодней, но сами подумайте.
Вот у вас есть фотографии с вашей половинкой (просто для примера), которые Вам памятны, и Вы не хотите это передавать другим ну вообще никаким макаром, потому что это ваше личное дело.
А я, вот такой крутой хакер, взял, сгенерировал хеш случайным образом, и попал именно на Вас и именно на эти фотографии...
А если брать по чесноку, то сейчас есть много дебилов, которые отправляют какие-нибудь важные документы через облака… А так же бекапы, которые вообще-то зашифрованы обычно ключём, но хеш можно узнать на сервере, ведь много проще сидеть спокойно дома и качать с майла, чем через ssh тунель, да и шансов спалиться в разы меньше.
Короче, если хеш вычисляется на клиенте, то это потенциальная дыра в безопасности.
gvitalik
14.05.2017 21:30Возможно я не прав, но вычисление хэша на клиенте будет дырой, только если хэш — единственный ключ для доступа к содержимому файла. В любом случае сервер, прежде чем отдать содержимое файла, должен проверить, а если у этого пользователя доступ к содержимому с таким хэшем. В таком случае, не вижу проблемы, чтобы на клиенте считался хэш и первым делом отправлялся на сервер, а сервер уже запрашивал с клиента содержимое файла или не запрашивал, если такой хэш уже есть на сервере.
bano-notit
14.05.2017 21:34Окей, тогда вопрос, вот я посчитал хеш, отправил, и оказался уже такой файл в облаке. Что сервер будет делать? Файл то изначально не мой… Заставлять качать файл и потом разрешать мне доступ на тот, который уже есть?
Короче говоря, легче и безопаснее сделать это на стороне сервера, учитывая, что облако майла изначально работает по стандартному http, то там и не может быть каких-то изысков.
gvitalik
14.05.2017 23:38Как решение вижу, такой вариант, если файл больше размера 2-х блоков, то разбивать файл на блоки, для каждого блока считать хэш сумму и пересылать этот набор хэш сумм. Если размер файла меньше, то пересылать файл целиком и все расчеты делать на сервере. При таком подходе пересылать придется значительно меньше, а угадать хеш каждого блока в файле и при этом угадать, существование файла, становится труднее. Дополнительно можно использовать автобан, на случай, когда после нескольких попыток клиент прислал, только хэш суммы, но не разу по запросу сервера не предоставил содержимое файла.
Но в целом, я согласен, что легче все манипуляции сделать на стороне сервера.
justhabrauser
14.05.2017 10:55Отличная работа, коллега!
Пара камментов:
* не указана версия GPL (а еще лучше — положить в гитхаб текст лицензии — это как бы «прилично»)
* бы еще в README.md добавить requirements — что нужно для сборки; понятно, что по сообщениям cmake можно сделать некие выводы — но довольно сложно (например libcurl-devel, fuse-devel, jsoncpp-devel, jemalloc-devel для RH-based)
* еще можно напилить примочку для mc — там не совсем fuse, поэтому оверхед должен быть меньше.
* ну и зарелизить, что ли :-)Kanedias
14.05.2017 17:03Самое важное-то и забыл! Спасибо! Лицензия GPLv3.
бы еще в README.md добавить requirements — что нужно для сборки
Я все комментарии с этой темы к себе понемногу в TODO переношу, как вынырну в очередной раз из работы и релизов, сделаю вики. Пока что можно посмотреть зависимости для наиболее популярных дистрибутивов в gitlab-CI скрипте
grumbler66rus
16.05.2017 17:51У меня есть задача хранить в облаке резервные копии. Причём шифрование не является необходимостью, более того, лучше обойтись без него.
Пробовал Яндекс.Диск через davfs, но кеширование, которое ещё и оставляет недокачанные куски, убивает всю вкусность WEBDAV.
Насколько просто исключить шифрование из вашего кода?Kanedias
16.05.2017 18:43Шифрования вообще нет в коде, оно идёт поверх, с помощью EncFS/cryptfs. См. README в корне проекта.
Но у меня кэшированные куски тоже могут остаться, если будете пользоваться
-o cachedir
.
В общем, попробуйте, если вас устроит, сразу поймёте.
GH0st3rs
Я правильно понял, что всё это работает только с 1 аккаунтом в облаке? На сколько мне известно прикрутить несколько аккаунтов не так сложно, но придутся немного изменить структуру самой ФС
Kanedias
Всё верно, поддержку авторизации по нескольким аккаунтам сделать несложно. Потом нужно ввести балансировку файлов по разным аккаунтам с нормальный распределением. Ну и да, это всё если маилру ещё не забанят вас за твинководство на этапе тестирования ;)
GH0st3rs
С банами верно подмечено, пока тестил, заблочили пару учеток за такие тесты, якобы из-за подозрительной активности)
А по поводу балансировки, я думал можно разбить файл но количество аккаунтов и залить в каждый по кусочку
Kanedias
Можно и так, верно, полезно было бы как раз для крупных файлов > 2 Гб, после разбивки их не пришлось бы распиливать как сейчас.
kilgur
Если учетку могут заблокировать, то нельзя файлы распределять — заблокируют одну учетку, а потеряются все файлы.
Kanedias
Судя по манам mhddfs, он пишет на один диск пока там не закончится место, и только потом переключается на следующий:
ValdikSS
mhddfs и unonfs — очень старые проекты. Используйте aufs3, там есть балансировка записи. Использую его дома, для объединения нескольких HDD в «один большой» на уровне файлов, а не блочных устройств, как в RAID.
bano-notit
Балансировка в данном случае не сильно нужна… Потому что если на 2 облака писать одно и то же. Или на 2 облака писать с одного и того же ip — подозрительно как-то...
А на счёт работоспособности mhddfs могу сказать так: работает отлично) Вот я у себя запустил такое интересное сочетание, и оно работает! Правда память отжирает… Но с памятью могут проблемы из-за доступа программы к файлам в /tmp… Хотя туда вроде как писать можно всем и всё.
Kanedias
В нынешних дистрибутивах /tmp монтируется как tmpfs, напрямую из памяти, будьте осторожны.
bano-notit
Хм… Не знал, спасибо. Попробую тогда использовать другую папку для сохранения кеша.
grumbler66rus
Я отказался от mhddfs несколько лет назад потому, что она криво работает с «дырявыми» файлами и время от времени подвисает… Использовал для объединения 4 дисков.
/tmp/ был на диске.
GH0st3rs
Можно сделать что-то типа RAID и выделить один из акков под резервную часть, тогда блокировка одного аккаунта не скажется на целостности файлов
st0ne_c0ld
если на обоих учетках будут в точности одинаковые операции — будет не сложно забанить обе по паттерну… Вот если бы второй аккаунт был в другом облаке… :-)
bano-notit
Кстати говоря, я тут ночью как раз думал, на счёт другого облака. С помощь webdavfs можно подключиться к яндексу, а объединить marcfs и webdavfs с помощью mhddfs уже тоже не проблема)
bano-notit
А вообще было бы неплохо, на самом деле, с балансировкой то. А на счёт твинководства… Можно через tor попробовать пустить весь трафик)
Kanedias
Я этим могу заняться позже, скорее всего, когда опакечу ФС для популярных дистрибутивов. Пока что можете внести в issues с пометкой enhancement.
bano-notit
Я не могу ставить пометки)
dartraiden
И обязательно через выходные узлы одной страны. Иначе блокировка из-за подозрения на взлом учётной записи.
bano-notit
Ну тогда легче просто сделать свой прокси, раскидывающий запросы на другие прокси...
Self_Perfection
Но зачем, если есть mhddfs, UnionFS и всё такое?
Kanedias
О, напишите пример использования, если вас не затруднит? Я добавлю в README.
Self_Perfection
Трудно: самому понадобится читать маны.
Кроме того пример использования и не нужен, достаточно отсылки: «для объединения нескольких аккаунтов можете использовать mhddfs, UnionFS или Aufs». Если по справке этих технологий нельзя быстро понять, как ими пользоваться, то это их маны нужно улучшать, а не раздувать ридми вашего проекта.
Kanedias
Честный ответ. Спасибо за наводку в любом случае, уже читаю доки.
vladm
Сам пользуюсь вариантом, схожим с таким: https://enztv.wordpress.com/2016/10/19/using-amazon-cloud-drive-with-plex-media-server-and-encrypting-it/ Часть его решений в том или ином виде применяю с некоторыми доработками.
Да, тоже в основном для Plex. Использую безлимитный GDrive, ACD тоже, например. Объем данных — десятки терабайт. Думаю, что после половины петабайта насыщение произойдет и больше не нужно будет, поглядим. Начну еще больше внимания уделять отказоустойчивости. Сейчас только три копии в разных концах планеты. Важно все это дело использовать только для себя и не шэрить, тем более с доходом. Просто на всякий случай.
bano-notit
Проблема в том, что config.json пока что только один… А в нём, как я понял из того, что видел в readme, не поддерживается несколько файловых систем за раз. Поэтому придётся писать какой-то свой скрипт, который будет в несколько команд включать сначала marcfs, а потом их объединять с помощью того же mhddfs.
Self_Perfection
Правильнее: монтировать разные инстансы выставив различные переменные окружения $HOME, чтобы config.json читался из разных путей.
Ну да, нужно будет автоматизировать монтирование всего добра в нужном порядке. Но и без того автор предлагал бутерброд из MARC-FS + encfs, добавляется только один слой.
Автоматизировать монтирование в нужном порядке в современных реалиях видимо правильнее через systemd. А умеючи и удобнее.
bano-notit
Я думаю вы сами согласитесь, что танцы с
бубномпеременными $HOME — не лучшая идея.Ну тут палка о двух концах. Да, если мне нужно будет 2 облачка в одно сливать, то это красиво, но если я порядочный гражданин, который имеет только 1 акк у майла? Мне кажется, что лучше уж пусть закононепослушные, типа меня, будут сами объединять несколько marcfs с помощью того же mhddfs, чем заставлять человека писать ещё один велосипед.
А вот сделать в marcfs возможность из одного конфига делать много точек монтирования — будет очень полезной.
Kanedias
Я, скорее, добавлю поддержку $XDG_CONFIG_DIR, так что не придётся менять целиком $HOME. Или того лучше, сделаю опцию
--config=/path/to/conf
Self_Perfection
Да, но это лучше, чем последовательно скриптом запускать разные инстансы монтирования, перед каждым очередным запуском вписывая в config.json разные значения.
Если под человеком, которому писать велосипед, подразумевался Kanedias, то это полностью совпадает с моим отношением к ситуации.
Я больше голосую за возможность указать путь к конфигу параметром командной строки.
bano-notit
Да, человек именно Kanedias...
Вообще да, получается, что хорошим людям вообще не нужно знать о том, что можно несколько систем делать, тогда наличие флага пути к файлу конфигурации и текущая функциональность этого файла конфигурации уже достаточна.