From 1b0a11f9ef91c64aea878638f9c0fcec7153d168 Mon Sep 17 00:00:00 2001 From: Benjy Weinberger Date: Thu, 20 Aug 2020 13:23:35 -0400 Subject: [PATCH 1/4] Support custom CA bundles. [ci skip-build-wheels] [ci skip-rust] --- src/python/pants/engine/BUILD | 1 + src/python/pants/engine/fs_test.py | 256 ++++++++++-------- src/python/pants/engine/internals/BUILD | 5 + src/python/pants/engine/internals/native.py | 4 +- .../pants/engine/internals/scheduler.py | 4 +- .../engine/internals/scheduler_test_base.py | 2 + .../internals/tls_testing/generate_certs.sh | 62 +++++ .../engine/internals/tls_testing/openssl.cnf | 15 + .../internals/tls_testing/rsa/server.chain | 55 ++++ .../internals/tls_testing/rsa/server.crt | 23 ++ .../internals/tls_testing/rsa/server.key | 28 ++ src/python/pants/init/engine_initializer.py | 8 + src/python/pants/option/global_options.py | 8 + src/python/pants/testutil/test_base.py | 1 + src/python/pants/util/contextutil.py | 6 +- src/rust/engine/Cargo.lock | 1 + src/rust/engine/Cargo.toml | 1 + src/rust/engine/src/context.rs | 48 +++- src/rust/engine/src/externs/interface.rs | 3 + 19 files changed, 407 insertions(+), 124 deletions(-) create mode 100755 src/python/pants/engine/internals/tls_testing/generate_certs.sh create mode 100644 src/python/pants/engine/internals/tls_testing/openssl.cnf create mode 100644 src/python/pants/engine/internals/tls_testing/rsa/server.chain create mode 100644 src/python/pants/engine/internals/tls_testing/rsa/server.crt create mode 100644 src/python/pants/engine/internals/tls_testing/rsa/server.key diff --git a/src/python/pants/engine/BUILD b/src/python/pants/engine/BUILD index 7e7b7993131..3a9d880e8e0 100644 --- a/src/python/pants/engine/BUILD +++ b/src/python/pants/engine/BUILD @@ -6,6 +6,7 @@ python_library() python_tests( name='tests', dependencies=[ + 'src/python/pants/engine/internals:tls_testing', 'src/python/pants/engine/internals/examples:fs_test', ], timeout=90, diff --git a/src/python/pants/engine/fs_test.py b/src/python/pants/engine/fs_test.py index eb5a91a6ae1..31523732960 100644 --- a/src/python/pants/engine/fs_test.py +++ b/src/python/pants/engine/fs_test.py @@ -4,7 +4,9 @@ import hashlib import logging import os +import pkgutil import shutil +import ssl import tarfile import time import unittest @@ -43,13 +45,34 @@ from pants.util.dirutil import relative_symlink, safe_file_dump -class FSTest(TestBase, SchedulerTestBase): +class FSTestBase(TestBase, SchedulerTestBase): + @staticmethod + def assert_snapshot_equals(snapshot: Snapshot, files: List[str], digest: Digest) -> None: + assert list(snapshot.files) == files + assert snapshot.digest == digest + + def prime_store_with_roland_digest(self) -> Digest: + """This method primes the store with a directory of a file named 'roland' and contents + 'European Burmese'.""" + with temporary_dir() as temp_dir: + with open(os.path.join(temp_dir, "roland"), "w") as f: + f.write("European Burmese") + globs = PathGlobs(["*"]) + snapshot = self.scheduler.capture_snapshots((PathGlobsAndRoot(globs, temp_dir),))[0] + + expected_digest = Digest( + "63949aa823baf765eff07b946050d76ec0033144c785a94d3ebd82baa931cd16", 80 + ) + self.assert_snapshot_equals(snapshot, ["roland"], expected_digest) + return expected_digest + + +class FSTest(FSTestBase): @classmethod def rules(cls): return ( *super().rules(), QueryRule(Snapshot, (DigestSubset,)), - QueryRule(Snapshot, (DownloadFile,)), ) _original_src = os.path.join( @@ -328,13 +351,7 @@ def test_snapshot_from_outside_buildroot_failure(self) -> None: ) assert "doesnotexist" in str(cm.exception) - @staticmethod - def assert_snapshot_equals(snapshot: Snapshot, files: List[str], digest: Digest) -> None: - assert list(snapshot.files) == files - assert snapshot.digest == digest - - def test_merge_digests(self) -> None: - assert self.request_product(Digest, [MergeDigests([])]) == EMPTY_DIGEST + def test_asynchronously_merge_digests(self) -> None: with temporary_dir() as temp_dir: Path(temp_dir, "roland").write_text("European Burmese") Path(temp_dir, "susannah").write_text("Not sure actually") @@ -558,110 +575,6 @@ def test_glob_match_ignore_logging(self) -> None: ) assert len(captured.warnings()) == 0 - def prime_store_with_roland_digest(self) -> Digest: - """This method primes the store with a directory of a file named 'roland' and contents - 'European Burmese'.""" - with temporary_dir() as temp_dir: - with open(os.path.join(temp_dir, "roland"), "w") as f: - f.write("European Burmese") - globs = PathGlobs(["*"]) - snapshot = self.scheduler.capture_snapshots((PathGlobsAndRoot(globs, temp_dir),))[0] - - expected_digest = Digest( - "63949aa823baf765eff07b946050d76ec0033144c785a94d3ebd82baa931cd16", 80 - ) - self.assert_snapshot_equals(snapshot, ["roland"], expected_digest) - return expected_digest - - pantsbuild_digest = Digest( - "f461fc99bcbe18e667687cf672c2dc68dc5c5db77c5bd426c9690e5c9cec4e3b", 184 - ) - - def test_download(self) -> None: - with self.isolated_local_store(): - with http_server(StubHandler) as port: - snapshot = self.request_product( - Snapshot, - [ - DownloadFile( - f"http://localhost:{port}/do_not_remove_or_edit.txt", - self.pantsbuild_digest, - ) - ], - ) - self.assert_snapshot_equals( - snapshot, - ["do_not_remove_or_edit.txt"], - Digest("03bb499daabafc60212d2f4b2fab49b47b35b83a90c056224c768d52bce02691", 102), - ) - - def test_download_missing_file(self) -> None: - with self.isolated_local_store(): - with http_server(StubHandler) as port: - with self.assertRaises(ExecutionError) as cm: - self.request_product( - Snapshot, - [DownloadFile(f"http://localhost:{port}/notfound", self.pantsbuild_digest)], - ) - assert "404" in str(cm.exception) - - def test_download_wrong_digest(self) -> None: - with self.isolated_local_store(): - with http_server(StubHandler) as port: - with self.assertRaises(ExecutionError) as cm: - self.request_product( - Snapshot, - [ - DownloadFile( - f"http://localhost:{port}/do_not_remove_or_edit.txt", - Digest( - self.pantsbuild_digest.fingerprint, - self.pantsbuild_digest.serialized_bytes_length + 1, - ), - ) - ], - ) - assert "wrong digest" in str(cm.exception).lower() - - # It's a shame that this isn't hermetic, but setting up valid local HTTPS certificates is a pain. - def test_download_https(self) -> None: - with self.isolated_local_store(): - snapshot = self.request_product( - Snapshot, - [ - DownloadFile( - "https://binaries.pantsbuild.org/do_not_remove_or_edit.txt", - Digest( - "f461fc99bcbe18e667687cf672c2dc68dc5c5db77c5bd426c9690e5c9cec4e3b", 184 - ), - ) - ], - ) - self.assert_snapshot_equals( - snapshot, - ["do_not_remove_or_edit.txt"], - Digest("03bb499daabafc60212d2f4b2fab49b47b35b83a90c056224c768d52bce02691", 102), - ) - - def test_caches_downloads(self) -> None: - with self.isolated_local_store(): - with http_server(StubHandler) as port: - self.prime_store_with_roland_digest() - - # This would error if we hit the HTTP server, because 404, - # but we're not going to hit the HTTP server because it's cached, - # so we shouldn't see an error... - url = DownloadFile( - f"http://localhost:{port}/roland", - Digest("693d8db7b05e99c6b7a7c0616456039d89c555029026936248085193559a0b5d", 16), - ) - snapshot = self.request_product(Snapshot, [url]) - self.assert_snapshot_equals( - snapshot, - ["roland"], - Digest("9341f76bef74170bedffe51e4f2e233f61786b7752d21c2339f8ee6070eba819", 82), - ) - def generate_original_digest(self) -> Digest: content = b"dummy content" return self.request_product( @@ -865,13 +778,116 @@ def mutation_function(project_tree, dir_path): self.assert_mutated_digest(mutation_function) -class StubHandler(BaseHTTPRequestHandler): - # See https://binaries.pantsbuild.org/do_not_remove_or_edit.txt - response_text = b"""Some tests rely on the existence of this file, with this exact content. -Edit only if you know what you're doing. +class DownloadsTest(FSTestBase): + file_digest = Digest("8fcbc50cda241aee7238c71e87c27804e7abc60675974eaf6567aa16366bc105", 14) + + expected_snapshot_digest = Digest( + "4c9cf91fcd7ba1abbf7f9a0a1c8175556a82bee6a398e34db3284525ac24a3ad", 84 + ) + + @classmethod + def rules(cls): + return ( + *super().rules(), + QueryRule(Snapshot, (DownloadFile,)), + ) + + def test_download(self) -> None: + with self.isolated_local_store(): + with http_server(StubHandler) as port: + snapshot = self.request_product( + Snapshot, + [DownloadFile(f"http://localhost:{port}/file.txt", self.file_digest)], + ) + self.assert_snapshot_equals( + snapshot, + ["file.txt"], + self.expected_snapshot_digest, + ) + + def test_download_missing_file(self) -> None: + with self.isolated_local_store(): + with http_server(StubHandler) as port: + with self.assertRaises(ExecutionError) as cm: + self.request_product( + Snapshot, + [DownloadFile(f"http://localhost:{port}/notfound", self.file_digest)], + ) + assert "404" in str(cm.exception) + + def test_download_wrong_digest(self) -> None: + with self.isolated_local_store(): + with http_server(StubHandler) as port: + with self.assertRaises(ExecutionError) as cm: + self.request_product( + Snapshot, + [ + DownloadFile( + f"http://localhost:{port}/file.txt", + Digest( + self.file_digest.fingerprint, + self.file_digest.serialized_bytes_length + 1, + ), + ) + ], + ) + assert "wrong digest" in str(cm.exception).lower() + + def test_caches_downloads(self) -> None: + with self.isolated_local_store(): + with http_server(StubHandler) as port: + self.prime_store_with_roland_digest() -See src/python/pants/engine/fs_test.py in the pants repo for details. -""" + # This would error if we hit the HTTP server, because 404, + # but we're not going to hit the HTTP server because it's cached, + # so we shouldn't see an error... + url = DownloadFile( + f"http://localhost:{port}/roland", + Digest("693d8db7b05e99c6b7a7c0616456039d89c555029026936248085193559a0b5d", 16), + ) + snapshot = self.request_product(Snapshot, [url]) + self.assert_snapshot_equals( + snapshot, + ["roland"], + Digest("9341f76bef74170bedffe51e4f2e233f61786b7752d21c2339f8ee6070eba819", 82), + ) + + def test_download_https(self) -> None: + # Note that this also tests that the custom certs functionality works. + with temporary_dir() as temp_dir: + + def write_resource(name: str) -> Path: + path = Path(temp_dir) / name + data = pkgutil.get_data("pants.engine.internals", f"tls_testing/rsa/{name}") + assert data is not None + path.write_bytes(data) + return path + + server_cert = write_resource("server.crt") + server_key = write_resource("server.key") + cert_chain = write_resource("server.chain") + + scheduler = self.mk_scheduler( + rules=[*fs_rules(), QueryRule(Snapshot, (DownloadFile,))], + ca_certs_path=str(cert_chain), + ) + with self.isolated_local_store(): + ssl_context = ssl.SSLContext() + ssl_context.load_cert_chain(certfile=str(server_cert), keyfile=str(server_key)) + + with http_server(StubHandler, ssl_context=ssl_context) as port: + snapshot = self.execute( + scheduler, + Snapshot, + DownloadFile(f"https://localhost:{port}/file.txt", self.file_digest), + )[0] + self.assert_snapshot_equals( + snapshot, ["file.txt"], self.expected_snapshot_digest + ) + + +class StubHandler(BaseHTTPRequestHandler): + response_text = b"Hello, client!" def do_HEAD(self): self.send_headers() @@ -881,7 +897,7 @@ def do_GET(self): self.wfile.write(self.response_text) def send_headers(self): - code = 200 if self.path == "/do_not_remove_or_edit.txt" else 404 + code = 200 if self.path == "/file.txt" else 404 self.send_response(code) self.send_header("Content-Type", "text/utf-8") self.send_header("Content-Length", f"{len(self.response_text)}") diff --git a/src/python/pants/engine/internals/BUILD b/src/python/pants/engine/internals/BUILD index 01b4fb62e12..bc6c0600e66 100644 --- a/src/python/pants/engine/internals/BUILD +++ b/src/python/pants/engine/internals/BUILD @@ -36,3 +36,8 @@ resources( 'native_engine.so.metadata', ], ) + +resources( + name='tls_testing', + sources=['tls_testing/rsa/*'] +) diff --git a/src/python/pants/engine/internals/native.py b/src/python/pants/engine/internals/native.py index 08a4db25c60..e9c157a8260 100644 --- a/src/python/pants/engine/internals/native.py +++ b/src/python/pants/engine/internals/native.py @@ -3,7 +3,7 @@ import logging import os -from typing import Dict, Iterable, List, Mapping, Tuple, Union, cast +from typing import Dict, Iterable, List, Mapping, Optional, Tuple, Union, cast from typing_extensions import Protocol @@ -224,6 +224,7 @@ def new_scheduler( local_store_dir: str, local_execution_root_dir: str, named_caches_dir: str, + ca_certs_path: Optional[str], ignore_patterns: List[str], use_gitignore: bool, execution_options, @@ -273,6 +274,7 @@ def new_scheduler( local_store_dir, local_execution_root_dir, named_caches_dir, + ca_certs_path, ignore_patterns, use_gitignore, remoting_options, diff --git a/src/python/pants/engine/internals/scheduler.py b/src/python/pants/engine/internals/scheduler.py index aa7b36492d6..87828819b34 100644 --- a/src/python/pants/engine/internals/scheduler.py +++ b/src/python/pants/engine/internals/scheduler.py @@ -93,6 +93,7 @@ def __init__( local_store_dir: str, local_execution_root_dir: str, named_caches_dir: str, + ca_certs_path: Optional[str], rules: FrozenOrderedSet[Rule], union_membership: UnionMembership, execution_options: ExecutionOptions, @@ -105,10 +106,10 @@ def __init__( :param ignore_patterns: A list of gitignore-style file patterns for pants to ignore. :param use_gitignore: If set, pay attention to .gitignore files. :param build_root: The build root as a string. - :param work_dir: The pants work dir. :param local_store_dir: The directory to use for storing the engine's LMDB store in. :param local_execution_root_dir: The directory to use for local execution sandboxes. :param named_caches_dir: The directory to use as the root for named mutable caches. + :param ca_certs_path: Path to pem file for custom CA, if needed. :param rules: A set of Rules which is used to compute values in the graph. :param union_membership: All the registered and normalized union rules. :param execution_options: Execution options for (remote) processes. @@ -153,6 +154,7 @@ def __init__( local_store_dir=local_store_dir, local_execution_root_dir=local_execution_root_dir, named_caches_dir=named_caches_dir, + ca_certs_path=ca_certs_path, ignore_patterns=ignore_patterns, use_gitignore=use_gitignore, execution_options=execution_options, diff --git a/src/python/pants/engine/internals/scheduler_test_base.py b/src/python/pants/engine/internals/scheduler_test_base.py index f54e6e0bfbc..cc6a2293208 100644 --- a/src/python/pants/engine/internals/scheduler_test_base.py +++ b/src/python/pants/engine/internals/scheduler_test_base.py @@ -49,6 +49,7 @@ def mk_scheduler( include_trace_on_error=True, should_report_workunits=False, execution_options=None, + ca_certs_path=None, ): """Creates a SchedulerSession for a Scheduler with the given Rules installed.""" rules = rules or [] @@ -69,6 +70,7 @@ def mk_scheduler( local_store_dir=local_store_dir, local_execution_root_dir=local_execution_root_dir, named_caches_dir=named_caches_dir, + ca_certs_path=ca_certs_path, rules=rules, union_membership=UnionMembership({}), execution_options=execution_options or DEFAULT_EXECUTION_OPTIONS, diff --git a/src/python/pants/engine/internals/tls_testing/generate_certs.sh b/src/python/pants/engine/internals/tls_testing/generate_certs.sh new file mode 100755 index 00000000000..071e867bd41 --- /dev/null +++ b/src/python/pants/engine/internals/tls_testing/generate_certs.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Run this script in this dir to generate the certs and keys needed for this test. +# Note that on MacOS you will need a recent-ish homebrewed openssl, not the system one. + +set -xeuo pipefail + +rm -rf rsa/ +mkdir rsa/ + +openssl req -nodes \ + -x509 \ + -days 3650 \ + -newkey rsa:4096 \ + -keyout rsa/root_ca.key \ + -out rsa/root_ca.crt \ + -sha256 \ + -batch \ + -subj "/CN=Root CA for testing" + +openssl req -nodes \ + -newkey rsa:3072 \ + -keyout rsa/intermediate_ca.key \ + -out rsa/intermediate_ca.req \ + -sha256 \ + -batch \ + -subj "/CN=Intermediate CA for testing" + +openssl req -nodes \ + -newkey rsa:2048 \ + -keyout rsa/server.key \ + -out rsa/server.req \ + -sha256 \ + -batch \ + -subj "/CN=Server for testing" + +openssl x509 -req \ + -in rsa/intermediate_ca.req \ + -out rsa/intermediate_ca.crt \ + -CA rsa/root_ca.crt \ + -CAkey rsa/root_ca.key \ + -sha256 \ + -days 3650 \ + -set_serial 123 \ + -extensions v3_intermediate_ca -extfile openssl.cnf + +openssl x509 -req \ + -in rsa/server.req \ + -out rsa/server.crt \ + -CA rsa/intermediate_ca.crt \ + -CAkey rsa/intermediate_ca.key \ + -sha256 \ + -days 3000 \ + -set_serial 456 \ + -extensions v3_server -extfile openssl.cnf + +cat rsa/intermediate_ca.crt rsa/root_ca.crt > rsa/server.chain + +# Clean up intermediate files. +rm -f rsa/root_ca.* +rm -f rsa/intermediate_ca.* +rm -f rsa/server.req diff --git a/src/python/pants/engine/internals/tls_testing/openssl.cnf b/src/python/pants/engine/internals/tls_testing/openssl.cnf new file mode 100644 index 00000000000..706b05230e7 --- /dev/null +++ b/src/python/pants/engine/internals/tls_testing/openssl.cnf @@ -0,0 +1,15 @@ +[ v3_server ] +basicConstraints = critical,CA:false +keyUsage = nonRepudiation, digitalSignature +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +subjectAltName = @alt_names + +[ v3_intermediate_ca ] +subjectKeyIdentifier = hash +extendedKeyUsage = critical, serverAuth, clientAuth +basicConstraints = CA:true +keyUsage = cRLSign, keyCertSign, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign + +[ alt_names ] +DNS.1 = localhost diff --git a/src/python/pants/engine/internals/tls_testing/rsa/server.chain b/src/python/pants/engine/internals/tls_testing/rsa/server.chain new file mode 100644 index 00000000000..c423bb4d4f0 --- /dev/null +++ b/src/python/pants/engine/internals/tls_testing/rsa/server.chain @@ -0,0 +1,55 @@ +-----BEGIN CERTIFICATE----- +MIIEnTCCAoWgAwIBAgIBezANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDDBNSb290 +IENBIGZvciB0ZXN0aW5nMB4XDTIwMDkxMTAxMTAzMVoXDTMwMDkwOTAxMTAzMVow +JjEkMCIGA1UEAwwbSW50ZXJtZWRpYXRlIENBIGZvciB0ZXN0aW5nMIIBojANBgkq +hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAqe0ugwo9sydGt8mCvPJ0Z3GYwjFHhFpc +/jIDUp9Ppfl9BBRKim5MFl2EQXOMffeRsONsN9PhWJeBsFiQgItA4IKPyiyA3bma +jSsqyWlsNzdeofTl3Mu+B2ezdtbDz+K9xonxZ8kT+TEy/ziNLXEeqjG/eAq9yvsc +JbFqusTyC0Evc+G1kbEmTDUdvayOwJwY7F/VxY0JDWAqkaoU71Js8tB0iOp4nPwF +SQ2ioDDkXBfrKPUE0MHcAcb25JS0M8vGlUrnKBTjARBygmb9QiHStI9DWRdTQvxx +NB7knX2lP+FrkjEtbrc9Wokc4jxRxtCfu6K85JOCej3hE1Wj9EHIACU231aci8KP +CU4rd9uKBt+uj6/Ly4wbfcygZOnNYsgwW11dl6wHPwbWcGUu4zU7x457H1CY/xr2 +sBIe5yX6orBbcagZhlN3xtEIB9Ssi2dG8JuS+OXklhbM48YwaXTJr4SP9NAgEkLO +CyYNUZWcEXM4r9vSsaWPC2FLjuHo+Z4VAgMBAAGjXjBcMB0GA1UdDgQWBBTiNT0B +EwGm+estWVTC3fPxNE2SdDAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAQYIKwYBBQUH +AwIwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAf4wDQYJKoZIhvcNAQELBQADggIB +AHl1ZjEf4k4k8kZB62uEV/0yKmWbLxfyzc3qq/BGW8RPlRbIG1v3M0mWQe/rwcdm +0CFsfLwAt/NYRc1bJPjLR5yyaMiZ3pODyqGcbxTWO/0wDXzkodtapA2CBdgUPayJ +GW7jrKRm0tdD0+MAHkv/L8D8Mq2t3H+MbRcRJOLgrm4Rj9mhARt309o+nlUu6wHo +jr+013Jd62UKZI6z8DWMjs0LLuDgTVv1uF2/XRa3GCxuk6mms9AvlNVu0W0tQTi7 +ABCxk9i4Q5oavGerm6JuZ1+/6VSReTqPbDMqxUb6ZA0K8yRaIKl0s3Dc0eo3N0Ba +10upIs/UX4hfFZES3SmdmCiUa0hhwxLFgj/ac22VcErAkS6ZlzwCZdYwSwOZSjeo +FgB1oJwlFFn3VgeIQW/uQLUKZ/+JarXE0F0O7enJfAJum6kYoBN7QR1rgOmMminE +NP44v11A4Jhb48kwieHTizkOcByugDYA8lQeLvcs9dNJEItR9eAO6pEcy7bwZgsq +TueF5k56kj9WyJ4866GKwIFfsNDsNd6CV7URrgJo12NEWgbzjQaJ4j8D01dDuX5k +2kaY7Ki/dqzeX7fbPkPlIrvyRhCgBVEKu+j8RKObUeG4QOp7nq6nbETXqB4epEK9 +kQWwz3tjuoIVqowu/smYZ72/ff/UH/8ZUk+tw+WYGU// +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEuDCCAqACCQCDmtAhDPh5ezANBgkqhkiG9w0BAQsFADAeMRwwGgYDVQQDDBNS +b290IENBIGZvciB0ZXN0aW5nMB4XDTIwMDkxMTAxMTAzMVoXDTMwMDkwOTAxMTAz +MVowHjEcMBoGA1UEAwwTUm9vdCBDQSBmb3IgdGVzdGluZzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAMLT0jd0gP3tGGInjIrRwT3E42JsdX9zmxP7Ad3j +k1uMQ61goLaiEXH98z39ox9G5d26y3dt51+6SgvmG7SmStk2QGphlWkURfoTMLNu +Rj0dQ8aqC+dR/W8tQ622DZfG6RYmYYaW0KACHWSC3wdM3C0FOZHl5x4L1YPLzG1k +uhZd3GhTY0o7d6Mm7Bgd+4Oiz3gpKIEje+AuMQ3/2bWKejKY4mmLYF3sAsn1huCQ +9LTBt5Rhj+DMH6BXJiPZh/nIWqd3ih6zx3EXpad3g32fZQvt+nuH8nYAe9LQ8A7G +F4h+nN6Z2wrdnNJa7DcFQi6fu9wTKOGxNWsP2ipl9zrCvjBBT/+3uCtLOYOGfTE5 +hgIcy7wOCbntDsn4X+aCYVzgI3hW48sl3ZMr+LBgFMvRhhcDhY/3/MIyl7NbVXlf +TDEcLjk63nnFHKsU5Moist6Zpno1GQ1gOUjL+gAsMfAxbZGlmVOxM0nzhlDUhuX8 +jW8fR8UIY6w1ew0Y8sQ/w0x29+GPHicyoUN7LM8pMiBKpurdmq9SvSankhHOntIk +T7scohLwoCUcjLC+3uLOOfy38oOusLv/MCmpavjQwkLELL6OoEcAFYKfXsxDqw0y +f7+/BXqdgavZo4QYCcTQ3gsCzHdswNx2kMWdh7xFC+tlqHcRX4+/0i9iHZ0n5GNB +IUnDAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAAo4WzsGu9+bNCwykGpYRgxAV5JY +gltLI7lQOgMVFWdiKee52HbO5rI08VvhwUrhmLGjtgAuCOP1+dZsJTdEi+CUWYRt +g67Yfe4T9YljT/dJwo96EgWZJqUde8V/uukPbs/hwZ8ep2F5vAJNmfLoywrhdh2k +UPZ3x13vxBuVOyMVjGLHjMDfGWm9Nmzv4HPRbf6d9y3ALjwJdmJF4CWnJdTYLCpq +sx8V+FiL23vdl1316OON7OjiR/oTXBn+h8inT1IJpWt1+9JAcCeQLs7F1zscza6y +LmrwYHu+7aFTGzFs2VBUan4diIHSovN1H81jRWRrLA0+zxxdF2wqKLmlXx3It82g +97cvqo+ki+Fx5pvDUNfDeWHAMDQ9wExXvrknQ6Dx2l4HtF1SQFPgusgNAEXZYDnA +30+3AG8psHgHFr3KBTYa/f16XonDWxZjExFTtczfwoX1Hmr7OTv+SsHjfYdwWsOM +8uD0HDFczE0pziCOXyNy1A9H159gMBh9AcO2+Y97pQxIv++9QBMJCa7uWV2kP6kc +3jAgN5ZD6ud2pikRzIhL0MqjX7KL3q/IwU55K02272NZbfS/IlIlYhKfmwaEG/bO +cQlYySGq1tYYMHLlU/K11PRju/ZB6JhNzx9RgJU8nhwdjUB1Yks1Wbw0z9eWGNiZ +B+ourhhtq77POts1 +-----END CERTIFICATE----- diff --git a/src/python/pants/engine/internals/tls_testing/rsa/server.crt b/src/python/pants/engine/internals/tls_testing/rsa/server.crt new file mode 100644 index 00000000000..f1adb6d2592 --- /dev/null +++ b/src/python/pants/engine/internals/tls_testing/rsa/server.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID2zCCAkOgAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwJjEkMCIGA1UEAwwbSW50 +ZXJtZWRpYXRlIENBIGZvciB0ZXN0aW5nMB4XDTIwMDkxMTAxMTAzMVoXDTI4MTEy +ODAxMTAzMVowHTEbMBkGA1UEAwwSU2VydmVyIGZvciB0ZXN0aW5nMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArtnfnLuKwVTUlvKW1loAm6juxuMNRoc1 +xqSPLgNVTnuudWfq3IciMX+HpvgmD/GFpk2deyLcu5LGKXkZ9olPK1f8HuD065a1 +IScJv39LxE8nObwjbxrYEflyhW9CNNNWca07afNQPXcKb/+pdio/fSVj1g4ukAs5 +M/c5um4pupc39f7gkLr9n3wcCLsUyY1RXq/dcEgGb6mlvI+6HxNqJNTuDJskoqFu +/8mv1BU3KMGkFlm7FO2TydoURc1iQfuGbzL8QefqDGexTe61Aywc+/FpPZfNiN9D +W7yy6OEzEwhKFTC7C4WE+lJOp/6y8TBO9NYd5zttDH3+BmWbOo/c2QIDAQABo4Gb +MIGYMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgbAMB0GA1UdDgQWBBSjtWcMFR5c +8hDE25gwf418ojOXfTBGBgNVHSMEPzA9gBTiNT0BEwGm+estWVTC3fPxNE2SdKEi +pCAwHjEcMBoGA1UEAwwTUm9vdCBDQSBmb3IgdGVzdGluZ4IBezAUBgNVHREEDTAL +gglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGBAC8mScoShcC2VRRHy8VULNTT +N+Ek68DFlYhLqTgtOfdN2DI1v8DZsV6cIWInfvarMrA6UsoLQbGL76hzAmp11asf +1U5B4oCy08zNel/yi82TwbrtwjHyXRxuKxKVZunkYw878iaH8FwAWZpbLSfrjTqL +bl8pUyoIzqixLMCtcv/FCc2G4G/qO/XBYrGTMuWPuU0z1+9f4/9GkFbTlOfaXZWE +NsqrV1Hc4V7F9bqB3ZCX4XuFq2+VED88lZRyD1j67FYju2OgS2Bqe1B8RMf9hsOn +WxHcK7Otk6QmSSwE2EMVZQay+hSHDuCuiMuleyGOAKr+jw1OGf82wCJIOWM3KjxX +jF+GH3pXgPIfy1eroKcG8TASH/MNzNOByF9lcPfMc2RdQxPuO2gX/r9zxKOIZKdX +boiIjL6NeJKBNjgSKBaet+jgI/ArU3F3Xr+1ouNj0+pWYfXoXGa3RVhPf3ci9IQI +VKLZrw2V2qlsMgaZKyNEizU0G+WVNC60lHQwaS8ClQ== +-----END CERTIFICATE----- diff --git a/src/python/pants/engine/internals/tls_testing/rsa/server.key b/src/python/pants/engine/internals/tls_testing/rsa/server.key new file mode 100644 index 00000000000..a4413ff1e3b --- /dev/null +++ b/src/python/pants/engine/internals/tls_testing/rsa/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCu2d+cu4rBVNSW +8pbWWgCbqO7G4w1GhzXGpI8uA1VOe651Z+rchyIxf4em+CYP8YWmTZ17Ity7ksYp +eRn2iU8rV/we4PTrlrUhJwm/f0vETyc5vCNvGtgR+XKFb0I001ZxrTtp81A9dwpv +/6l2Kj99JWPWDi6QCzkz9zm6bim6lzf1/uCQuv2ffBwIuxTJjVFer91wSAZvqaW8 +j7ofE2ok1O4MmySioW7/ya/UFTcowaQWWbsU7ZPJ2hRFzWJB+4ZvMvxB5+oMZ7FN +7rUDLBz78Wk9l82I30NbvLLo4TMTCEoVMLsLhYT6Uk6n/rLxME701h3nO20Mff4G +ZZs6j9zZAgMBAAECggEAOOsnxcevNZw90B2MSJ7nz8RAZnIHIkxljxKGoX3oSTIt +6n43kC5VTTOsnwfsrAf9b9OYTjtdC5qN83Y+o2izYvKzWgH0+LQIppAktjfkHDWA +GWVLS6G7cK55P0KmlLevTjYMP0M7fabAuRDKw3KhVEXVqBrZwX5F0srJ+gxefSSv +B/y5c2eeC1GMsDlZb3SC1RRtE43ged3s6z/QPDpYY10a4PvgJbSqyFikgmaIeZyn +SRDZOU4j8L7d/gOy5T4aNpZpcyP+mSsN13IOuTnvqDDfuytNuc39oLeLE6Q7pafm +MUDms3SO7yxu9SfP1fK8zz+GLPQsxPyAC3gyvBPJYQKBgQDXi9oacXY4MiH3pmKu +U9YYAw+fxVm/9fPnxas+itThsJOPIiz59RdF7UAHHjiX7T24QzQekrz7iyJ0ntUB +rjcu+Mr4AlbURJIgrSvG6T2DGqUwYKddJoXutzmOVoBIXiwieH/Ia7IFNRAILPzZ +caX5YphUepGplY55RGumfxH3VQKBgQDPqsbMskZ1L7oXTqpcnEh88dic/jJcXaZl +qDbUkVt+I0/dB95NN8P+JkvHWEzbIgO6/VwofoywWoXQ++5oGgfhz1cMdh+q8ixP +ZFtGeaIUtOqwqrJFLBcNMDjLsDfKZ6LGO7iao4fruO5M5KqsJHr3NNUPndVZeE/1 +jfeT3jyHdQKBgEk2I1qf9zuXK5pmVVZI+4skXwYxjg55h+LNbSDNHwmZZEVVolk0 +OjdyIjFIvog7+J7BlM5doPr/gzCBUnBDsFKwBqC+iqeGsuuOZjQlvNLvFfScn7wv +/YXbjh0enGv4MJ10uFJm4gyKvWtJfIiO5YUTTBJFn3wuZDokVzwyJPQRAoGAKW/l +LTEhd/6m/kGXItWYbT4AwArE18NkEYLINhAvFnNLW0pXpEbNV+giVMucwlj+L5Cg +k//0aLgXXYT3pFQVYIxzzSvA0+ZwywK2Z9Cbc9BJyCi3W7AZhWsq2hH2f++//hgq +lMiutW14N2WlXKYG1072eKBA0xJ7uf6y+RFuOIUCgYBLYbDML9g5iI7O9RwOsKqv +ACjT/xwLN9eSuuDwW/Q2/Ndj6nNjXFVIANn9YUoKvJsbEXgj42KsdkRSyQ7ezNKX +utfi6zcXa7Zdsbr/97Of+PXZArIW5qKDHeug+KP+DGyN+vFsmzQ+yb2w1YhBGVNY +Mqn6bD8H4lVZLY/2oabsDg== +-----END PRIVATE KEY----- diff --git a/src/python/pants/init/engine_initializer.py b/src/python/pants/init/engine_initializer.py index 21205a86736..70ab50de4fc 100644 --- a/src/python/pants/init/engine_initializer.py +++ b/src/python/pants/init/engine_initializer.py @@ -188,6 +188,7 @@ def setup_graph( local_store_dir=bootstrap_options.local_store_dir, local_execution_root_dir=bootstrap_options.local_execution_root_dir, named_caches_dir=bootstrap_options.named_caches_dir, + ca_certs_path=bootstrap_options.ca_certs_path, build_root=build_root, native=native, include_trace_on_error=bootstrap_options.print_exception_stacktrace, @@ -205,6 +206,7 @@ def setup_graph_extended( local_store_dir: str, local_execution_root_dir: str, named_caches_dir: str, + ca_certs_path: Optional[str] = None, build_root: Optional[str] = None, include_trace_on_error: bool = True, ) -> GraphScheduler: @@ -276,6 +278,11 @@ def build_root_singleton() -> BuildRoot: def ensure_absolute_path(v: str) -> str: return Path(v).resolve().as_posix() + def ensure_optional_absolute_path(v: Optional[str]) -> Optional[str]: + if v is None: + return None + return ensure_absolute_path(v) + scheduler = Scheduler( native=native, ignore_patterns=pants_ignore_patterns, @@ -284,6 +291,7 @@ def ensure_absolute_path(v: str) -> str: local_store_dir=ensure_absolute_path(local_store_dir), local_execution_root_dir=ensure_absolute_path(local_execution_root_dir), named_caches_dir=ensure_absolute_path(named_caches_dir), + ca_certs_path=ensure_optional_absolute_path(ca_certs_path), rules=rules, union_membership=union_membership, execution_options=execution_options, diff --git a/src/python/pants/option/global_options.py b/src/python/pants/option/global_options.py index 3a791bae61b..3a05dcb78cd 100644 --- a/src/python/pants/option/global_options.py +++ b/src/python/pants/option/global_options.py @@ -693,6 +693,14 @@ def register_bootstrap_options(cls, register): ), default=os.path.join(get_pants_cachedir(), "named_caches"), ) + register( + "--ca-certs-path", + advanced=True, + type=str, + default=None, + help="Path to a file containing PEM-format CA certificates used for verifying secure " + "connections when downloading files required by a build.", + ) register( "--remote-execution", advanced=True, diff --git a/src/python/pants/testutil/test_base.py b/src/python/pants/testutil/test_base.py index 611cbfc5986..a70919ee0fc 100644 --- a/src/python/pants/testutil/test_base.py +++ b/src/python/pants/testutil/test_base.py @@ -313,6 +313,7 @@ def _init_engine(self, local_store_dir: Optional[str] = None) -> None: local_store_dir=local_store_dir, local_execution_root_dir=local_execution_root_dir, named_caches_dir=named_caches_dir, + ca_certs_path=global_options.ca_certs_path, native=Native(), options_bootstrapper=options_bootstrapper, build_root=self.build_root, diff --git a/src/python/pants/util/contextutil.py b/src/python/pants/util/contextutil.py index 070e67b5890..4bf6ff8d783 100644 --- a/src/python/pants/util/contextutil.py +++ b/src/python/pants/util/contextutil.py @@ -4,6 +4,7 @@ import logging import os import shutil +import ssl import sys import tempfile import termios @@ -326,10 +327,13 @@ def maybe_profiled(profile_path: Optional[str]) -> Iterator[None]: @contextmanager -def http_server(handler_class: Type) -> Iterator[int]: +def http_server(handler_class: Type, ssl_context: Optional[ssl.SSLContext] = None) -> Iterator[int]: def serve(port_queue: "Queue[int]", shutdown_queue: "Queue[bool]") -> None: httpd = TCPServer(("", 0), handler_class) httpd.timeout = 0.1 + if ssl_context: + httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True) + port_queue.put(httpd.server_address[1]) while shutdown_queue.empty(): httpd.handle_request() diff --git a/src/rust/engine/Cargo.lock b/src/rust/engine/Cargo.lock index 4bbda56692d..8df2bea399d 100644 --- a/src/rust/engine/Cargo.lock +++ b/src/rust/engine/Cargo.lock @@ -649,6 +649,7 @@ dependencies = [ "parking_lot 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "process_execution 0.0.1", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", "rule_graph 0.0.1", "sharded_lmdb 0.0.1", diff --git a/src/rust/engine/Cargo.toml b/src/rust/engine/Cargo.toml index 2ae5d324c24..c25cb5c2939 100644 --- a/src/rust/engine/Cargo.toml +++ b/src/rust/engine/Cargo.toml @@ -116,6 +116,7 @@ num_enum = "0.4" parking_lot = "0.11" process_execution = { path = "process_execution" } rand = "0.6" +regex = "1" reqwest = { version = "0.10", default_features = false, features = ["stream", "rustls-tls"] } rule_graph = { path = "rule_graph" } sharded_lmdb = { path = "sharded_lmdb" } diff --git a/src/rust/engine/src/context.rs b/src/rust/engine/src/context.rs index 005f833e60d..1648803b1f6 100644 --- a/src/rust/engine/src/context.rs +++ b/src/rust/engine/src/context.rs @@ -4,6 +4,7 @@ use std::collections::{BTreeMap, HashSet}; use std::convert::{Into, TryInto}; use std::future::Future; +use std::io::Read; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -24,6 +25,7 @@ use process_execution::{ Platform, ProcessMetadata, }; use rand::seq::SliceRandom; +use regex::Regex; use rule_graph::RuleGraph; use sharded_lmdb::{ShardedLmdb, DEFAULT_LEASE_TIME}; use store::Store; @@ -33,6 +35,18 @@ use watch::{Invalidatable, InvalidationWatcher}; const GIGABYTES: usize = 1024 * 1024 * 1024; +// The reqwest crate has no support for ingesting multiple certificates in a single file, +// and requires single PEM blocks. There is a pem crate that can decode multiple certificates +// from a single buffer, but it is not suitable for our use because we don't want to decode +// the certificates, but rather pass them as source to reqwest. The pem also inappropriately +// squelches errors. +// +// Instead we make our own use of a copy of the regex used by the pem crate. Note: +// - The leading (?s) which sets the flag to allow . to match \n. +// - The use of ungreedy repetition via .*?, so we get shortest instead of longest matches. +const PEM_RE_STR: &str = + r"(?s)-----BEGIN (?P.*?)-----\s*(?P.*?)-----END (?P.*?)-----\s*"; + /// /// The core context shared (via Arc) between the Scheduler and the Context objects of /// all running Nodes. @@ -95,6 +109,7 @@ impl Core { local_store_dir: PathBuf, local_execution_root_dir: PathBuf, named_caches_dir: PathBuf, + ca_certs_path: Option, remoting_opts: RemotingOptions, exec_strategy_opts: ExecutionStrategyOptions, ) -> Result { @@ -249,7 +264,38 @@ impl Core { } let graph = Arc::new(InvalidatableGraph(Graph::new())); - let http_client = reqwest::Client::new(); + // These certs are for downloads, not to be confused with the ones used for remoting. + let ca_certs = if let Some(ref path) = ca_certs_path { + let mut buf = Vec::new(); + std::fs::File::open(path) + .and_then(|mut f| f.read_to_end(&mut buf)) + .map_err(|err| format!("Error reading root CA certs file {:?}: {}", path, err))?; + let content = std::str::from_utf8(&buf) + .map_err(|err| format!("Error decoding root CA certs file {:?}: {}", path, err))?; + + let pem_re = Regex::new(PEM_RE_STR).unwrap(); + let certs_res: Result, _> = pem_re + .find_iter(content) + .map(|mat| reqwest::Certificate::from_pem(mat.as_str().as_bytes())) + .collect(); + certs_res.map_err(|err| { + format!( + "Error parsing PEM from root CA certs file {:?}: {}", + path, err + ) + })? + } else { + Vec::new() + }; + + let http_client_builder = ca_certs + .iter() + .fold(reqwest::Client::builder(), |builder, cert| { + builder.add_root_certificate(cert.clone()) + }); + let http_client = http_client_builder + .build() + .map_err(|err| format!("Error building HTTP client: {}", err))?; let rule_graph = RuleGraph::new(tasks.rules().clone(), tasks.queries().clone())?; let gitignore_file = if use_gitignore { diff --git a/src/rust/engine/src/externs/interface.rs b/src/rust/engine/src/externs/interface.rs index a479a990860..9c8903fa795 100644 --- a/src/rust/engine/src/externs/interface.rs +++ b/src/rust/engine/src/externs/interface.rs @@ -340,6 +340,7 @@ py_module_initializer!(native_engine, |py, m| { local_store_dir_buf: String, local_execution_root_dir_buf: String, named_caches_dir_buf: String, + ca_certs_path: Option, ignore_patterns: Vec, use_gitignore: bool, remoting_options: PyRemotingOptions, @@ -733,6 +734,7 @@ fn scheduler_create( local_store_dir_buf: String, local_execution_root_dir_buf: String, named_caches_dir_buf: String, + ca_certs_path_buf: Option, ignore_patterns: Vec, use_gitignore: bool, remoting_options: PyRemotingOptions, @@ -763,6 +765,7 @@ fn scheduler_create( PathBuf::from(local_store_dir_buf), PathBuf::from(local_execution_root_dir_buf), PathBuf::from(named_caches_dir_buf), + ca_certs_path_buf.map(PathBuf::from), remoting_options.options(py).clone(), exec_strategy_opts.options(py).clone(), ) From 60e3c429c9c917f70a28e4368a648522d7f87a2b Mon Sep 17 00:00:00 2001 From: Benjy Weinberger Date: Thu, 10 Sep 2020 21:41:38 -0700 Subject: [PATCH 2/4] Simplify file reading # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/rust/engine/src/context.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/rust/engine/src/context.rs b/src/rust/engine/src/context.rs index 1648803b1f6..024da731bc6 100644 --- a/src/rust/engine/src/context.rs +++ b/src/rust/engine/src/context.rs @@ -266,16 +266,13 @@ impl Core { // These certs are for downloads, not to be confused with the ones used for remoting. let ca_certs = if let Some(ref path) = ca_certs_path { - let mut buf = Vec::new(); + let mut content = String::new(); std::fs::File::open(path) - .and_then(|mut f| f.read_to_end(&mut buf)) + .and_then(|mut f| f.read_to_string(&mut content)) .map_err(|err| format!("Error reading root CA certs file {:?}: {}", path, err))?; - let content = std::str::from_utf8(&buf) - .map_err(|err| format!("Error decoding root CA certs file {:?}: {}", path, err))?; - let pem_re = Regex::new(PEM_RE_STR).unwrap(); let certs_res: Result, _> = pem_re - .find_iter(content) + .find_iter(&content) .map(|mat| reqwest::Certificate::from_pem(mat.as_str().as_bytes())) .collect(); certs_res.map_err(|err| { From 790e5c428116e48647f5443b2e5e90e3207c22f6 Mon Sep 17 00:00:00 2001 From: Benjy Weinberger Date: Fri, 11 Sep 2020 13:56:55 -0700 Subject: [PATCH 3/4] Fix test and address code review comment # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/python/pants/engine/rules_test.py | 1 + src/rust/engine/src/context.rs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/python/pants/engine/rules_test.py b/src/python/pants/engine/rules_test.py index 41dcb718d78..290bd9c36ad 100644 --- a/src/python/pants/engine/rules_test.py +++ b/src/python/pants/engine/rules_test.py @@ -48,6 +48,7 @@ def create_scheduler(rules, validate=True, native=None): local_store_dir="./.pants.d/lmdb_store", local_execution_root_dir="./.pants.d", named_caches_dir="./.pants.d/named_caches", + ca_certs_path=None, rules=rules, union_membership=UnionMembership({}), execution_options=DEFAULT_EXECUTION_OPTIONS, diff --git a/src/rust/engine/src/context.rs b/src/rust/engine/src/context.rs index 024da731bc6..cad60db5212 100644 --- a/src/rust/engine/src/context.rs +++ b/src/rust/engine/src/context.rs @@ -36,10 +36,10 @@ use watch::{Invalidatable, InvalidationWatcher}; const GIGABYTES: usize = 1024 * 1024 * 1024; // The reqwest crate has no support for ingesting multiple certificates in a single file, -// and requires single PEM blocks. There is a pem crate that can decode multiple certificates -// from a single buffer, but it is not suitable for our use because we don't want to decode -// the certificates, but rather pass them as source to reqwest. The pem also inappropriately -// squelches errors. +// and requires single PEM blocks. There is a crate (https://crates.io/crates/pem) that can decode +// multiple certificates from a single buffer, but it is not suitable for our use because we don't +// want to decode the certificates, but rather pass them as source to reqwest. That crate also +// inappropriately squelches errors. // // Instead we make our own use of a copy of the regex used by the pem crate. Note: // - The leading (?s) which sets the flag to allow . to match \n. From f256ffc799ba7d1e5881e972219a2054b4a500f6 Mon Sep 17 00:00:00 2001 From: Benjy Weinberger Date: Fri, 11 Sep 2020 15:54:09 -0700 Subject: [PATCH 4/4] Use path.display() # Building wheels and fs_util will be skipped. Delete if not intended. [ci skip-build-wheels] --- src/rust/engine/src/context.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/rust/engine/src/context.rs b/src/rust/engine/src/context.rs index cad60db5212..cfb7e438776 100644 --- a/src/rust/engine/src/context.rs +++ b/src/rust/engine/src/context.rs @@ -269,7 +269,13 @@ impl Core { let mut content = String::new(); std::fs::File::open(path) .and_then(|mut f| f.read_to_string(&mut content)) - .map_err(|err| format!("Error reading root CA certs file {:?}: {}", path, err))?; + .map_err(|err| { + format!( + "Error reading root CA certs file {}: {}", + path.display(), + err + ) + })?; let pem_re = Regex::new(PEM_RE_STR).unwrap(); let certs_res: Result, _> = pem_re .find_iter(&content) @@ -277,8 +283,9 @@ impl Core { .collect(); certs_res.map_err(|err| { format!( - "Error parsing PEM from root CA certs file {:?}: {}", - path, err + "Error parsing PEM from root CA certs file {}: {}", + path.display(), + err ) })? } else {