Add ssl-cert-file option (#341)

* Add ssl-cert-file option

* Add reqwest support for ssl cert

* Fix build

* Include in install differences

* Handle weird paths, include ENV setting in instructions
This commit is contained in:
Ana Hobden 2023-03-16 09:32:14 -07:00 committed by GitHub
parent df0d8eb01f
commit c128700130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 90 additions and 11 deletions

View file

@ -35,6 +35,7 @@ Differing from the current official [Nix](https://github.com/NixOS/nix) installe
* `extra-nix-path` is set to `nixpkgs=flake:nixpkgs`
* an installation receipt (for uninstalling) is stored at `/nix/receipt.json` as well as a copy of the install binary at `/nix/nix-installer`
* `nix-channel --update` is not run, `~/.nix-channels` is not provisioned
* `NIX_SSL_CERT_FILE` is set in the various shell profiles if the `ssl-cert-file` argument is used.
## Motivations

View file

@ -1,7 +1,7 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use bytes::{Buf, Bytes};
use reqwest::Url;
use reqwest::{Certificate, Url};
use tracing::{span, Span};
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
@ -14,6 +14,7 @@ pub struct FetchAndUnpackNix {
url: Url,
dest: PathBuf,
proxy: Option<Url>,
ssl_cert_file: Option<PathBuf>,
}
impl FetchAndUnpackNix {
@ -22,6 +23,7 @@ impl FetchAndUnpackNix {
url: Url,
dest: PathBuf,
proxy: Option<Url>,
ssl_cert_file: Option<PathBuf>,
) -> Result<StatefulAction<Self>, ActionError> {
// TODO(@hoverbear): Check URL exists?
// TODO(@hoverbear): Check tempdir exists
@ -46,7 +48,17 @@ impl FetchAndUnpackNix {
};
}
Ok(Self { url, dest, proxy }.into())
if let Some(ssl_cert_file) = &ssl_cert_file {
parse_ssl_cert(&ssl_cert_file).await?;
}
Ok(Self {
url,
dest,
proxy,
ssl_cert_file,
}
.into())
}
}
@ -66,11 +78,18 @@ impl Action for FetchAndUnpackNix {
"fetch_and_unpack_nix",
url = tracing::field::display(&self.url),
proxy = tracing::field::Empty,
ssl_cert_file = tracing::field::Empty,
dest = tracing::field::display(self.dest.display()),
);
if let Some(proxy) = &self.proxy {
span.record("proxy", tracing::field::display(&proxy));
}
if let Some(ssl_cert_file) = &self.ssl_cert_file {
span.record(
"ssl_cert_file",
tracing::field::display(&ssl_cert_file.display()),
);
}
span
}
@ -89,6 +108,10 @@ impl Action for FetchAndUnpackNix {
ActionError::Custom(Box::new(FetchUrlError::Reqwest(e)))
})?)
}
if let Some(ssl_cert_file) = &self.ssl_cert_file {
let ssl_cert = parse_ssl_cert(&ssl_cert_file).await?;
buildable_client = buildable_client.add_root_certificate(ssl_cert);
}
let client = buildable_client
.build()
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
@ -140,6 +163,23 @@ impl Action for FetchAndUnpackNix {
}
}
async fn parse_ssl_cert(ssl_cert_file: &Path) -> Result<Certificate, ActionError> {
let cert_buf = tokio::fs::read(ssl_cert_file)
.await
.map_err(|e| ActionError::Read(ssl_cert_file.to_path_buf(), e))?;
// We actually try them since things could be `.crt` and `pem` format or `der` format
let cert = if let Ok(cert) = Certificate::from_pem(cert_buf.as_slice()) {
cert
} else if let Ok(cert) = Certificate::from_der(cert_buf.as_slice()) {
cert
} else {
return Err(ActionError::Custom(Box::new(
FetchUrlError::UnknownCertFormat,
)));
};
Ok(cert)
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum FetchUrlError {
@ -153,6 +193,8 @@ pub enum FetchUrlError {
Unarchive(#[source] std::io::Error),
#[error("Unknown url scheme, `file://`, `https://` and `http://` supported")]
UnknownUrlScheme,
#[error("Unknown url scheme, `https://`, `socks5://`, and `http://` supported")]
#[error("Unknown proxy scheme, `https://`, `socks5://`, and `http://` supported")]
UnknownProxyScheme,
#[error("Unknown certificate format, `der` and `pem` supported")]
UnknownCertFormat,
}

View file

@ -29,9 +29,13 @@ impl ConfigureNix {
.map_err(|e| ActionError::Child(SetupDefaultProfile::action_tag(), Box::new(e)))?;
let configure_shell_profile = if settings.modify_profile {
Some(ConfigureShellProfile::plan().await.map_err(|e| {
ActionError::Child(ConfigureShellProfile::action_tag(), Box::new(e))
})?)
Some(
ConfigureShellProfile::plan(settings.ssl_cert_file.clone())
.await
.map_err(|e| {
ActionError::Child(ConfigureShellProfile::action_tag(), Box::new(e))
})?,
)
} else {
None
};

View file

@ -52,13 +52,24 @@ pub struct ConfigureShellProfile {
impl ConfigureShellProfile {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
pub async fn plan(ssl_cert_file: Option<PathBuf>) -> Result<StatefulAction<Self>, ActionError> {
let mut create_or_insert_files = Vec::default();
let mut create_directories = Vec::default();
let maybe_ssl_cert_file_setting = if let Some(ssl_cert_file) = ssl_cert_file {
format!(
"export NIX_SSL_CERT_FILE={:?}\n",
ssl_cert_file
.canonicalize()
.map_err(|e| { ActionError::Canonicalize(ssl_cert_file, e) })?
)
} else {
"".to_string()
};
let shell_buf = format!(
"\n\
# Nix\n\
{maybe_ssl_cert_file_setting}\
if [ -e '{PROFILE_NIX_FILE_SHELL}' ]; then\n\
{inde}. '{PROFILE_NIX_FILE_SHELL}'\n\
fi\n\
@ -103,6 +114,7 @@ impl ConfigureShellProfile {
let fish_buf = format!(
"\n\
# Nix\n\
{maybe_ssl_cert_file_setting}\
if test -e '{PROFILE_NIX_FILE_FISH}'\n\
{inde}. '{PROFILE_NIX_FILE_FISH}'\n\
end\n\

View file

@ -28,6 +28,7 @@ impl ProvisionNix {
settings.nix_package_url.clone(),
PathBuf::from(SCRATCH_DIR),
settings.proxy.clone(),
settings.ssl_cert_file.clone(),
)
.await?;
let create_users_and_group = CreateUsersAndGroups::plan(settings.clone())

View file

@ -369,6 +369,8 @@ pub enum ActionError {
std::path::PathBuf,
#[source] std::io::Error,
),
#[error("Canonicalizing `{0}`")]
Canonicalize(std::path::PathBuf, #[source] std::io::Error),
#[error("Read path `{0}`")]
Read(std::path::PathBuf, #[source] std::io::Error),
#[error("Reading directory `{0}`")]

View file

@ -114,7 +114,7 @@ impl CommandExecute for Install {
serde_json::from_str(&install_plan_string)?
},
(None, None) => {
let builtin_planner = BuiltinPlanner::from_common_settings(settings)
let builtin_planner = BuiltinPlanner::from_common_settings(settings.clone())
.await
.map_err(|e| eyre::eyre!(e))?;
@ -249,7 +249,7 @@ impl CommandExecute for Install {
println!(
"\
{success}\n\
To get started using Nix, open a new shell or run `{shell_reminder}`\n\
To get started using Nix, open a new shell or run `{maybe_ssl_cert_file_reminder}{shell_reminder}`\n\
",
success = "Nix was installed successfully!".green().bold(),
shell_reminder = match std::env::var("SHELL") {
@ -258,6 +258,16 @@ impl CommandExecute for Install {
Ok(_) | Err(_) =>
". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh".bold(),
},
maybe_ssl_cert_file_reminder = if let Some(ssl_cert_file) = &settings.ssl_cert_file {
format!(
"export NIX_SSL_CERT_FILE={:?}; ",
ssl_cert_file
.canonicalize()
.map_err(|e| { eyre!(e).wrap_err(format!("Could not canonicalize {}", ssl_cert_file.display())) })?
)
} else {
"".to_string()
}
);
},
}

View file

@ -1,6 +1,6 @@
/*! Configurable knobs and their related errors
*/
use std::collections::HashMap;
use std::{collections::HashMap, path::PathBuf};
#[cfg(feature = "cli")]
use clap::ArgAction;
@ -176,6 +176,10 @@ pub struct CommonSettings {
#[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_PROXY"))]
pub(crate) proxy: Option<Url>,
/// An SSL cert to use (if any), used for fetching Nix and sets `NIX_SSL_CERT_FILE` for Nix
#[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_SSL_CERT_FILE"))]
pub(crate) ssl_cert_file: Option<PathBuf>,
/// Extra configuration lines for `/etc/nix.conf`
#[cfg_attr(feature = "cli", clap(long, action = ArgAction::Set, num_args = 0.., value_delimiter = ',', env = "NIX_INSTALLER_EXTRA_CONF", global = true))]
pub extra_conf: Vec<String>,
@ -279,6 +283,7 @@ impl CommonSettings {
proxy: Default::default(),
extra_conf: Default::default(),
force: false,
ssl_cert_file: Default::default(),
#[cfg(feature = "diagnostics")]
diagnostic_endpoint: Some(
"https://install.determinate.systems/nix/diagnostic".try_into()?,
@ -299,6 +304,7 @@ impl CommonSettings {
proxy,
extra_conf,
force,
ssl_cert_file,
#[cfg(feature = "diagnostics")]
diagnostic_endpoint,
} = self;
@ -333,6 +339,7 @@ impl CommonSettings {
serde_json::to_value(nix_package_url)?,
);
map.insert("proxy".into(), serde_json::to_value(proxy)?);
map.insert("ssl_cert_file".into(), serde_json::to_value(ssl_cert_file)?);
map.insert("extra_conf".into(), serde_json::to_value(extra_conf)?);
map.insert("force".into(), serde_json::to_value(force)?);