Add opt --root-ceritificates & env BINSTALL_HTTPS_ROOT_CERTS (#820)

for specifying root ceritificates used for https connnections.

And remove old environment variable `CARGO_HTTP_CAINFO`, `SSL_CERT_FILE`
and `SSL_CERT_PATH` to avoid accidentally setting them, especially in CI
env.

Also:
 - Rm fn `binstalk_downloader::Certificate::from_env`
 - Enable feature `env` of dep `clap` in `crates/bin`
 - Add new dep `file-format` v0.14.0 to `crates/bin`
 - Use `file-format` to determine pem/der file format when loading root certs
 - Rm fn `binstalk_downloader::Certificate::open` and enum `binstalk_downloader::OpenCertificateError`

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2023-02-20 20:48:33 +11:00 committed by GitHub
parent 467ba0d854
commit 7bc4d4a5c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 62 deletions

7
Cargo.lock generated
View file

@ -361,6 +361,7 @@ dependencies = [
"crates_io_api",
"dirs",
"embed-resource",
"file-format",
"fs-lock",
"log",
"miette",
@ -727,6 +728,12 @@ dependencies = [
"instant",
]
[[package]]
name = "file-format"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d645737d3dda11cbf14905e9b943a1bd578cdcb751709b581a924ea9b9b18b5c"
[[package]]
name = "filetime"
version = "0.2.20"

View file

@ -24,9 +24,10 @@ pkg-fmt = "zip"
[dependencies]
binstalk = { path = "../binstalk", version = "0.8.0", default-features = false }
binstalk-manifests = { path = "../binstalk-manifests", version = "0.3.0" }
clap = { version = "4.1.6", features = ["derive"] }
clap = { version = "4.1.6", features = ["derive", "env"] }
crates_io_api = { version = "0.8.1", default-features = false }
dirs = "4.0.0"
file-format = { version = "0.14.0", default-features = false }
fs-lock = { version = "0.1.0", path = "../fs-lock" }
log = { version = "0.4.17", features = ["std"] }
miette = "5.5.0"

View file

@ -203,6 +203,11 @@ pub struct Args {
#[clap(help_heading = "Options", long, value_enum, value_name = "VERSION")]
pub min_tls_version: Option<TLSVersion>,
/// Specify the root certificates to use for https connnections,
/// in addition to default system-wide ones.
#[clap(help_heading = "Options", long, env = "BINSTALL_HTTPS_ROOT_CERTS")]
pub root_certificates: Vec<PathBuf>,
/// Print logs in json format to be parsable.
#[clap(help_heading = "Options", long)]
pub json_output: bool,
@ -313,7 +318,7 @@ pub fn parse() -> Args {
// Filter extraneous arg when invoked by cargo
// `cargo run -- --help` gives ["target/debug/cargo-binstall", "--help"]
// `cargo binstall --help` gives ["/home/ryan/.cargo/bin/cargo-binstall", "binstall", "--help"]
let mut args: Vec<OsString> = std::env::args_os().collect();
let mut args: Vec<OsString> = env::args_os().collect();
let args = if args.get(1).map(|arg| arg == "binstall").unwrap_or_default() {
// Equivalent to
//

View file

@ -1,4 +1,9 @@
use std::{fs, path::PathBuf, sync::Arc, time::Duration};
use std::{
fs,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use binstalk::{
errors::BinstallError,
@ -17,6 +22,7 @@ use binstalk::{
};
use binstalk_manifests::cargo_toml_binstall::PkgOverride;
use crates_io_api::AsyncClient as CratesIoApiClient;
use file_format::FileFormat;
use log::LevelFilter;
use miette::{miette, Result, WrapErr};
use tokio::task::block_in_place;
@ -77,15 +83,7 @@ pub async fn install_crates(args: Args, jobserver_client: LazyJobserverClient) -
args.min_tls_version.map(|v| v.into()),
Duration::from_millis(rate_limit.duration.get()),
rate_limit.request_count,
["CARGO_HTTP_CAINFO", "SSL_CERT_FILE", "SSL_CERT_PATH"]
.into_iter()
.filter_map(|env_name| match Certificate::from_env(env_name) {
Ok(option) => option,
Err(err) => {
warn!("Failed to load root certificate specified by env {env_name}: {err}",);
None
}
}),
read_root_certs(args.root_certificates),
)
.map_err(BinstallError::from)?;
@ -180,6 +178,49 @@ pub async fn install_crates(args: Args, jobserver_client: LazyJobserverClient) -
Ok(())
}
fn do_read_root_cert(path: &Path) -> Result<Option<Certificate>, BinstallError> {
use std::io::{Read, Seek};
let mut file = fs::File::open(path)?;
let file_format = FileFormat::from_reader(&mut file)?;
let open_cert = match file_format {
FileFormat::PemCertificate => Certificate::from_pem,
FileFormat::DerCertificate => Certificate::from_der,
_ => {
warn!(
"Unable to load {}: Expected pem or der ceritificate but found {file_format}",
path.display()
);
return Ok(None);
}
};
// Move file back to its head
file.rewind()?;
let mut buffer = Vec::with_capacity(200);
file.read_to_end(&mut buffer)?;
open_cert(&buffer).map_err(From::from).map(Some)
}
fn read_root_certs(root_certificate_paths: Vec<PathBuf>) -> impl Iterator<Item = Certificate> {
root_certificate_paths
.into_iter()
.filter_map(|path| match do_read_root_cert(&path) {
Ok(optional_cert) => optional_cert,
Err(err) => {
warn!(
"Failed to load root certificate at {}: {err}",
path.display()
);
None
}
})
}
/// Return (install_path, manifests, temp_dir)
fn compute_paths_and_load_manifests(
roots: Option<PathBuf>,

View file

@ -23,7 +23,7 @@ mod delay_request;
use delay_request::DelayRequest;
mod certificate;
pub use certificate::{Certificate, OpenCertificateError};
pub use certificate::Certificate;
const MAX_RETRY_DURATION: Duration = Duration::from_secs(120);
const MAX_RETRY_COUNT: u8 = 3;

View file

@ -1,60 +1,11 @@
use std::{env, ffi::OsStr, fs, io, path::Path};
use compact_str::CompactString;
use reqwest::tls;
use thiserror::Error as ThisError;
use super::ReqwestError;
#[derive(Debug, ThisError)]
pub enum OpenCertificateError {
#[error(transparent)]
Reqwest(#[from] ReqwestError),
#[error(transparent)]
Io(#[from] io::Error),
#[error("Expected extension .pem or .der, but found {0:#?}")]
UnknownExtensions(Option<CompactString>),
}
#[derive(Clone, Debug)]
pub struct Certificate(pub(super) tls::Certificate);
impl Certificate {
/// Open Certificate with path specified by the environment variable `name`
pub fn from_env(name: impl AsRef<OsStr>) -> Result<Option<Self>, OpenCertificateError> {
Self::from_env_inner(name.as_ref())
}
fn from_env_inner(name: &OsStr) -> Result<Option<Self>, OpenCertificateError> {
env::var_os(name)
.map(|value| Self::open_inner(Path::new(&value)))
.transpose()
}
/// Open Certificate on disk and automatically detect its format based on
/// its extension.
pub fn open(path: impl AsRef<Path>) -> Result<Self, OpenCertificateError> {
Self::open_inner(path.as_ref())
}
fn open_inner(path: &Path) -> Result<Self, OpenCertificateError> {
let ext = path.extension();
let f = if ext == Some(OsStr::new("pem")) {
Self::from_pem
} else if ext == Some(OsStr::new("der")) {
Self::from_der
} else {
return Err(OpenCertificateError::UnknownExtensions(
ext.map(|os_str| os_str.to_string_lossy().into()),
));
};
Ok(f(fs::read(path)?)?)
}
/// Create a Certificate from a binary DER encoded certificate
pub fn from_der(der: impl AsRef<[u8]>) -> Result<Self, ReqwestError> {
tls::Certificate::from_der(der.as_ref()).map(Self)