:ssl и :httpc в Erlang/Elixir
Источник
Эта статья — краткий (очень) конспект доклада про ssl с ElixirConfEU 2019 от Bram Verburg. Не столько для объяснения, сколько для копирования кода.
Для полноценного изучения вопроса крайне рекомендую посмотреть сам доклад.
Проблема
В том, что ssl в Erlang — скользкая тема (для тех, кто не шарит, т.е. для меня).
По-умолчанию :ssl
(и, соответственно, :httpc
) поддерживают tls (то есть не выдают ошибку при подключении к tls-хостам), но не обеспечивают безопасность соединения, так как не выполняют проверку аутентичности сервера. Опции по-умолчанию выглядят так: [ssl: [verify: :verify_none]]
.
Если вам (как мне) нужно ходить на сервер, аутентичность которого лучше бы подтверждать, опции по-умолчанию не подходят.
Решение
Для начала нужно влючить проверку подлинности сервера:
verify: :verify_peer
Затем указать файл с сертификатами опцией cacertfile
. В Ubuntu при установленном пакете openssl
сертификаты лежат в /etc/ssl/certs/ca-certificates.crt
. Получаем
cacertfile: '/etc/ssl/certs/ca-certificates.crt'
Далее для возможности работы с wildcard-сертификатами нужно указать функцию проверки доменного имени подходящую для https:
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
Если нужно обращаться к старому серверу, который по какой-то причине работает со старыми алгоритмами, нужно эти алгоритмы включить:
ciphers: :ssl.cipher_suites(:all, :"tlsv1.2")
В результате получаем следующую конфигурацию (без ciphers
):
[
verify: :verify_peer,
cacertfile: '/etc/ssl/certs/ca-certificates.crt',
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
И финальный код с :httpc
выглядит примерно вот так:
ssl_options = [
verify: :verify_peer,
cacertfile: '/etc/ssl/certs/ca-certificates.crt',
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
connection_options = [ssl: ssl_options]
url = 'https://www.google.com:443/'
:httpc.request(:get, {url, []}, connection_options, [])
Сохраняем конфигурацию
Часто так бывает, что из-за каких-то проблем нужно включить verify_none
. Например, небезопасная работа приложения принесёт меньше бед, чем неработоспособное приложение. В таком случае нужна возможность управлять подключением без внесения изменений в программу.
Я предпочитаю для такого делать отдельный модуль, который для ускорения работы сохраняет настройки в persistent_term:
defmodule MyApp.HTTPCSSLOptions do
def get do
get_stored() || get_from_env!()
end
defp get_stored do
:persistent_term.get(__MODULE__, nil)
end
defp get_from_env! do
options =
case verify_type() do
:verify_none -> verify_none_options()
:verify_peer -> verify_peer_options()
end
:persistent_term.put(__MODULE__, options)
options
end
defp verify_type do
case String.downcase(Application.get_env(:my_app, :ssl)[:verify]) do
"verify_none" -> :verify_none
"verify_peer" -> :verify_peer
_ -> :verify_peer
end
end
defp verify_none_options do
[verify: :verify_none]
end
defp verify_peer_options do
cacertfile = Application.get_env(:my_app, :ssl)[:casertfile]
[
verify: :verify_peer,
cacertfile: to_charlist(cacertfile),
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
end
end
И затем использовать этот модуль:
url = 'https://www.google.com:443/'
:httpc.request(:get, {url, []}, [ssl: MyApp.HTTPCSSLOptions.get()], [])