From fc697302d0491e211815f18b827321607b4dba12 Mon Sep 17 00:00:00 2001 From: Benjamin Philip Date: Thu, 2 Feb 2023 04:43:33 +0530 Subject: [PATCH] Add trusted_mirror_url config (#956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eric Meadows-Jönsson --- lib/hex/repo.ex | 59 ++++++++++++++++-------------- lib/hex/state.ex | 29 ++++++--------- lib/mix/tasks/hex.config.ex | 18 ++++----- lib/mix/tasks/hex.repo.ex | 7 +++- test/hex/repo_test.exs | 11 +++--- test/mix/tasks/hex.config_test.exs | 1 + 6 files changed, 65 insertions(+), 60 deletions(-) diff --git a/lib/hex/repo.ex b/lib/hex/repo.ex index 8838d7736..3dc480621 100644 --- a/lib/hex/repo.ex +++ b/lib/hex/repo.ex @@ -22,14 +22,19 @@ defmodule Hex.Repo do repos = Hex.State.fetch!(:repos) case Map.fetch(repos, repo) do - {:ok, config} -> + {:ok, config} when repo == "hexpm" -> + trusted_mirror_url = Hex.State.fetch!(:trusted_mirror_url) mirror_url = Hex.State.fetch!(:mirror_url) - if repo == "hexpm" and mirror_url do - {:ok, Map.put(config, :url, mirror_url)} - else - {:ok, config} - end + config = + config + |> Map.put(:url, trusted_mirror_url || mirror_url || config.url) + |> Map.put(:trusted, trusted_mirror_url != nil or mirror_url == nil) + + {:ok, config} + + {:ok, config} -> + {:ok, config} :error -> fetch_organization_fallback(repo, repos) @@ -55,13 +60,15 @@ defmodule Hex.Repo do |> Map.put(:url, url) |> Map.put(:public_key, public_key) |> Map.put(:auth_key, auth_key) + |> Map.put(:trusted, auth_key) end def default_hexpm_repo(auth_key \\ Hex.State.fetch!(:repos_key)) do %{ url: @hexpm_url, public_key: @hexpm_public_key, - auth_key: auth_key + auth_key: auth_key, + trusted: true } end @@ -171,6 +178,11 @@ defmodule Hex.Repo do HTTP.request(:get, tarball_url(repo, package, version), headers, nil) end + def get_public_key(repo) do + headers = auth_headers(repo) + HTTP.request(:get, public_key_url(repo), headers, nil) + end + def verify(body, repo) do public_key = get_repo(repo).public_key @@ -210,17 +222,23 @@ defmodule Hex.Repo do config.url <> "/tarballs/#{URI.encode(package)}-#{URI.encode(version)}.tar" end + defp public_key_url(repo), do: repo.url <> "/public_key" + defp etag_headers(nil), do: %{} defp etag_headers(etag), do: %{~c"if-none-match" => String.to_charlist(etag)} - defp auth_headers(repo) do - repo = get_repo(repo) + defp auth_headers(repo) when is_binary(repo) or repo == nil do + repo + |> get_repo() + |> auth_headers() + end + + defp auth_headers(%{trusted: true, auth_key: key}) when is_binary(key) do + %{~c"authorization" => String.to_charlist(key)} + end - if key = repo.auth_key do - %{~c"authorization" => String.to_charlist(key)} - else - %{} - end + defp auth_headers(%{trusted: _, auth_key: _}) do + %{} end defp parse_csv(body) do @@ -323,17 +341,4 @@ defmodule Hex.Repo do defp repo_name(name) do name |> split_repo_name() |> List.last() end - - def get_public_key(repo_url, auth_key) do - auth_headers = - if auth_key do - %{~c"authorization" => String.to_charlist(auth_key)} - else - %{} - end - - HTTP.request(:get, public_key_url(repo_url), auth_headers, nil) - end - - defp public_key_url(repo_url), do: repo_url <> "/public_key" end diff --git a/lib/hex/state.ex b/lib/hex/state.ex index b06adc2ab..fece12b81 100644 --- a/lib/hex/state.ex +++ b/lib/hex/state.ex @@ -82,6 +82,11 @@ defmodule Hex.State do config: [:mirror_url], fun: {__MODULE__, :trim_slash} }, + trusted_mirror_url: %{ + env: ["HEX_TRUSTED_MIRROR_URL", "HEX_TRUSTED_MIRROR"], + config: [:trusted_mirror_url], + fun: {__MODULE__, :trim_slash} + }, offline: %{ env: ["HEX_OFFLINE"], config: [:offline], @@ -224,9 +229,10 @@ defmodule Hex.State do end defp load_config_value(global_config, project_config, spec) do + env = System.get_env() + result = - load_env(spec[:env]) || - load_env_path_join(spec[:env_path_join]) || + load_env(spec[:env], env) || load_project_config(project_config, spec[:config]) || load_global_config(global_config, spec[:config]) @@ -271,22 +277,11 @@ defmodule Hex.State do |> Path.join("hex.config") end - defp load_env(keys) do + defp load_env(keys, env) do Enum.find_value(keys || [], fn key -> - if value = System.get_env(key) do - {{:env, key}, value} - else - nil - end - end) - end - - defp load_env_path_join(keys) do - Enum.find_value(keys || [], fn {key, prefix} -> - if value = System.get_env(key) do - {{:env_path_join, {key, prefix}}, Path.join(value, prefix)} - else - nil + case Map.fetch(env, key) do + {:ok, value} -> {{:env, key}, value} + :error -> nil end end) end diff --git a/lib/mix/tasks/hex.config.ex b/lib/mix/tasks/hex.config.ex index 63088f836..55e12778c 100644 --- a/lib/mix/tasks/hex.config.ex +++ b/lib/mix/tasks/hex.config.ex @@ -40,23 +40,26 @@ defmodule Mix.Tasks.Hex.Config do origin. Can be overridden by setting the environment variable `HEX_NO_VERIFY_REPO_ORIGIN` (Default: `false`) * `http_proxy` - HTTP proxy server. Can be overridden by setting the - environment variable `HTTP_PROXY` (Default: `nil`) + environment variable `HTTP_PROXY` * `https_proxy` - HTTPS proxy server. Can be overridden by setting the - environment variable `HTTPS_PROXY` (Default: `nil`) + environment variable `HTTPS_PROXY` * `no_proxy` - A comma separated list of hostnames that will not be proxied, asterisks can be used as wildcards. Can be overridden by setting the - environment variable `no_proxy` or `NO_PROXY` (Default: `nil`) + environment variable `no_proxy` or `NO_PROXY` * `http_concurrency` - Limits the number of concurrent HTTP requests in flight. Can be overridden by setting the environment variable `HEX_HTTP_CONCURRENCY` (Default: `8`) * `http_timeout` - Sets the timeout for HTTP requests in seconds. Can be overridden by setting the environment variable `HEX_HTTP_TIMEOUT` - (Default: `nil`) * `mirror_url` - Hex mirror URL. Can be overridden by setting the - environment variable `HEX_MIRROR` (Default: `nil`) + environment variable `HEX_TRUSTED_MIRROR` + * `trusted_mirror_url` - Hex mirror URL. Unlike `mirror_url`, this mirror is + "trusted", secrets such as authentication information will be sent to the + mirror. Can be overridden by setting the environment variable + `HEX_TRUSTED_MIRROR` * `cacerts_path` - Path to the CA certificate store PEM file. If not set, a CA bundle that ships with Hex is used. Can be overridden by setting the - environment variable `HEX_CACERTS_PATH`. (Default: `nil`) + environment variable `HEX_CACERTS_PATH` * `no_short_urls` - If set to true Hex will not shorten any links. Can be overridden by setting the environment variable `HEX_NO_SHORT_URLS` (Default: `false`) @@ -191,9 +194,6 @@ defmodule Mix.Tasks.Hex.Config do {:ok, {{:project_config, _key}, value}} -> print_value(key, value, verbose, "(using `mix.exs`)") - {:ok, {{:env_path_join, {env_var, _prefix}}, value}} -> - print_value(key, value, verbose, "(using `#{env_var}`)") - {:ok, {kind, value}} when kind in [:default, :computed] -> print_value(key, value, verbose, "(default)") diff --git a/lib/mix/tasks/hex.repo.ex b/lib/mix/tasks/hex.repo.ex index 848edc1ec..c6f9d9c15 100644 --- a/lib/mix/tasks/hex.repo.ex +++ b/lib/mix/tasks/hex.repo.ex @@ -128,7 +128,8 @@ defmodule Mix.Tasks.Hex.Repo do url: url, public_key: nil, fetch_public_key: nil, - auth_key: nil + auth_key: nil, + trusted: true } |> Map.merge(Map.new(opts)) |> Map.put(:public_key, public_key) @@ -214,7 +215,9 @@ defmodule Mix.Tasks.Hex.Repo do defp fetch_public_key(nil, _, _), do: nil defp fetch_public_key(fingerprint, repo_url, auth_key) do - case Hex.Repo.get_public_key(repo_url, auth_key) do + repo_config = %{url: repo_url, auth_key: auth_key, trusted: true} + + case Hex.Repo.get_public_key(repo_config) do {:ok, {200, key, _}} -> if show_public_key(key) == fingerprint do key diff --git a/test/hex/repo_test.exs b/test/hex/repo_test.exs index ffa907589..5db31de87 100644 --- a/test/hex/repo_test.exs +++ b/test/hex/repo_test.exs @@ -55,19 +55,20 @@ defmodule Hex.RepoTest do Bypass.expect(bypass, fn %Plug.Conn{request_path: path} = conn -> case path do "/public_key" -> + assert Plug.Conn.get_req_header(conn, "authorization") == ["key"] Plug.Conn.resp(conn, 200, hexpm.public_key) "/not_found/public_key" -> + assert Plug.Conn.get_req_header(conn, "authorization") == [] Plug.Conn.resp(conn, 404, "not found") end end) - assert {:ok, {200, public_key, _}} = - Hex.Repo.get_public_key("http://localhost:#{bypass.port}", nil) - + config = %{url: "http://localhost:#{bypass.port}", auth_key: "key", trusted: true} + assert {:ok, {200, public_key, _}} = Hex.Repo.get_public_key(config) assert public_key == hexpm.public_key - assert {:ok, {404, "not found", _}} = - Hex.Repo.get_public_key("http://localhost:#{bypass.port}/not_found", nil) + config = %{url: "http://localhost:#{bypass.port}/not_found", auth_key: "key", trusted: false} + assert {:ok, {404, "not found", _}} = Hex.Repo.get_public_key(config) end end diff --git a/test/mix/tasks/hex.config_test.exs b/test/mix/tasks/hex.config_test.exs index f5ed3ec72..f74e9e197 100644 --- a/test/mix/tasks/hex.config_test.exs +++ b/test/mix/tasks/hex.config_test.exs @@ -22,6 +22,7 @@ defmodule Mix.Tasks.Hex.ConfigTest do assert_received {:mix_shell, :info, ["http_concurrency: 8 (default)"]} assert_received {:mix_shell, :info, ["http_timeout: nil (default)"]} assert_received {:mix_shell, :info, ["mirror_url: nil (default)"]} + assert_received {:mix_shell, :info, ["trusted_mirror_url: nil (default)"]} assert_received {:mix_shell, :info, ["config_home:" <> _]} assert_received {:mix_shell, :info, ["no_short_urls: false (default)"]} end)