From c1287001307f170a0f71f2d52f65a0f8b2972c8a Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Thu, 16 Mar 2023 09:32:14 -0700 Subject: [PATCH] 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 --- README.md | 1 + src/action/base/fetch_and_unpack_nix.rs | 50 ++++++++++++++++++-- src/action/common/configure_nix.rs | 10 ++-- src/action/common/configure_shell_profile.rs | 14 +++++- src/action/common/provision_nix.rs | 1 + src/action/mod.rs | 2 + src/cli/subcommand/install.rs | 14 +++++- src/settings.rs | 9 +++- 8 files changed, 90 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 69064c0..b337f7d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/action/base/fetch_and_unpack_nix.rs b/src/action/base/fetch_and_unpack_nix.rs index 1446157..28ffe69 100644 --- a/src/action/base/fetch_and_unpack_nix.rs +++ b/src/action/base/fetch_and_unpack_nix.rs @@ -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, + ssl_cert_file: Option, } impl FetchAndUnpackNix { @@ -22,6 +23,7 @@ impl FetchAndUnpackNix { url: Url, dest: PathBuf, proxy: Option, + ssl_cert_file: Option, ) -> Result, 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 { + 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, } diff --git a/src/action/common/configure_nix.rs b/src/action/common/configure_nix.rs index 029c689..fc35b4e 100644 --- a/src/action/common/configure_nix.rs +++ b/src/action/common/configure_nix.rs @@ -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 }; diff --git a/src/action/common/configure_shell_profile.rs b/src/action/common/configure_shell_profile.rs index 59a6dcc..a0783d3 100644 --- a/src/action/common/configure_shell_profile.rs +++ b/src/action/common/configure_shell_profile.rs @@ -52,13 +52,24 @@ pub struct ConfigureShellProfile { impl ConfigureShellProfile { #[tracing::instrument(level = "debug", skip_all)] - pub async fn plan() -> Result, ActionError> { + pub async fn plan(ssl_cert_file: Option) -> Result, 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\ diff --git a/src/action/common/provision_nix.rs b/src/action/common/provision_nix.rs index 6ea79e8..6239a44 100644 --- a/src/action/common/provision_nix.rs +++ b/src/action/common/provision_nix.rs @@ -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()) diff --git a/src/action/mod.rs b/src/action/mod.rs index 428aeb4..f3b3798 100644 --- a/src/action/mod.rs +++ b/src/action/mod.rs @@ -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}`")] diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index 95955d7..354b37c 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -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() + } ); }, } diff --git a/src/settings.rs b/src/settings.rs index 7a9bace..155f875 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -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, + /// 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, + /// 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, @@ -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)?);