Информация о проекте  Документация  Исходный код проекта  Докер регистри  Цель статьи - поделиться опытом, болью и поныть немного о проблемах. Никоим образом не ставилось целью показать, что именно такой путь правильный.  Задача - создать help раздел на базе движка wikijs. Версия wikijs, которую брали за основу – 2.5.268 Крайняя версия, которую ставили (на момент написания статьи) – 2.5.274 исходный код править нельзя. Во первых, у нас в команде нет фронтендера, во вторых, непонятно как это потом поддерживать, когда будут выходить новые версии wikijs с новыми фичами   должны быть устранены критические уязвимости (CVE) на внешнем фронте должна быть закрыта админка на сборочных раннерах нет интернета, ну почти нет - некоторые ресурсы открыты через прокси по согласованию с ИБ Основной упор был нацелен на устранение уязвимостей, а там их оказалось не мало.  Сначала было желание оповестить разработчика обо всех имеющихся критических уязвимостях, но почитав Security Policy (https://github.com/requarks/wiki/security/policy) и оценив скорость реакции на проблемы пользователей в Discussions, пришло понимание, что это плохой путь. Многие проблемы, озвученные в Discussions не решаются годами, хотя сам проект в целом активно развивается. В Security Policy они хотят, чтобы по мимо известных CVE им присылали и примеры эксплоитов -:). Итак, было принято решение устранять уязвимости самим. Для поиска существующих уязвимостей, использовали отчет Xray от JFrog, интегрированного с нашим docker registry.   Для начала, надо понимать, что wikijs это гигантский комбайн с кучей всего, но весь функционал нам был не нужен, по этому, 1-й этап устранения уязвимостей – выкидывание не нужного. Каким образом удаляем не нужное – модифицируем package.json и пересобираем контейнер с приложением. sqlite3 – не нужно от слова совсем passport-microsoft – требуется для интеграции с azure (нам не нужно) mssql – в топку passport-azure-ad – azure нами не используется, удаляем Следующий этап – апгрейд версий модулей, их зависимостей, а так же транзитивных зависимостей. Тут было много экспериментов. Если посмотрите файлик yarn.lock в исходном коде проекта – вам станет плохо. Там огромное количество транзитивных зависимостей. Чтобы было понимание о масштабах бедствия – этот текстовый файл имеет размер 6Mb! Вторая большая боль – некоторые npm пакеты, используемые в данном проекте – давно не поддерживаются и имеют крайние обновления 3 – 5 лет назад, например: apollo-fetch - https://www.npmjs.com/package/apollo-fetch А некоторые npm пакет и того пуще – deprecated, например graphql-tools версии 7.0.0 - https://www.npmjs.com/package/graphql-tools/v/7.0.0 highlight.js – заменяем на версию 10.4.1 highlight.js присутствует также в зависимостях модуля diff2html. Повышаем версию diff2html до 3.4.14 i18next - повышаем версию до 19.8.5  js-yaml – повышаем версию до 3.14.1, также, это зависимость модуля i18next-node-fs-backend, разрешаем через resolutions: "i18next-node-fs-backend/js-yaml": "3.14.1" sqlite-libs – непонятно из каких зависимостях они берутся и в каких слоях, но для верности удаляем  целиком модуль sqlite3, так как нам он не нужен. clean-css  - повышаем до версии 5.2.2.  css-what – транзитивная зависимость модуля html-webpack-plugin – повышаем до версии 5.5.0 (css-what  -> css-select -> renderkid -> pretty-error -> html-webpack-plugin) xmldom – зависимость модуля passport-saml, повышаем версию последнего до 3.1.0 lodash – зависимость модуля passport-azure-ad, удаляем последний целиком node-fetch – также одна из зависимостей passport-azure-ad node-forge – транзитивная зависимость passport-saml, решаем вопрос через resolutions: "passport-saml/**/node-forge": "^1.0.0" nanoid – повышаем до версии 3.1.31 ansi-regex 5.0.0 & 3.0.0 – залетают как зависимости пакетного менеджера npm вместе с образом nodejs 16.13.2. Поскольку, npm не учавствует в runtime на это можно закрыть глаза. Еще один вариант – сделать собственную сборку образа nodejs, в которую не ставить npm.  ansi-regex – транзитивная зависимость eslint & cypress, решаем через механизм resolutions: "eslint/**/ansi-regex": "^5.0.1", "cypress/**/ansi-regex": "^5.0.1" underscore – зависимость пакетов express-brute & passport-cas, решаем вопрос через resolutions: "passport-cas/underscore": "^1.8.3", "express-brute/underscore": "^1.8.3" node-uuid – зависимость модуля passport-cas, разрешаем через resolutions: "passport-cas/node-uuid": "1.4.8" markdown-it – повышаем версию до 12.3.2 passport-oauth2 – зависимость модуля passport-microsoft, последний не обновляется уже 2 года и имеет уже крайнюю версию – удаляем целиком из package.json (нам не требуется) ansi-regex 2.1.1 – транзитивная зависимость mssql (ansi-regex -> strip-ansi -> gauge -> npmlog -> prebuild-install -> keytar -> @azure/identity -> tedious -> mssql), удаляем целиком из package.json  Что не удалось Дополнительные, транзитивные зависимости node-fetch -> apollo-fetch & cross-fetch -> graphql-tools. Модули apollo-fetch & cross-fetch давно не обновлялись (5 лет назад последний раз и имеют крайнюю версию). Попытка изменить версию node-fetch для apollo-fetch, приводит к ошибке. Модули ansi-regex версий 5.0.0 & 3.0.0 - залетают как зависимости пакетного менеджера npm вместе с образом nodejs 16.13.2. Поскольку, npm не участвует в runtime, на это можно закрыть глаза. Еще один вариант – сделать собственную сборку образа nodejs, в которую не ставить npm. В модулях самого приложения, ansi-regex требуемой версии: Это ошибки сборки некоторых опциональных зависимостей и это можно игнорировать. Интерпретатор python не установлен в контейнере намеренно, о чем указывает вышеописанная ошибка: "gyp ERR! find Python". В противном случае, будет осуществлена попытка сборки опциональных зависимостей из исходников, а это приведет к ошибке в пайплайне, так как будет попытка скачать исходники с разных ресурсов в интернете, а интернета на раннерах нет. diff2html 3.4.14 i18next 19.8.5 js-yaml 3.14.1 sqlite3 5.0.2 chalk 4.1.2 passport-microsoft 0.1.0 mssql 6.2.3 - remove passport-saml 3.1.0 highlight.js 10.4.1 passport-azure-ad 4.3.1 html-webpack-plugin 5.5.0 cheerio 1.0.0-rc.10 nanoid 3.1.31 markdown-it 12.3.2 clean-css 5.2.2 resolutions:     "i18next-node-fs-backend/js-yaml": "3.14.1"     "passport-cas/node-uuid": "1.4.8",     "passport-cas/underscore": "^1.8.3",     "express-brute/underscore": "^1.8.3",     "eslint/**/ansi-regex": "^5.0.1",     "cypress/**/ansi-regex": "^5.0.1     "passport-saml/**/node-forge": "^1.0.0" На какие поля еще стоит обратить внимание в package.json "version": "2.5.274", "releaseDate"  "2022-01-29T18:45:51.000Z", "dev" false, Версию релиза и дату необходимо будет поддерживать самим. Если в поле dev будет true - увидите сверху красную полосу в UI, что приложение находится в "Development mode". Апгрейдить версию chalk до мажорной 5 и выше Chalk 5 has changed to ESM. They provide a link to better understand what that means: Pure ESM. From chalk README: IMPORTANT: Chalk 5 is ESM. If you want to use Chalk with TypeScript or a build tool, you will probably want to use Chalk 4 for now.    Решение - понизить версию chalk до крайней мажорной ветки 4 - 4.1.2    Повышать версию js-yaml до 4-й мажорной и выше Решение без правки исходного кода -   не повышать версию js-yaml до 4-й мажорной ветки. Крайняя версия в ветке 3 - 3.14.1  Решение с правкой исходных кодов - менять в исходниках appconfig = yaml.safeLoad на appconfig = yaml.Load     Повышать версию graphql-rate-limit-directive выше 1.3.0 Решение - понизить версию graphql-rate-limit-directive до 1.2.1 Повышать версию graphql-tools выше 7.0.0 Решение - не повышать версию graphql-tools выше 7.0.0 Апгрейдить версию apollo-server-express Описание проблемы: This is a known bug with an open issue and a merged PR to fix it. For now, you can downgrade to apollo-server-express@^2 Пруфлинк Решение - понижение версии apollo-server-express до 2.25.2 Одновременно ставить несколько модулей graphql разных версий, например через dependency & resolutions Решение - в package.json в dependency & resolutions должны быть одинаковые версии модуля graphql Апгрейдить модуль graphql до 16-й мажорной ветки Решение - понижение версии модуля graphql до 15.3.0 клонирование проекта с исходным кодом и переход на ветку с номером релиза  скачивание архива со сбилдженным приложением. Из данного архива берутся assets & server/views.  формирование .npmrc & .yarnrc для подключения remote-npm  скачивание пакетов локализации из проекта wiki-localization.git Сборка  осуществляется на раннерах гитлаба с подключением удаленного репозитория npm (remote-npm). Этап build (yarn build) исключен из докерфайла, так как требует сборки некоторых модулей из исходников и соответственно, для этого нужен интернет. Артифакты этапа build - assets, копируются из архива, официально сбилдженного      приложения из github. В  контейнер с приложением, дополнительно копируются файлы локализации из      проекта wiki-localization.git (https://github.com/Requarks/wiki-localization) Файл конфига немного модифицировали (config.yml)  В качестве базового брали образ nodejs 16.13.2 alpine 3.15. Стейдж build Установка yarn Копирование модифицированного package.json из данного проекта в рабочую директорию Копирование .npmrc & .yarnrc в рабочую директорию Сборка модулей и их зависимостей из списка dependencies Стейдж release Установка всех необходимых пакетов Создание директорий Копирование собранных модулей из стейджа build Копирование папки servers из проекта с исходным кодом Копирование assets & server/views из архива с официальной сборкой проекта Копирование package.json Копирование конфигурационного файла приложения config.yml Копирование лицензионного соглашение (из офиц релиза) Копирование файлов локализации  На этом процесс сборки контейнера с приложением закончен. Для того чтобы запретить любую возможность входа в административную панель приложения снаружи, было принято решение поднять 2 фронта. Примерно это выглядит так: внутренний фронт с админкой - крутится в корпоративной сети (одна реплика) внешний фронт без админки - торчит наружу (2 реплики) оба фронта смотрят в один бэк - в одну БД (HA Cluster Patroni) Как закрывали админку на внешнем фронте – закрытием локаций с использованием nginx на ingress контроллере кубера, где деплоили приложение. Пример: Вторая часть задачи по закрытию возможности авторизации - выпиливание кнопки авторизации с панели управления, удалением куска js кода с использованием subs_filter nginx на том же ingress контроллере. Пример: Что еще делали – онлайн замену реальных пользователей, на обезличенного Administrator. Эта инфа отображается на каждой страничках wiki – Дата изменения и username. Тут так же - subs_filter от nginx.  Замена favicon, так же, через грязный хак. Ну нельзя штатными средствами это сделать. Наши апсеки узрели дырку в авторизации на локейшене /graphl. Целиком данный локейш выпилить нельзя - на нем реализован поиск контента. Пришлось применить lua скрипт по ограничению методов с просмотром тела запросов. Спасибо коллегам за помощь!  Тут было несколько проблем LDAP URL - Если указать ldaps://domain.ru:636, то возникала ошибка невозможности отрезолвить данный домен, при этом, сеть внутри контейнера работала нормально. Проблема в каком-то js модуле или методе. Ошибка воспроизводится 100% только в docker контейнере. Пришлось указывать тут IP PDC. Проблема в понимании секции "Разрешить самостоятельную регистрацию/self-registration (https://docs.requarks.io/auth/ldap). Если не включать данную опцию и не прописывать «Назначить группу», то при авторизации нового пользователя получим «тыкву». Дело в том, что для полноценной работы, нужны локальные пользователи и локальная группа, которой будут даны определенные права. Если включить данную опцию, то при успешной LDAP авторизации, создадутся новые локальные пользователи, с правами группы, которая указана в данной настройке. Пользователи создадутся с именем = значению поля в AD, указанным в "Display Name Field Mapping". Либо необходимо заранее создать локальных пользователей, назначить им права, при этом имена пользователей должны совпадать со значением поля в AD указанного в "Display Name Field Mapping" Приложение может быть размещено только на выделенном домене, например wiki.domain.com и не может быть размещено в subpath, т.е. www.domain.com/wiki Пруф https://github.com/Requarks/wiki/discussions/4656 Приложение пытается лезть в интернет, для скачки файлов локализации, по этому, если интернета нет на сервере, скачиваем пакеты локализации, подкладываем приложению (описано в dockerfile), а в конфиге config.yml прописываем offline: true Невозможно штатными средствами заменить favicon (применяли вышеуказанный хак). Обсуждение https://github.com/Requarks/wiki/discussions/3371, https://js.wiki/feedback/p/favicon Нельзя удалить/переименовать директории для медиафайлов (прям рука-лицо) https://feedback.js.wiki/wiki/p/delete-folders-in-the-image-file-manager Нельзя глобально изменить дефолтный timezone (тут тоже рука-лицо). Пруф https://feedback.js.wiki/wiki/p/select-date-format-and-time-zone-for-the-entire-site. Пока только такой грязный хак: ALTER TABLE users ALTER COLUMN timezone SET DEFAULT 'Europe/Moscow '; По умолчанию пользователь получает timezone GMT-3 или что-то в этом духе. Настройка через текстовый конфиг невозможна. Например, настройка интеграции с LDAP, настройка локализации, создание групп, пользователей и т.д. Настройка предполагается только через UI. Автоматизация? Не, не слышали. Ну либо применять вливание настроек через sql скрипт. Некоторые вещи, например таблица стилей css, попадает в локальный файловый кэш на момент старта приложения. Этот кэш обновляется при изменении и сохранении контента страниц. Если несколько инстансов приложения (например несколько реплик в кубернетес для HA), то могут возникать такие коллизии - при логине в админку попадаешь на одно приложение в рамках сессии и после обновления контента, локальный кэш обновляется только у одного приложения. Далее если перейти на данный ресурс в браузере и понажимать F5 для обновления страницы, попадая на второй инстанс приложения, можно увидеть различия в оформлении, если оно менялось. Механизма управления локальным кэшем нет. По этому, сделали авторестарт внешних фронтов по расписанию (ночью). Спрашивайте, может чего еще вспомню. А пока, пойду помою руки после nodejs.  Всем добра!  -:)