Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do less without dnixd #145

Merged
merged 6 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading