From 531e51628f42949161db7bc7b4d1630440f38039 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Thu, 26 Sep 2024 14:01:01 -0400 Subject: [PATCH 1/6] Simplify the dnixd/upstream split --- src/cli/cmd/login/mod.rs | 102 ++++++++++++++++++--------------------- src/shared/mod.rs | 1 - 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/src/cli/cmd/login/mod.rs b/src/cli/cmd/login/mod.rs index 8ded267..f1f4655 100644 --- a/src/cli/cmd/login/mod.rs +++ b/src/cli/cmd/login/mod.rs @@ -90,7 +90,7 @@ impl LoginSubcommand { let dnixd_uds = match dnixd_uds().await { Ok(socket) => Some(socket), Err(err) => { - tracing::error!( + tracing::debug!( "failed to connect to determinate-nixd socket, will not attempt to use it: {:?}", err ); @@ -98,16 +98,6 @@ impl LoginSubcommand { } }; - let xdg = xdg::BaseDirectories::new()?; - // $XDG_CONFIG_HOME/nix/nix.conf; basically ~/.config/nix/nix.conf - let nix_config_path = xdg.place_config_file("nix/nix.conf")?; - // $XDG_CONFIG_HOME/fh/auth; basically ~/.config/fh/auth - let token_path = user_auth_token_write_path()?; - - let dnixd_state_dir = Path::new(&DETERMINATE_STATE_DIR); - let netrc_file_path: PathBuf = dnixd_state_dir.join(DETERMINATE_NIXD_NETRC_NAME); - let netrc_file_string: String = netrc_file_path.display().to_string(); - let mut login_url = self.frontend_addr.clone(); login_url.set_path("token/create"); login_url.query_pairs_mut().append_pair( @@ -135,42 +125,6 @@ impl LoginSubcommand { } }; - // Note the root version uses extra-trusted-substituters, which - // mean the cache is not enabled until a user (trusted or untrusted) - // adds it to extra-substituters in their nix.conf. - // - // Note the root version sets netrc-file until the user authentication - // patches (https://github.com/NixOS/nix/pull/9857) land. - let root_nix_config_addition = format!( - "\n\ - netrc-file = {netrc}\n\ - extra-substituters = {cache_addr}\n\ - extra-trusted-public-keys = {keys}\n\ - ", - netrc = netrc_file_string, - cache_addr = self.cache_addr, - keys = CACHE_PUBLIC_KEYS.join(" "), - ); - - let user_nix_config_addition = format!( - "\n\ - netrc-file = {netrc}\n\ - extra-substituters = {cache_addr}\n\ - extra-trusted-public-keys = {keys}\n\ - ", - netrc = netrc_file_string, - cache_addr = self.cache_addr, - keys = CACHE_PUBLIC_KEYS.join(" "), - ); - let netrc_contents = crate::shared::netrc_contents( - &self.frontend_addr, - &self.api_addr, - &self.cache_addr, - &token, - )?; - - tokio::fs::write(token_path, &token).await?; - // NOTE: Keep an eye on any movement in the following issues / PRs. Them being resolved // means we may be able to ditch setting `netrc-file` in favor of `access-tokens`. (The // benefit is that `access-tokens` can be appended to, but `netrc-file` is a one-time thing @@ -179,13 +133,11 @@ impl LoginSubcommand { // https://github.com/NixOS/nix/issues/8635 ("Credentials provider support for builtins.fetch*") // https://github.com/NixOS/nix/issues/8439 ("--access-tokens option does nothing") - let mut token_updated = false; if let Some(mut uds) = dnixd_uds { tracing::debug!("trying to update netrc via determinatenixd"); let add_req = NetrcTokenAddRequest { token: token.clone(), - netrc_lines: netrc_contents.clone(), }; let add_req_json = serde_json::to_string(&add_req)?; let request = http::request::Builder::new() @@ -200,14 +152,56 @@ impl LoginSubcommand { let text: String = String::from_utf8_lossy(&bytes).into(); tracing::trace!("sent the add request: {:?}", text); + } else { + tracing::debug!( + "failed to update netrc via determinatenixd, falling back to local-file approach" + ); - token_updated = true; - } + // $XDG_CONFIG_HOME/fh/auth; basically ~/.config/fh/auth + tokio::fs::write(user_auth_token_write_path()?, &token).await?; + + let dnixd_state_dir = Path::new(&DETERMINATE_STATE_DIR); + let netrc_file_path: PathBuf = dnixd_state_dir.join(DETERMINATE_NIXD_NETRC_NAME); + let netrc_file_string: String = netrc_file_path.display().to_string(); + + let xdg = xdg::BaseDirectories::new()?; + + // $XDG_CONFIG_HOME/nix/nix.conf; basically ~/.config/nix/nix.conf + let nix_config_path = xdg.place_config_file("nix/nix.conf")?; + + // Note the root version uses extra-trusted-substituters, which + // mean the cache is not enabled until a user (trusted or untrusted) + // adds it to extra-substituters in their nix.conf. + // + // Note the root version sets netrc-file until the user authentication + // patches (https://github.com/NixOS/nix/pull/9857) land. + let root_nix_config_addition = format!( + "\n\ + netrc-file = {netrc}\n\ + extra-substituters = {cache_addr}\n\ + extra-trusted-public-keys = {keys}\n\ + ", + netrc = netrc_file_string, + cache_addr = self.cache_addr, + keys = CACHE_PUBLIC_KEYS.join(" "), + ); - if !token_updated { - tracing::warn!( - "failed to update netrc via determinatenixd, falling back to local-file approach" + let user_nix_config_addition = format!( + "\n\ + netrc-file = {netrc}\n\ + extra-substituters = {cache_addr}\n\ + extra-trusted-public-keys = {keys}\n\ + ", + netrc = netrc_file_string, + cache_addr = self.cache_addr, + keys = CACHE_PUBLIC_KEYS.join(" "), ); + let netrc_contents = crate::shared::netrc_contents( + &self.frontend_addr, + &self.api_addr, + &self.cache_addr, + &token, + )?; update_netrc_file(&netrc_file_path, &netrc_contents).await?; diff --git a/src/shared/mod.rs b/src/shared/mod.rs index cd5b932..cd298f1 100644 --- a/src/shared/mod.rs +++ b/src/shared/mod.rs @@ -11,7 +11,6 @@ pub struct DaemonInfoReponse { #[derive(Deserialize, Serialize)] pub struct NetrcTokenAddRequest { pub token: String, - pub netrc_lines: String, } pub async fn update_netrc_file( From 9872fb72181a1ee27cb7aa1f10b2c597746ccc62 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Thu, 26 Sep 2024 14:04:00 -0400 Subject: [PATCH 2/6] If dnixd isn't a thing, don't try to write to its files --- src/cli/cmd/login/mod.rs | 22 ++++++++++------------ src/main.rs | 1 - 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/cli/cmd/login/mod.rs b/src/cli/cmd/login/mod.rs index f1f4655..3f20f03 100644 --- a/src/cli/cmd/login/mod.rs +++ b/src/cli/cmd/login/mod.rs @@ -14,7 +14,7 @@ use tokio::net::UnixStream; use crate::cli::cmd::FlakeHubClient; use crate::cli::error::FhError; use crate::shared::{update_netrc_file, NetrcTokenAddRequest}; -use crate::{DETERMINATE_NIXD_NETRC_NAME, DETERMINATE_NIXD_SOCKET_NAME, DETERMINATE_STATE_DIR}; +use crate::{DETERMINATE_NIXD_SOCKET_NAME, DETERMINATE_STATE_DIR}; use super::CommandExecute; @@ -160,12 +160,10 @@ impl LoginSubcommand { // $XDG_CONFIG_HOME/fh/auth; basically ~/.config/fh/auth tokio::fs::write(user_auth_token_write_path()?, &token).await?; - let dnixd_state_dir = Path::new(&DETERMINATE_STATE_DIR); - let netrc_file_path: PathBuf = dnixd_state_dir.join(DETERMINATE_NIXD_NETRC_NAME); - let netrc_file_string: String = netrc_file_path.display().to_string(); - let xdg = xdg::BaseDirectories::new()?; + let netrc_path = xdg.place_config_file("nix/netrc")?; + // $XDG_CONFIG_HOME/nix/nix.conf; basically ~/.config/nix/nix.conf let nix_config_path = xdg.place_config_file("nix/nix.conf")?; @@ -178,10 +176,10 @@ impl LoginSubcommand { let root_nix_config_addition = format!( "\n\ netrc-file = {netrc}\n\ - extra-substituters = {cache_addr}\n\ + extra-trusted-substituters = {cache_addr}\n\ extra-trusted-public-keys = {keys}\n\ ", - netrc = netrc_file_string, + netrc = netrc_path.display(), cache_addr = self.cache_addr, keys = CACHE_PUBLIC_KEYS.join(" "), ); @@ -192,7 +190,7 @@ impl LoginSubcommand { extra-substituters = {cache_addr}\n\ extra-trusted-public-keys = {keys}\n\ ", - netrc = netrc_file_string, + netrc = netrc_path.display(), cache_addr = self.cache_addr, keys = CACHE_PUBLIC_KEYS.join(" "), ); @@ -203,12 +201,12 @@ impl LoginSubcommand { &token, )?; - update_netrc_file(&netrc_file_path, &netrc_contents).await?; + update_netrc_file(&netrc_path, &netrc_contents).await?; // only update user_nix_config if we could not use determinatenixd upsert_user_nix_config( &nix_config_path, - &netrc_file_string, + &netrc_path, &netrc_contents, &user_nix_config_addition, &self.cache_addr, @@ -272,7 +270,7 @@ impl LoginSubcommand { // on that, then move it back if all is good pub async fn upsert_user_nix_config( nix_config_path: &Path, - netrc_file_string: &str, + netrc_path: &Path, netrc_contents: &str, user_nix_config_addition: &str, cache_addr: &url::Url, @@ -281,7 +279,7 @@ pub async fn upsert_user_nix_config( let mut merged_nix_config = nix_config_parser::NixConfig::new(); merged_nix_config .settings_mut() - .insert("netrc-file".to_string(), netrc_file_string.to_string()); + .insert("netrc-file".to_string(), netrc_path.display().to_string()); let setting = "extra-trusted-public-keys".to_string(); if let Some(existing) = nix_config.settings().get(&setting) { diff --git a/src/main.rs b/src/main.rs index 910f87d..381fd87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,6 @@ use crate::cli::{ const DETERMINATE_STATE_DIR: &str = "/nix/var/determinate"; const DETERMINATE_NIXD_SOCKET_NAME: &str = "determinate-nixd.socket"; -const DETERMINATE_NIXD_NETRC_NAME: &str = "netrc"; const DETERMINATE_NIXD_TOKEN_NAME: &str = "token"; static APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); From 5cdaca9c2f0c01d8c333a5c4f8a081323c9b1d9a Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Thu, 26 Sep 2024 14:18:42 -0400 Subject: [PATCH 3/6] Support reading the token from a file --- src/cli/cmd/login/mod.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/cli/cmd/login/mod.rs b/src/cli/cmd/login/mod.rs index 3f20f03..e6be5f4 100644 --- a/src/cli/cmd/login/mod.rs +++ b/src/cli/cmd/login/mod.rs @@ -4,6 +4,7 @@ use std::process::ExitCode; use axum::body::Body; use clap::Parser; use color_eyre::eyre::eyre; +use color_eyre::eyre::WrapErr; use http_body_util::BodyExt as _; use hyper::client::conn::http1::SendRequest; use hyper::{Method, StatusCode}; @@ -12,6 +13,7 @@ use tokio::io::AsyncWriteExt; use tokio::net::UnixStream; use crate::cli::cmd::FlakeHubClient; +use crate::cli::cmd::TokenStatus; use crate::cli::error::FhError; use crate::shared::{update_netrc_file, NetrcTokenAddRequest}; use crate::{DETERMINATE_NIXD_SOCKET_NAME, DETERMINATE_STATE_DIR}; @@ -32,6 +34,10 @@ const CACHE_PUBLIC_KEYS: &[&str] = &[ /// Log in to FlakeHub in order to allow authenticated fetching of flakes. #[derive(Debug, Parser)] pub(crate) struct LoginSubcommand { + /// Read the FlakeHub token from a file. + #[clap(long)] + token_file: Option, + /// Skip following up a successful login with `fh status`. #[clap(long)] skip_status: bool, @@ -108,12 +114,20 @@ impl LoginSubcommand { ), ); - println!("Log in to FlakeHub: {}", login_url); - println!("And then follow the prompts below:"); - println!(); + let mut token: Option = if let Some(ref token_file) = self.token_file { + Some( + tokio::fs::read_to_string(token_file) + .await + .wrap_err("Reading the provided token file")?, + ) + } else { + println!("Log in to FlakeHub: {}", login_url); + println!("And then follow the prompts below:"); + println!(); + crate::cli::cmd::init::prompt::Prompt::maybe_token("Paste your token here:") + }; - let token = crate::cli::cmd::init::prompt::Prompt::maybe_token("Paste your token here:"); - let (token, status) = match token { + let (token, status): (String, TokenStatus) = match token.take() { Some(token) => { // This serves as validating that provided token is actually a JWT, and is valid. let status = FlakeHubClient::auth_status(self.api_addr.as_ref(), &token).await?; From e0970b3a4c6f9e313e21c0107e395265b53cb912 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Thu, 26 Sep 2024 14:21:57 -0400 Subject: [PATCH 4/6] Strip whitespace from the token --- src/cli/cmd/login/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cli/cmd/login/mod.rs b/src/cli/cmd/login/mod.rs index e6be5f4..a0eb35f 100644 --- a/src/cli/cmd/login/mod.rs +++ b/src/cli/cmd/login/mod.rs @@ -118,7 +118,9 @@ impl LoginSubcommand { Some( tokio::fs::read_to_string(token_file) .await - .wrap_err("Reading the provided token file")?, + .wrap_err("Reading the provided token file")? + .trim() + .to_string(), ) } else { println!("Log in to FlakeHub: {}", login_url); From 3471ce136f212cc0fba362e01b0acde5e674f022 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Thu, 26 Sep 2024 14:36:47 -0400 Subject: [PATCH 5/6] Contextualize way more errors --- src/cli/cmd/login/mod.rs | 41 ++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/cli/cmd/login/mod.rs b/src/cli/cmd/login/mod.rs index a0eb35f..82da767 100644 --- a/src/cli/cmd/login/mod.rs +++ b/src/cli/cmd/login/mod.rs @@ -65,9 +65,14 @@ pub async fn dnixd_uds() -> color_eyre::Result> { let dnixd_state_dir = Path::new(&DETERMINATE_STATE_DIR); let dnixd_uds_socket_path: PathBuf = dnixd_state_dir.join(DETERMINATE_NIXD_SOCKET_NAME); - let stream = TokioIo::new(UnixStream::connect(dnixd_uds_socket_path).await?); - let (mut sender, conn): (SendRequest, _) = - hyper::client::conn::http1::handshake(stream).await?; + let stream = TokioIo::new( + UnixStream::connect(dnixd_uds_socket_path) + .await + .wrap_err("Connecting to the determinate-nixd socket")?, + ); + let (mut sender, conn): (SendRequest, _) = hyper::client::conn::http1::handshake(stream) + .await + .wrap_err("Çompleting the http1 handshake with determinate-nixd")?; // NOTE(colemickens): for now we just drop the joinhandle and let it keep running let _join_handle = tokio::task::spawn(async move { @@ -81,7 +86,10 @@ pub async fn dnixd_uds() -> color_eyre::Result> { .uri("http://localhost/info") .body(axum::body::Body::empty())?; - let response = sender.send_request(request).await?; + let response = sender + .send_request(request) + .await + .wrap_err("Querying information about determinate-nixd")?; if response.status() != StatusCode::OK { tracing::error!("failed to connect to determinate-nixd socket"); @@ -132,7 +140,9 @@ impl LoginSubcommand { let (token, status): (String, TokenStatus) = match token.take() { Some(token) => { // This serves as validating that provided token is actually a JWT, and is valid. - let status = FlakeHubClient::auth_status(self.api_addr.as_ref(), &token).await?; + let status = FlakeHubClient::auth_status(self.api_addr.as_ref(), &token) + .await + .wrap_err("Checking the validity of the provided token")?; (token, status) } None => { @@ -161,7 +171,10 @@ impl LoginSubcommand { .method(Method::POST) .header("Content-Type", "application/json") .body(Body::from(add_req_json))?; - let response = uds.send_request(request).await?; + let response = uds + .send_request(request) + .await + .wrap_err("Performing the enrollment request with determinate-nixd")?; let body = response.into_body(); let bytes = body.collect().await.unwrap_or_default().to_bytes(); @@ -217,7 +230,9 @@ impl LoginSubcommand { &token, )?; - update_netrc_file(&netrc_path, &netrc_contents).await?; + update_netrc_file(&netrc_path, &netrc_contents) + .await + .wrap_err("Writing out the netrc")?; // only update user_nix_config if we could not use determinatenixd upsert_user_nix_config( @@ -230,9 +245,11 @@ impl LoginSubcommand { .await?; let added_nix_config = - nix_config_parser::NixConfig::parse_string(root_nix_config_addition.clone(), None)?; + nix_config_parser::NixConfig::parse_string(root_nix_config_addition.clone(), None) + .wrap_err("Parsing the Nix configuration additions")?; let root_nix_config_path = PathBuf::from("/etc/nix/nix.conf"); - let root_nix_config = nix_config_parser::NixConfig::parse_file(&root_nix_config_path)?; + let root_nix_config = nix_config_parser::NixConfig::parse_file(&root_nix_config_path) + .wrap_err("Parsing the existing global Nix configuration")?; let mut root_meaningfully_different = false; for (merged_setting_name, merged_setting_value) in added_nix_config.settings() { @@ -374,7 +391,11 @@ pub async fn upsert_user_nix_config( if were_meaningfully_different { let update_nix_conf = crate::cli::cmd::init::prompt::Prompt::bool(&prompt); if update_nix_conf { - let nix_config_contents = tokio::fs::read_to_string(&nix_config_path).await?; + let nix_config_contents = tokio::fs::read_to_string(&nix_config_path) + .await + .wrap_err_with(|| { + format!("Reading the Nix configuration file {:?}", &nix_config_path) + })?; nix_conf_write_success = match tokio::fs::OpenOptions::new() .create(true) .truncate(true) From f47db98f78f1c2064cabfc37d8a00eba5368965b Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Thu, 26 Sep 2024 14:42:47 -0400 Subject: [PATCH 6/6] If the file is not found, return an empty string in its stead --- src/cli/cmd/login/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cli/cmd/login/mod.rs b/src/cli/cmd/login/mod.rs index 82da767..f79f075 100644 --- a/src/cli/cmd/login/mod.rs +++ b/src/cli/cmd/login/mod.rs @@ -393,6 +393,13 @@ pub async fn upsert_user_nix_config( if update_nix_conf { let nix_config_contents = tokio::fs::read_to_string(&nix_config_path) .await + .or_else(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + Ok("".to_string()) + } else { + Err(e) + } + }) .wrap_err_with(|| { format!("Reading the Nix configuration file {:?}", &nix_config_path) })?;