hexpm / hex

Package manager for the Erlang ecosystem.
https://hex.pm
961 stars 184 forks source link

cacerts_path may not be enough #1045

Closed lovett closed 1 month ago

lovett commented 1 month ago

Hello, I've run into a unusual situation when it comes to SSL certs.

I have an Erlang 27 installation that is unable to run mix deps.get because of a bogus expired certificate problem as discussed here. It looks like this:

mix deps.get --only prod
 (Mix) httpc request failed with: {:failed_connect, [{:to_address, {~c"builds.hex.pm", 443}}, {:inet, [:inet], {:tls_alert, {:certificate_expired, ~c"TLS client: In state wait_cert_cr at ssl_handshake.erl:2158 generated CLIENT ALERT: Fatal - Certificate Expired\n"}}}]}

I think there might be an underlying problem on the Erlang side which is an issue for a different repository. Here I wanted to ask about the difference between cacerts_path in hex, and cacertfile in httpc.

When cacerts_path is set, its contents are sent to :public_key.pem_decode/1. Could an underlying issue with that call explain why I get a failure from mix, but can get httpc to behave if I configure it directly: like this:

:httpc.request(:get, {"https://builds.hex.pm/", []}, [
  ssl: [
    cacertfile: "/opt/local/etc/openssl/cert.pem",
  ]
], [])

That works fine. I think what is happening is Erlang doesn't like my system's default cert chain for whatever reason, but the alternative that hex provides in the form of cacert_path is susceptible to the same breakage. Meanwhile, cacertfile works because it is presumably handled in a different way. But that option is not otherwise available through hex.

The alternate cert file I'm passing to httpc happens to be the same as the one shipped with Hex, apart from the timestamp. It also works if I pass in Hex's ca-bundle.crt.

Version details:

$ mix --version
Erlang/OTP 27 [erts-15.0] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]

Mix 1.17.0 (compiled with Erlang/OTP 27)

$ openssl version
OpenSSL 3.1.6 4 Jun 2024 (Library: OpenSSL 3.1.6 4 Jun 2024)
ericmj commented 1 month ago

Can you show the full output of the mix deps.get command. It's making a request to builds.hex.pm which suggests it's trying to install hex or rebar? It could also be the update checker failing but that shouldn't fully error like that.

If it's trying to install rebar or hex then the cacerts_path won't be used as it's Elixir doing that. Elixir will use the environment variable HEX_CACERTS_PATH though which you can set to the same value.

lovett commented 1 month ago

Thanks for the environment variable suggestion, I think that was it.

I was indeed getting stuck on rebar. And I had been favoring hex.config over the env approach. I'm not yet sure why, but it seems like the macports-based Erlang installation I started with was somehow not right (apart from the OS cert chain not working out-of-the-box). Setting HEX_CACERTS_PATH via environment variable did not work there, but it did work when I moved to an asdf-based Erlang installation. Not sure if it's an OpenSSL complication or not, but for this issue it's probably moot.

If it's helpful, the dynamics of the environment variable and the hex config weren't clear. They don't seem to be as equal as the documentation suggests, at least when it comes to rebar.

For completeness, the full output I was seeing previously is below. Probably nothing more to be done here.

Thanks again for the help.


Run 1: Erlang + OpenSSL 3.1.6 (from macports)

$ mix deps.get --only prod
** (Mix) httpc request failed with: {:failed_connect, [{:to_address, {~c"builds.hex.pm", 443}}, {:inet, [:inet], {:tls_alert, {:certificate_expired, ~c"TLS client: In state wait_cert_cr at ssl_handshake.erl:2158 generated CLIENT ALERT: Fatal - Certificate Expired\n"}}}]}

Could not install Hex because Mix could not download metadata at https://builds.hex.pm/installs/hex-1.x.csv.

Alternatively, you can compile and install Hex directly with this command:

    $ mix archive.install github hexpm/hex branch latest

Run 2: After installing hex from GitHub

$ mix deps.get --only prod
Resolving Hex dependencies...
Resolution completed in 0.647s
Unchanged:
...[package list omitted]...
** (Mix) httpc request failed with: {:failed_connect, [{:to_address, {~c"builds.hex.pm", 443}}, {:inet, [:inet], {:tls_alert, {:certificate_expired, ~c"TLS client: In state wait_cert_cr at ssl_handshake.erl:2158 generated CLIENT ALERT: Fatal - Certificate Expired\n"}}}]}

Could not install Rebar because Mix could not download metadata at https://builds.hex.pm/installs/rebar3-1.x.csv.

Run 3: With HEX_CACERTS_PATH via enironment

$ HEX_CACERTS_PATH=/opt/local/etc/openssl/cert.pem mix deps.get --only prod
...
Could not install Rebar...

Run 4: Erlang + OpenSSL 1.1 (from asdf)

sudo port install openssl11
KERL_CONFIGURE_OPTIONS="--without-javac --without-wx --without-odbc --with-ssl=/opt/local/libexec/openssl11/" asdf install erlang latest
asdf install elixir latest
mix deps.get --only prod

same as run 1

Run 5: Setting HEX_CACERTS_PATH via enironment

HEX_CACERTS_PATH=/opt/local/etc/openssl/cert.pem mix deps.get --only prod

Works

Run 6: Erlang + OpenSSL 3 (from asdf)

KERL_CONFIGURE_OPTIONS="--without-javac --without-wx --without-odbc" asdf install erlang latest
asdf install elixir latest
mix archive.install github hexpm/hex branch latest
HEX_CACERTS_PATH=/opt/local/etc/openssl/cert.pem mix deps.get --only prod

Works