image


Синопсис


Не секрет, что у многих из нас остался 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. Работа ведётся и фронт улучшений широкий, пулл-реквесты и предложения, само собой, приветствуются.


Пример шифрованного каталога:


image


Локально дешифрованный:


image


На облаке:


image


Пара заметок на полях


Не смог найти никакого описания 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)


  1. GH0st3rs
    13.05.2017 12:53
    +1

    Я правильно понял, что всё это работает только с 1 аккаунтом в облаке? На сколько мне известно прикрутить несколько аккаунтов не так сложно, но придутся немного изменить структуру самой ФС


    1. Kanedias
      13.05.2017 13:10

      Всё верно, поддержку авторизации по нескольким аккаунтам сделать несложно. Потом нужно ввести балансировку файлов по разным аккаунтам с нормальный распределением. Ну и да, это всё если маилру ещё не забанят вас за твинководство на этапе тестирования ;)


      1. GH0st3rs
        13.05.2017 13:54

        С банами верно подмечено, пока тестил, заблочили пару учеток за такие тесты, якобы из-за подозрительной активности)
        А по поводу балансировки, я думал можно разбить файл но количество аккаунтов и залить в каждый по кусочку


        1. Kanedias
          13.05.2017 14:11

          Можно и так, верно, полезно было бы как раз для крупных файлов > 2 Гб, после разбивки их не пришлось бы распиливать как сейчас.


        1. kilgur
          13.05.2017 19:56
          +1

          Если учетку могут заблокировать, то нельзя файлы распределять — заблокируют одну учетку, а потеряются все файлы.


          1. Kanedias
            13.05.2017 22:13

            Судя по манам mhddfs, он пишет на один диск пока там не закончится место, и только потом переключается на следующий:


            При записи файлов в файловую систему файлы пишутся на первый hdd до
            тех пор пока там есть место (см опцию mlimit), затем они пишутся на
            второй hdd, третий итп.


            1. ValdikSS
              15.05.2017 00:38

              mhddfs и unonfs — очень старые проекты. Используйте aufs3, там есть балансировка записи. Использую его дома, для объединения нескольких HDD в «один большой» на уровне файлов, а не блочных устройств, как в RAID.


              1. bano-notit
                15.05.2017 21:19

                Балансировка в данном случае не сильно нужна… Потому что если на 2 облака писать одно и то же. Или на 2 облака писать с одного и того же ip — подозрительно как-то...


                А на счёт работоспособности mhddfs могу сказать так: работает отлично) Вот я у себя запустил такое интересное сочетание, и оно работает! Правда память отжирает… Но с памятью могут проблемы из-за доступа программы к файлам в /tmp… Хотя туда вроде как писать можно всем и всё.


                1. Kanedias
                  16.05.2017 09:23

                  В нынешних дистрибутивах /tmp монтируется как tmpfs, напрямую из памяти, будьте осторожны.


                  1. bano-notit
                    16.05.2017 14:56

                    Хм… Не знал, спасибо. Попробую тогда использовать другую папку для сохранения кеша.


                1. grumbler66rus
                  16.05.2017 17:44

                  Я отказался от mhddfs несколько лет назад потому, что она криво работает с «дырявыми» файлами и время от времени подвисает… Использовал для объединения 4 дисков.
                  /tmp/ был на диске.


          1. GH0st3rs
            13.05.2017 23:10

            Можно сделать что-то типа RAID и выделить один из акков под резервную часть, тогда блокировка одного аккаунта не скажется на целостности файлов


            1. st0ne_c0ld
              14.05.2017 09:49

              если на обоих учетках будут в точности одинаковые операции — будет не сложно забанить обе по паттерну… Вот если бы второй аккаунт был в другом облаке… :-)


              1. bano-notit
                14.05.2017 10:10

                Кстати говоря, я тут ночью как раз думал, на счёт другого облака. С помощь webdavfs можно подключиться к яндексу, а объединить marcfs и webdavfs с помощью mhddfs уже тоже не проблема)


      1. bano-notit
        13.05.2017 14:10

        А вообще было бы неплохо, на самом деле, с балансировкой то. А на счёт твинководства… Можно через tor попробовать пустить весь трафик)


        1. Kanedias
          13.05.2017 14:17

          Я этим могу заняться позже, скорее всего, когда опакечу ФС для популярных дистрибутивов. Пока что можете внести в issues с пометкой enhancement.


          1. bano-notit
            13.05.2017 14:28

            Я не могу ставить пометки)


        1. dartraiden
          13.05.2017 20:57

          И обязательно через выходные узлы одной страны. Иначе блокировка из-за подозрения на взлом учётной записи.


          1. bano-notit
            13.05.2017 21:34

            Ну тогда легче просто сделать свой прокси, раскидывающий запросы на другие прокси...


      1. Self_Perfection
        13.05.2017 18:50

        Но зачем, если есть mhddfs, UnionFS и всё такое?


        1. Kanedias
          13.05.2017 18:59

          О, напишите пример использования, если вас не затруднит? Я добавлю в README.


          1. Self_Perfection
            13.05.2017 20:03

            Трудно: самому понадобится читать маны.

            Кроме того пример использования и не нужен, достаточно отсылки: «для объединения нескольких аккаунтов можете использовать mhddfs, UnionFS или Aufs». Если по справке этих технологий нельзя быстро понять, как ими пользоваться, то это их маны нужно улучшать, а не раздувать ридми вашего проекта.


            1. Kanedias
              13.05.2017 22:17

              Честный ответ. Спасибо за наводку в любом случае, уже читаю доки.


          1. vladm
            14.05.2017 16:59

            Сам пользуюсь вариантом, схожим с таким: https://enztv.wordpress.com/2016/10/19/using-amazon-cloud-drive-with-plex-media-server-and-encrypting-it/ Часть его решений в том или ином виде применяю с некоторыми доработками.
            Да, тоже в основном для Plex. Использую безлимитный GDrive, ACD тоже, например. Объем данных — десятки терабайт. Думаю, что после половины петабайта насыщение произойдет и больше не нужно будет, поглядим. Начну еще больше внимания уделять отказоустойчивости. Сейчас только три копии в разных концах планеты. Важно все это дело использовать только для себя и не шэрить, тем более с доходом. Просто на всякий случай.


        1. bano-notit
          13.05.2017 21:41

          Проблема в том, что config.json пока что только один… А в нём, как я понял из того, что видел в readme, не поддерживается несколько файловых систем за раз. Поэтому придётся писать какой-то свой скрипт, который будет в несколько команд включать сначала marcfs, а потом их объединять с помощью того же mhddfs.


          1. Self_Perfection
            13.05.2017 21:57

            Правильнее: монтировать разные инстансы выставив различные переменные окружения $HOME, чтобы config.json читался из разных путей.

            Ну да, нужно будет автоматизировать монтирование всего добра в нужном порядке. Но и без того автор предлагал бутерброд из MARC-FS + encfs, добавляется только один слой.

            Автоматизировать монтирование в нужном порядке в современных реалиях видимо правильнее через systemd. А умеючи и удобнее.


            1. bano-notit
              13.05.2017 22:03

              Я думаю вы сами согласитесь, что танцы с бубном переменными $HOME — не лучшая идея.


              бутерброд из MARC-FS + encfs, добавляется только один слой.

              Ну тут палка о двух концах. Да, если мне нужно будет 2 облачка в одно сливать, то это красиво, но если я порядочный гражданин, который имеет только 1 акк у майла? Мне кажется, что лучше уж пусть закононепослушные, типа меня, будут сами объединять несколько marcfs с помощью того же mhddfs, чем заставлять человека писать ещё один велосипед.


              А вот сделать в marcfs возможность из одного конфига делать много точек монтирования — будет очень полезной.


              1. Kanedias
                13.05.2017 22:11

                Я, скорее, добавлю поддержку $XDG_CONFIG_DIR, так что не придётся менять целиком $HOME. Или того лучше, сделаю опцию --config=/path/to/conf


              1. Self_Perfection
                13.05.2017 22:14

                Я думаю вы сами согласитесь, что танцы с бубном переменными $HOME — не лучшая идея.

                Да, но это лучше, чем последовательно скриптом запускать разные инстансы монтирования, перед каждым очередным запуском вписывая в config.json разные значения.

                Мне кажется, что лучше уж пусть закононепослушные, типа меня, будут сами объединять несколько marcfs с помощью того же mhddfs, чем заставлять человека писать ещё один велосипед.

                Если под человеком, которому писать велосипед, подразумевался Kanedias, то это полностью совпадает с моим отношением к ситуации.

                А вот сделать в marcfs возможность из одного конфига делать много точек монтирования — будет очень полезной.

                Я больше голосую за возможность указать путь к конфигу параметром командной строки.


                1. bano-notit
                  13.05.2017 22:33

                  Да, человек именно Kanedias...


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


  1. bano-notit
    13.05.2017 14:11

    А у меня вопрос есть, тупой скорее всего, но всё же. В Readme сказано, что нужно сначала отмонтировать прошлую encfs, из этого я делаю вывод, что работать с несколькими примонтированными системами я не могу. Это я делаю правильный вывод или нет?


    1. Kanedias
      13.05.2017 14:14

      Почему, можете примонтировать, скажем, одну фс в ~/remote-1, encfs для него в ~/remote-decrypted-1, вторую фс в ~/remote-2, encfs в ~/remote-decrypted-2. В README имелось в виду, что нужно отмонтировать encfs, которая смотрит на ту фс, которую вы хотите отключить.


      1. bano-notit
        13.05.2017 14:17

        Окей, тогда понятно. Просто подумал, что в реализации что-то не так у Вас)
        Тогда, возвращаясь к балансировке, можно установить балансировщик на локальной машине, который будет балансировать между 2 marcfs.


  1. NoRegrets
    13.05.2017 23:20

    Когда я в одном предложении встречаю слова «облако» и EncFS, то сразу вспоминаю о проведенном аудите encfs https://defuse.ca/audits/encfs.htm, пункт 2.2… В двух словах, если злоумышленник может получить несколько версий зашифрованных файлов, задача взлома значительно облегчается. Что, на мой взгляд, делает encfs полностью бесполезной для использования в облаке.


    1. Kanedias
      14.05.2017 00:30

      Можете использовать ecryptfs. Упоминание в статью добавил.


  1. Arqwer
    14.05.2017 00:19

    А почему бы просто не смонтировать облако mail.ru в davFS, и EncFS создать внутри точки монтирования davFS? Mail.ru не поддерживает webdav? Или encFS так просто внутри davFS не работает?


    1. Kanedias
      14.05.2017 00:20

      Mail.ru не поддерживает webdav?

      Нет, к сожалению. Анонс был, но… на этом всё.


      Или encFS так просто внутри davFS не работает?

      Тоже легко может быть. Как и говорил, encFS требует поддержки read/write блоками по файлу, что не в каждой ФС есть.


  1. ivan386
    14.05.2017 09:37

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

    Хеш считается уже в облаке или на локальном клиенте?


    Если на клиенте то нет смысла передавать в облако файл который там уже есть. А в таком случае можно "загрузить" в облако файл от которого у тебя только хеш и размер и забрать его оттуда.


    1. bano-notit
      14.05.2017 10:11

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


      1. ivan386
        14.05.2017 12:05

        Копирование это не кража. Но на клиенте же выгодней. Кроме хеша и размера ничего передавать тогда не надо.


        1. bano-notit
          14.05.2017 12:17

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


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


          Короче, если хеш вычисляется на клиенте, то это потенциальная дыра в безопасности.


          1. gvitalik
            14.05.2017 21:30

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


            1. bano-notit
              14.05.2017 21:34

              Окей, тогда вопрос, вот я посчитал хеш, отправил, и оказался уже такой файл в облаке. Что сервер будет делать? Файл то изначально не мой… Заставлять качать файл и потом разрешать мне доступ на тот, который уже есть?


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


              1. gvitalik
                14.05.2017 23:38

                Как решение вижу, такой вариант, если файл больше размера 2-х блоков, то разбивать файл на блоки, для каждого блока считать хэш сумму и пересылать этот набор хэш сумм. Если размер файла меньше, то пересылать файл целиком и все расчеты делать на сервере. При таком подходе пересылать придется значительно меньше, а угадать хеш каждого блока в файле и при этом угадать, существование файла, становится труднее. Дополнительно можно использовать автобан, на случай, когда после нескольких попыток клиент прислал, только хэш суммы, но не разу по запросу сервера не предоставил содержимое файла.

                Но в целом, я согласен, что легче все манипуляции сделать на стороне сервера.


    1. Kanedias
      14.05.2017 17:01

      Хэш считается на облаке, после заливки.


  1. justhabrauser
    14.05.2017 10:55

    Отличная работа, коллега!
    Пара камментов:
    * не указана версия GPL (а еще лучше — положить в гитхаб текст лицензии — это как бы «прилично»)
    * бы еще в README.md добавить requirements — что нужно для сборки; понятно, что по сообщениям cmake можно сделать некие выводы — но довольно сложно (например libcurl-devel, fuse-devel, jsoncpp-devel, jemalloc-devel для RH-based)
    * еще можно напилить примочку для mc — там не совсем fuse, поэтому оверхед должен быть меньше.
    * ну и зарелизить, что ли :-)


    1. Kanedias
      14.05.2017 17:03

      Самое важное-то и забыл! Спасибо! Лицензия GPLv3.


      бы еще в README.md добавить requirements — что нужно для сборки

      Я все комментарии с этой темы к себе понемногу в TODO переношу, как вынырну в очередной раз из работы и релизов, сделаю вики. Пока что можно посмотреть зависимости для наиболее популярных дистрибутивов в gitlab-CI скрипте


  1. grumbler66rus
    16.05.2017 17:51

    У меня есть задача хранить в облаке резервные копии. Причём шифрование не является необходимостью, более того, лучше обойтись без него.
    Пробовал Яндекс.Диск через davfs, но кеширование, которое ещё и оставляет недокачанные куски, убивает всю вкусность WEBDAV.
    Насколько просто исключить шифрование из вашего кода?


    1. Kanedias
      16.05.2017 18:43

      Шифрования вообще нет в коде, оно идёт поверх, с помощью EncFS/cryptfs. См. README в корне проекта.


      Но у меня кэшированные куски тоже могут остаться, если будете пользоваться -o cachedir.
      В общем, попробуйте, если вас устроит, сразу поймёте.