Projector — это способ запускать IntelliJ IDEA на удалённом сервере. Недавно я писал об этом статью, но умолчал о важной для любого параноика вещи — шифровании данных на вебсокете.
Генерация и подкладывание ключей — довольно муторный кусок работы. Тут придётся познакомиться с особенностями Docker и криптографии в Java. К сожалению, убежать от этого никуда нельзя, потому что это Java, и ребята из JetBrains совершенно не виноваты.
В изначальной статье этот текст был закрыт спойлером, но потом я почувствовал, что читать такую стену текста невозможно и родил вот этот текст. Заранее извините. Открывая эту статью вы соглашаетесь с тем, что увиденное вам не понравится.
Генерация ключей
Вначале нам нужно сгенерировать набор ключей. Для этого нужно установить OpenJDK и использовать инструмент keytool.
Генерация ключей — задача, состоящая из кучи шагов. Для себя я написал скрипт, и призываю вас воспользоваться им.
mkdir ~/keystore
cd ~/keystore
curl https://raw.githubusercontent.com/projectile-ide/projectile-keymaker/master/projectile-keymaker --output ./keymaker
chmod 755 ./keymaker
./projectile-keymaker projector idea true IP 192.168.1.1 mypassword
Впрочем, ничего не мешает сделать это самостоятельно. Главное, чтобы на выходе получились два важных файла: ca.crt
и server.jks
.
ca.crt
— это наш Certificate Authority, которым подписаны серверные ключи. Нужно будет заставить браузер доверять ему.server.jks
— это сертификат конкретного сервера с запущенной IDEA.
Восход Солнца вручную
Настоящий параноик никогда не верит каким-то чужим скриптам в интернете, и всё проверяет самостоятельно. Давайте пробежимся по основным шагам.
Вначале нам нужно сгенерить наш собственный Certificate Authority (CA), который мы дальше подпихнём во все браузеры.
keytool -genkeypair -v \
-alias ca \
-dname "CN=myCA, OU=Development, O=myCA, L=SPB, S=SPB, C=RU" \
-keystore ca.jks \
-keypass:env PW \
-storepass:env PW \
-keyalg RSA \
-keysize 4096 \
-ext KeyUsage:critical="keyCertSign" \
-ext BasicConstraints:critical="ca:true" \
-validity 9999
Небольшое отступление про пароли
Обратите внимание на вот эту строчку: -keypass:env PW
. Это означает, что пароль мы вписываем не в консоли (иначе он затеряется в bash history), а берем из переменных окружения.
Можно написать export PW=mypassword
и этого достаточно, но тогда он тоже потеряется в bash history. Куда логичней положить его в файл.
Например, вот так можно сгенерить случайный пароль и положить его в файл:
export PW=`pwgen -Bs 10 1`
echo $PW > password
А вот так можно высосать его назад в переменную окружения:
export PW=`cat password`
Красиво, правда? Конечно, такой файл всё ещё можно украсть с жесткого диска сервера, но это совсем другая история. На протяжении этой статьи я выпью свои таблеточки и представлю, что хранить пароли в текстовом файле — это нормально. Тем более, что нам нужен не файл, а только содержимое переменной окружения PW
.
Возвращаемся назад к генерации ключей...
CA мы сгенерили, но в каком-то богомерзком формате JKS, который браузеры не поймут. Чтобы его можно было скормить браузеру, вначале нужно перегнать его в привычный crt:
keytool -export -v \
-alias ca \
-file ca.crt \
-keypass:env PW \
-storepass:env PW \
-keystore ca.jks \
-rfc
Настала пора сгенерировать ключ для нашего сервера (того, который будет показывать Идею):
keytool -genkeypair -v \
-alias server \
-dname "CN=myServer, OU=Development, O=myServer, L=SPB, S=SPB, C=RU" \
-keystore server.jks \
-keypass:env PW \
-storepass:env PW \
-keyalg RSA \
-keysize 2048 \
-validity 385
Сертификат есть, но пока не подписанный и поэтому в браузере работать не станет. Продолжая церемонию, создаём запрос на подпись:
keytool -certreq -v \
-alias server \
-keypass:env PW \
-storepass:env PW \
-keystore server.jks \
-file server.csr
Теперь сертификат сервера можно подписать тем CA, который мы создали в самом начале:
keytool -gencert -v \
-alias ca \
-keypass:env PW \
-storepass:env PW \
-keystore ca.jks \
-infile server.csr \
-outfile server.crt \
-ext KeyUsage:critical="digitalSignature,keyEncipherment" \
-ext EKU="serverAuth" \
-ext SAN="IP:192.168.1.1" \
-rfc
Заметьте, что SAN может быть в двух вариантах: либо "DNS:website.com", если у вас есть зарегистрированное доменное имя. Либо "IP:192.168.1.1", если домена нет. Если нет ни IP, ни домена, то остаётся только забиться в угол и плакать, без этой записи браузер вам не поверит.
Дальше, в обратную сторону, нужно заставить наш JKS доверять самоподписанному CA (иначе следующий шаг сломается):
keytool -import -v \
-alias ca \
-file ca.crt \
-keystore server.jks \
-storetype JKS \
-storepass:env PW << EOF
yes
EOF
Берем подписанный ранее сертификат и упаковываем внутрь JKS:
keytool -import -v \
-alias server \
-file server.crt \
-keystore server.jks \
-storetype JKS \
-storepass:env PW
Ну и наконец, результат своих мучений можно посмотреть прямо в консоли:
keytool -list -v \
-keystore server.jks \
-storepass:env PW
На мой скромный взгляд, мучения с набором этих кодов в консоли того не стоят. Лучше возьмите готовый скрипт и поправьте его как вам удобней.
Прописываем файлы конфигурации
Для хранения настроек нужно сделать файл /home/olegchir/keystore/ssl.properties
с таким содержимым:
STORE_TYPE=JKS
FILE_PATH=/tmp/server.jks
STORE_PASSWORD=mypassword
KEY_PASSWORD=mypassword
Пароль, очевидно, нужно указывать точно такой же, который вы использовали при создании кейстора.
В этой статье я пишу полный путь до моего домашнего каталога (/home/olegchir
) чтобы вам не казалось, что я где-то вас обманул и кусок текста пропустил. Критически относитесь к тому, что читаете, и вписывайте свои пути. Ну или можете поменять имя и фамилию на olegchir
, хотя мне будет немного неуютно.
Почему /tmp/server.jks
, а не /home/olegchir/keystore/server.jks
? Потому что мы в таком виде, во временную папку, будем монтировать его внутрь докерного образа. Сейчас всё увидите.
Теперь нам нужно сказать Projector, чтобы он начал использовать эти файлы. Для этого нужно устанавить специальные переменные окружения с негуманоидными названиями в параметрах docker run
, а файлы смонтировать внутрь образа.
Идём в скачанный заранее репозиторий, открываем файл run-container.sh
и ищем строчку:
docker run --rm -p 8080:8080 -p 8887:8887 -it "$containerName" bash -c "nginx && ./run.sh"
Туда нужно добавить две переменные окружения:
ORG_JETBRAINS_PROJECTOR_SERVER_SSL_PROPERTIES_PATH
указывает на файл с настройками;ORG_JETBRAINS_PROJECTOR_SERVER_HANDSHAKE_TOKEN
задает пароль, который нужно указывать в URL, чтобы успешно подключиться;- Оба файла (настройки и кейстор) монтируем в
/tmp
.
docker run --rm \
-v /home/olegchir/keystore/ssl.properties:/tmp/ssl.properties \
-v /home/olegchir/keystore/server.jks:/tmp/server.jks \
--env ORG_JETBRAINS_PROJECTOR_SERVER_SSL_PROPERTIES_PATH=/tmp/ssl.properties \
--env ORG_JETBRAINS_PROJECTOR_SERVER_HANDSHAKE_TOKEN=mypassword \
-p 8080:8080 -p 8887:8887 -it "$containerName" bash -c "nginx && ./run.sh"
Теперь контейнер можно запускать!
./run-container.sh
При запуске могут быть какие-то ошибки. Желательно прочитать лог и найти строчки типа:
[INFO] :: ProjectorServer :: WebSocket SSL is enabled: /tmp/ssl.properties
[INFO] :: ProjectorServer :: Server started
Установка сертификата в браузеры
Скопируйте файл ca.crt
на то устройство, откуда собираетесь соединяться. Дальше нужно открыть браузер и установить сертификат.
Установка сертификата на разных сочетаниях барузера и операционной системы может выглядеть по-разному. Я не могу в этой статье рассказать о всех возможных сочетаниях, но упомяну самые популярные.
Firefox:
- Настройки
- Вкладка Privacy & Security
- В самом низу настроек — кнопка View Certificates
- Вкладка Authorities
- Кнопка Import...
- Ищем сертификат в диалоговом окне выбора файлов
- Прощёлкиваем, все доступные галки.
- Заканчиваем импорт.
Chrome для Windows:
- Настройки
- Раздел Privacy and security
- Раздел Securty
- Кнопка Manage certificates
- Вкладка Trusted Root Certification Authorities
- Кнопка Import...
- Открывается мастер, выбираем файл, соглашаемся импортировать именно в Trusted Root Certification Authorities.
- После импорта обязательно закрываем Chrome. На Windows это может оказаться совсем непросто. Когда вы закрыли Chrome, откройте Диспетчер задач и посмотрите, не осталось ли процессов с именем Chrome. Все такие процессы нужно убить кнопкой Delete.
- После перезапуска Chrome всё должно работать.
Chrome для Linux:
- Настройки
- Раздел Privacy and security
- Раздел Securty
- Кнопка Manage certificates
- Вкладка Authorities
- Кнопка Import...
- Выбираем файл
ca.crt
, - Прощёлкиваем, все доступные галки.
- Заканчиваем импорт.
Chrome и Fully Kiosk Browser для Android:
Установка под Android может быть очень разной. На Huawei MediaPad M5 мне достаточно было щелкнуть пальцем на сертификат в файловом менеджере Solid Explorer и он установился почти как на компьютере.
На других устройствах можно попробовать вот такой путь:
- Settings
- Security & privacy
- More Settings
- Encription and credentials
- Install from storage
- Выбрать
ca.crt
- Заканчиваем импорт.
Важно понимать, что каждый производитель Android-устройства сам себе хозяин, и пункт "Install from storage" может оказаться в любом месте настроек, и называться любым именем. Придется повозиться.
Подключаемся из браузера
Для локальной машины: https://localhost:8080/projector/?wss&token=mypassword
Для облачного сервера: https://hostname:8080/projector/?wss&host=hostname&port=8887&token=mypassword
Поиск проблем
Если ничего не помогло, попробуйте открыть адрес https://hostname:8887
и посмотрите, что напишет браузер. Возможно, там будет кнопка типа "всё равно доверять этому серверу навсегда" или что-то в этом роде. Возможно, там будет говорящее сообщение об ошибке.
Выводы
Настройка безопасного соединения — это долго и неприятно. Нужно набирать в консоли много команд, которые невозможно запомнить наизусть, рыться внутри докерфайлов, пересылать файлы на мобильное устройство. Если ты ошибся где-то хоть в одной букве, то ничего не работает.
С другой стороны, один раз сделал — и живи спокойно.