Skip to content

Commit

Permalink
Merge pull request #145 from DeterminateSystems/do-less-without-dnixd
Browse files Browse the repository at this point in the history
Do less without dnixd
  • Loading branch information
grahamc authored Sep 26, 2024
2 parents 2af05f9 + f47db98 commit e1b68fc
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 75 deletions.
182 changes: 109 additions & 73 deletions src/cli/cmd/login/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -12,9 +13,10 @@ 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_NETRC_NAME, DETERMINATE_NIXD_SOCKET_NAME, DETERMINATE_STATE_DIR};
use crate::{DETERMINATE_NIXD_SOCKET_NAME, DETERMINATE_STATE_DIR};

use super::CommandExecute;

Expand All @@ -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<std::path::PathBuf>,

/// Skip following up a successful login with `fh status`.
#[clap(long)]
skip_status: bool,
Expand Down Expand Up @@ -59,9 +65,14 @@ pub async fn dnixd_uds() -> color_eyre::Result<SendRequest<axum::body::Body>> {
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<Body>, _) =
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<Body>, _) = 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 {
Expand All @@ -75,7 +86,10 @@ pub async fn dnixd_uds() -> color_eyre::Result<SendRequest<axum::body::Body>> {
.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");
Expand All @@ -90,24 +104,14 @@ 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
);
None
}
};

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(
Expand All @@ -118,15 +122,27 @@ impl LoginSubcommand {
),
);

println!("Log in to FlakeHub: {}", login_url);
println!("And then follow the prompts below:");
println!();
let mut token: Option<String> = 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")?
.trim()
.to_string(),
)
} 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?;
let status = FlakeHubClient::auth_status(self.api_addr.as_ref(), &token)
.await
.wrap_err("Checking the validity of the provided token")?;
(token, status)
}
None => {
Expand All @@ -135,42 +151,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
Expand All @@ -179,52 +159,97 @@ 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()
.uri("http://localhost/enroll-netrc-token")
.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();
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 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")?;

// 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-trusted-substituters = {cache_addr}\n\
extra-trusted-public-keys = {keys}\n\
",
netrc = netrc_path.display(),
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_path.display(),
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?;
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(
&nix_config_path,
&netrc_file_string,
&netrc_path,
&netrc_contents,
&user_nix_config_addition,
&self.cache_addr,
)
.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() {
Expand Down Expand Up @@ -278,7 +303,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,
Expand All @@ -287,7 +312,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) {
Expand Down Expand Up @@ -366,7 +391,18 @@ 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
.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)
})?;
nix_conf_write_success = match tokio::fs::OpenOptions::new()
.create(true)
.truncate(true)
Expand Down
1 change: 0 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),);
Expand Down
1 change: 0 additions & 1 deletion src/shared/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit e1b68fc

Please sign in to comment.