Skip to content

Commit

Permalink
fix: Use CA certificates from conda-forge::ca-certificates (#3765)
Browse files Browse the repository at this point in the history
Signed-off-by: Julien Jerphanion <[email protected]>
  • Loading branch information
jjerphan authored Jan 24, 2025
1 parent 617be25 commit 7fa748f
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 39 deletions.
2 changes: 2 additions & 0 deletions libmamba/include/mamba/api/configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ namespace mamba

namespace detail
{
auto get_root_prefix() -> fs::u8path;

template <class T>
bool ConfigurableImpl<T>::cli_configured() const
{
Expand Down
79 changes: 43 additions & 36 deletions libmamba/src/api/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,22 +713,52 @@ namespace mamba
return { fs::weakly_canonical(std::move(prefix)) };
}

/**
* In mamba 1.0, only micromamba was using this location.
*/
auto default_root_prefix_v1() -> fs::u8path
auto get_default_root_prefix(fs::u8path& prefix) -> void
{
return fs::u8path(util::user_home_dir()) / "micromamba";
if (util::get_env("MAMBA_DEFAULT_ROOT_PREFIX"))
{
prefix = util::get_env("MAMBA_DEFAULT_ROOT_PREFIX").value();
LOG_WARNING << unindent(R"(
'MAMBA_DEFAULT_ROOT_PREFIX' is meant for testing purpose.
Consider using 'MAMBA_ROOT_PREFIX' instead)");
}
else
{
#ifdef MAMBA_USE_INSTALL_PREFIX_AS_BASE
// mamba case
// set the root prefix as the mamba installation path
get_root_prefix_from_mamba_bin(util::which("mamba"))
.transform([&](fs::u8path&& p) { prefix = std::move(p); })
.or_else([](mamba_error&& error) { throw std::move(error); });
#else
// micromamba case

// In 1.0, only micromamba was using this location.
const fs::u8path default_root_prefix_v1 = fs::u8path(util::user_home_dir())
/ "micromamba";

// In 2.0, we change the default location.
// We unconditionally name the subfolder "mamba" for compatibility between ``mamba``
// and ``micromamba``, as well as consistency with ``MAMBA_`` environment variables.
const fs::u8path default_root_prefix_v2 = fs::u8path(util::user_data_dir()) / "mamba";

validate_existing_root_prefix(default_root_prefix_v1)
.or_else([&default_root_prefix_v2](const auto& /* error */)
{ return validate_root_prefix(default_root_prefix_v2); })
.transform([&](fs::u8path&& p) { prefix = std::move(p); })
.or_else([](mamba_error&& error) { throw std::move(error); });
#endif
}
}

/**
* In mamba 2.0, we change the default location.
* We unconditionally name the subfolder "mamba" for compatibility between ``mamba``
* and ``micromamba``, as well as consistency with ``MAMBA_`` environment variables.
*/
auto default_root_prefix_v2() -> fs::u8path
auto get_root_prefix() -> fs::u8path
{
return fs::u8path(util::user_data_dir()) / "mamba";
fs::u8path root_prefix = util::get_env("MAMBA_ROOT_PREFIX").value_or("");
if (root_prefix.empty())
{
get_default_root_prefix(root_prefix);
}
return root_prefix;
}

void root_prefix_hook(Configuration& config, fs::u8path& prefix)
Expand All @@ -737,30 +767,7 @@ namespace mamba

if (prefix.empty())
{
if (util::get_env("MAMBA_DEFAULT_ROOT_PREFIX"))
{
prefix = util::get_env("MAMBA_DEFAULT_ROOT_PREFIX").value();
LOG_WARNING << unindent(R"(
'MAMBA_DEFAULT_ROOT_PREFIX' is meant for testing purpose.
Consider using 'MAMBA_ROOT_PREFIX' instead)");
}
else
{
#ifdef MAMBA_USE_INSTALL_PREFIX_AS_BASE
// mamba case
// set the root prefix as the mamba installation path
get_root_prefix_from_mamba_bin(util::which("mamba"))
.transform([&](fs::u8path&& p) { prefix = std::move(p); })
.or_else([](mamba_error&& error) { throw std::move(error); });
#else
// micromamba case
validate_existing_root_prefix(default_root_prefix_v1())
.or_else([](const auto& /* error */)
{ return validate_root_prefix(default_root_prefix_v2()); })
.transform([&](fs::u8path&& p) { prefix = std::move(p); })
.or_else([](mamba_error&& error) { throw std::move(error); });
#endif
}
prefix = get_root_prefix();

if (env_name.configured())
{
Expand Down
3 changes: 2 additions & 1 deletion libmamba/src/core/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>

#include "mamba/api/configuration.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/execution.hpp"
#include "mamba/core/output.hpp"
Expand Down Expand Up @@ -177,7 +178,7 @@ namespace mamba
Context::Context(const ContextOptions& options)
{
on_ci = static_cast<bool>(util::get_env("CI"));
prefix_params.root_prefix = util::get_env("MAMBA_ROOT_PREFIX").value_or("");
prefix_params.root_prefix = detail::get_root_prefix();
prefix_params.conda_prefix = prefix_params.root_prefix;

envs_dirs = { prefix_params.root_prefix / "envs" };
Expand Down
46 changes: 44 additions & 2 deletions libmamba/src/download/downloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//
// The full license is in the file LICENSE, distributed with this software.

#include "mamba/api/configuration.hpp"
#include "mamba/core/invoke.hpp"
#include "mamba/core/thread_utils.hpp"
#include "mamba/core/util.hpp"
Expand All @@ -24,13 +25,18 @@ namespace mamba::download
namespace
{

constexpr std::array<const char*, 6> cert_locations{
constexpr std::array<const char*, 10> cert_locations{
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
"/etc/ssl/cert.pem", // Alpine Linux
// MacOS
"/System/Library/OpenSSL/certs/cert.pem",
"/usr/local/etc/openssl/cert.pem",
"/usr/local/share/certs/ca-root-nss.crt",
"/usr/local/share/certs/ca-root.crt",
};

void init_remote_fetch_params(Context::RemoteFetchParams& remote_fetch_params)
Expand Down Expand Up @@ -74,16 +80,52 @@ namespace mamba::download
LOG_INFO << "Using REQUESTS_CA_BUNDLE " << remote_fetch_params.ssl_verify;
}
}
else if (remote_fetch_params.ssl_verify == "<system>" && util::on_linux)
// TODO: Adapt the semantic of `<system>` to decouple the use of CA certificates
// from `conda-forge::ca-certificates` and the system CA certificates.
else if (remote_fetch_params.ssl_verify == "<system>")
{
// Use the CA certificates from `conda-forge::ca-certificates` installed in the
// root prefix or the system CA certificates if the certificate is not present.
fs::u8path libmamba_library_path;

fs::u8path root_prefix = detail::get_root_prefix();
fs::u8path env_prefix_conda_cert = root_prefix / "ssl" / "cacert.pem";

LOG_INFO << "Checking for CA certificates at the root prefix: "
<< env_prefix_conda_cert;

if (fs::exists(env_prefix_conda_cert))
{
LOG_INFO << "Using CA certificates from `conda-forge::ca-certificates` installed in the root prefix "
<< "(i.e " << env_prefix_conda_cert << ")";
remote_fetch_params.ssl_verify = env_prefix_conda_cert;
remote_fetch_params.curl_initialized = true;
return;
}

// Fallback on system CA certificates.
bool found = false;

// TODO: find if one needs to specify a CA certificate on Windows or not
// given that the location of system's CA certificates is not clear on Windows.
// For now, just use `libcurl` and the SSL libraries' default.
if (util::on_win)
{
LOG_INFO << "Using libcurl/the SSL library's default CA certification";
remote_fetch_params.ssl_verify = "";
found = true;
remote_fetch_params.curl_initialized = true;
return;
}

for (const auto& loc : cert_locations)
{
if (fs::exists(loc))
{
LOG_INFO << "Using system CA certificates at: " << loc;
remote_fetch_params.ssl_verify = loc;
found = true;
break;
}
}

Expand Down
42 changes: 42 additions & 0 deletions libmamba/tests/src/download/test_downloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <catch2/catch_all.hpp>

#include "mamba/api/configuration.hpp"
#include "mamba/download/downloader.hpp"

#include "mambatests.hpp"
Expand Down Expand Up @@ -54,5 +55,46 @@ namespace mamba
std::runtime_error
);
}

TEST_CASE("Use CA certificate from the root prefix")
{
// Create a context, make a request and check that ssl_verify is set to the correct path
auto& context = mambatests::singletons().context;

// Set the context values to the default ones
context.remote_fetch_params.curl_initialized = false;
context.remote_fetch_params.ssl_verify = "<system>";

download::Request request(
"test",
download::MirrorName(""),
"https://conda.anaconda.org/conda-forge/linux-64/repodata.json",
"test_download_repodata.json"
);
download::MultiRequest dl_request{ std::vector{ std::move(request) } };

// Downloading must initialize curl and set `ssl_verify` to the path of the CA
// certificate
REQUIRE(!context.remote_fetch_params.curl_initialized);
download::MultiResult res = download::download(dl_request, context.mirrors, context);
REQUIRE(context.remote_fetch_params.curl_initialized);

auto certificates = context.remote_fetch_params.ssl_verify;
const fs::u8path root_prefix = detail::get_root_prefix();
const fs::u8path expected_certificates = root_prefix / "ssl" / "cert.pem";

// TODO: is libmamba tested without a root prefix or a base installation?
bool reach_fallback_certificates;
if (util::on_win)
{
// Default certificates from libcurl/libssl are used on Windows
reach_fallback_certificates = certificates == "";
}
else
{
reach_fallback_certificates = (mamba::util::ends_with(certificates, "cert.pem") || mamba::util::ends_with(certificates, "ca-certificates.crt"));
}
REQUIRE((certificates == expected_certificates || reach_fallback_certificates));
}
}
}
30 changes: 30 additions & 0 deletions micromamba/tests/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -1608,3 +1608,33 @@ def test_update_spec_list(tmp_path):
out = helpers.create("-p", env_prefix, "-f", env_spec_file, "--dry-run")

assert update_specs_list in out.replace("\r", "")


def test_ca_certificates(tmp_path):
# Check that CA certificates from conda-forge or that the fall back is used by micromamba.
env_prefix = tmp_path / "env-ca-certificates"

umamba = helpers.get_umamba()
args = [umamba, "create", "-p", env_prefix, "numpy", "--dry-run", "-vvv"]
p = subprocess.run(args, capture_output=True, check=True)
verbose_logs = p.stderr.decode()

root_prefix_ca_certificates_used = (
"Using CA certificates from `conda-forge::ca-certificates` installed in the root prefix"
in verbose_logs
)

system_ca_certificates_used = "Using system CA certificates at" in verbose_logs

default_libcurl_certificates_used = (
"Using libcurl/the SSL library's default CA certification" in verbose_logs
)

# On Windows default
fall_back_certificates_used = (
default_libcurl_certificates_used
if platform.system() == "Windows"
else system_ca_certificates_used
)

assert root_prefix_ca_certificates_used or fall_back_certificates_used

0 comments on commit 7fa748f

Please sign in to comment.