Допустим, наше Java приложение размещено на платформе "Heroku", и ему требуется подключиться к HTTP серверу, требующему предоставление Клиентского Сертификата.


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


  • Хранение паролей и других секретных данных в переменных окружения является распространённой практикой для приложений, размещённых в облаке.

Обычно приватные ключи содержались в защищённых паролем файлах — контейнерах ключей, таких как .p12 или jks на файловой системе.


Но проблема становится очевидна, как только становится необходимо разместить подобное приложение в облаке:


  • Даже защищённые паролем приватные ключи не допустимо размещать в репозитории вместе с исходным кодом


  • Тоже самое касается образов Docker и любых других подобных артефактов, доступных нескольким лицам


  • К счастью, это легко исправить в приложениях на Java!



Рассмотрим предполагаемый процесс с точки зрения теории безопасности:


  1. Офицер безопасности (о.б.) экспортирует приватный ключ как строку в кодировке Base64
  2. О.б. заходит в консоль управления облака (в нашем случае в Heroku Dashboard)
  3. О.б. импортирует строку в кодировке Base64 в переменную окружения
  4. Как часть автоматического процесса размещения приложения, эта же переменная окружения конвертируется в двоичные данные и записывается в физический файл
  5. Путь к этому файлу передаётся приложению на этапе запуска

  • Теперь, только круг лиц, имеющих доступ к консоли управления облака, получают доступ к приватному ключу.

Применим этот подход на практике:


  • Язык: Java
  • Платформа: Heroku
  • Система сборки: Gradle
  • Формат хранилища ключей: PKCS12

Экспорт файла .p12


Воспользуйтесь инструкцией, использующей средства, встроенные в Вашу операционную систему:



Импорт в Heroku


  • Зайдите в Heroku
  • Перейдите в секцию Settings Вашего приложения
  • Нажмите "Reveal the config vars"
  • Добавьте следующие переменные окружения:
    • keyStoreFileName — любое незанятое имя файла, например "private_key.p12"
    • keyStoreBase64 — вставьте сюда значение строки закодированной в формате Base64, экспортированной в предыдущем шаге "Экспорт файла .p12"
    • keyStorePassword — пароль, ранее использовавшийся с файлом .p12
    • keyStoreType — в нашем случае pkcs12
    • trustStoreTypejks

Запись файла .p12 используя Gradle


Добавьте нижеприведённое задание в Ваш файл guild.gradle и назначьте его исполнение перед задачей stage:


task initKeyStore() {
    doLast {
        println("Creating keystore file from environment variables.")
        String keyStoreFileName = System.getenv("keyStoreFileName")
        if (keyStoreFileName != null) {
            String keyStoreBase64 = System.getenv("keyStoreBase64")
            new File(keyStoreFileName).withOutputStream {
                it.write(Base64.decoder.decode(keyStoreBase64))
            }
        }
    }
}
stage.dependsOn(initKeyStore)

Передача реквизитов через "procfile" Heroku


В "procfile" в Heroku не поддерживаются многострочные команды.


Поэтому, мы рекомендуем создать отдельный shell скриптrunApp.sh.


runApp.sh:


java  -Dserver.port=$PORT ...
 -Djavax.net.ssl.keyStoreType=$keyStoreType  -Djavax.net.ssl.trustStoreType=$trustStoreType  -Djavax.net.ssl.keyStore=$keyStoreFileName  -Djavax.net.ssl.keyStorePassword=$keyStorePassword  $JAVA_OPTS ...

Не забудьте дать права на выполнение используя git и протолкнуть (push) подтверждение (commit):


git update-index --chmod=+x runApp.sh
git commit -m 'Добавление прав на выполнение runApp.sh'
git push origin master

Ограничения


В общей сложности Heroku позволяет хранить максимально 32 килобайта информации в переменных окружения.


Это накладывает некоторые ограничения, и вопрос их преодоления в настоящий момент прорабатывается в нашей организации.


Заключение


  • Теперь каждый раз, когда вы размещаете своё приложение в Heroku, Gradle автоматически создаёт хранилище ключей в виде файла P12 на основе переменной окружения.
  • Затем, используя procfile, платформа Heroku передаёт детали этого файла Вашему приложению.
  • Приватный ключ становится доступным приложению, и может быть использован для Клиентской аутентификации как обычный файл .p12 в Java
  • Никаких изменений исторической кодовой базы приложения не требуется для поддержки этого подхода.

Спасибо за внимание!