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

Add support for netrc files as a secondary fallback tier #395

Merged
merged 10 commits into from
Jan 3, 2024
1 change: 1 addition & 0 deletions crates/rattler_networking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ itertools = "0.11.0"
keyring = "2.0.5"
lazy_static = "1.4.0"
libc = "0.2.148"
netrc-rs = "0.1.2"
once_cell = "1.18.0"
reqwest = { version = "0.11.22", default-features = false }
retry-policies = { version = "0.2.0", default-features = false }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! This module provides a way to store and retrieve authentication information for a given host.
pub mod authentication;
pub mod fallback_storage;
pub mod netrc_storage;
pub mod storage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! Fallback storage for passwords.

use netrc_rs::{Machine, Netrc};
use std::{collections::HashMap, env, io::ErrorKind, path::Path, path::PathBuf};

/// A struct that implements storage and access of authentication
/// information backed by a on-disk JSON file
#[derive(Clone, Default)]
pub struct NetRcStorage {
/// The netrc file contents
machines: HashMap<String, Machine>,
}

/// An error that can occur when accessing the fallback storage
#[derive(thiserror::Error, Debug)]
pub enum NetRcStorageError {
/// An IO error occurred when accessing the fallback storage
#[error(transparent)]
IOError(#[from] std::io::Error),

/// An error occurred when parsing the netrc file
#[error("could not parse .netc file: {0}")]
ParseError(netrc_rs::Error),
}

impl NetRcStorage {
/// Create a new fallback storage by retrieving the netrc file from the user environment.
/// This uses the same environment variable as curl and will read the file from $NETRC
/// falling back to `~/.netrc`.
///
/// If reading the file fails or parsing the file fails, this will return an error. However,
/// if the file does not exist an empty storage will be returned.
///
/// When an error is returned the path to the file that the was read from is returned as well.
pub fn from_env() -> Result<Self, (PathBuf, NetRcStorageError)> {
// Get the path to the netrc file
let path = match env::var("NETRC") {
Ok(val) => PathBuf::from(val),
Err(_) => match dirs::home_dir() {
Some(mut path) => {
path.push(".netrc");
path
}
None => PathBuf::from(".netrc"),
},
};

match Self::from_path(&path) {
Ok(storage) => Ok(storage),
Err(NetRcStorageError::IOError(err)) if err.kind() == ErrorKind::NotFound => {
Ok(Self::default())
}
Err(err) => Err((path, err)),
}
}

/// Constructs a new [`NetRcStorage`] by reading the `.netrc` file at the given path. Returns
/// an error if reading from the file failed or if parsing the file failed.
pub fn from_path(path: &Path) -> Result<Self, NetRcStorageError> {
let content = std::fs::read_to_string(path)?;
let netrc = Netrc::parse(content, false).map_err(NetRcStorageError::ParseError)?;
let machines = netrc
.machines
.into_iter()
.map(|m| (m.name.clone(), m))
.filter_map(|(name, value)| name.map(|n| (n, value)))
.collect();
Ok(Self { machines })
}

/// Retrieve the authentication information for the given host
pub fn get_password(&self, host: &str) -> Result<Option<String>, NetRcStorageError> {
match self.machines.get(host) {
Some(machine) => Ok(machine.password.clone()),
None => Ok(None),
}
}
}
28 changes: 27 additions & 1 deletion crates/rattler_networking/src/authentication_storage/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::{
use keyring::Entry;
use reqwest::{IntoUrl, Url};

use super::netrc_storage;
use super::{authentication::Authentication, fallback_storage};

/// A struct that implements storage and access of authentication
Expand All @@ -24,16 +25,29 @@ pub struct AuthenticationStorage {

/// A cache so that we don't have to access the keyring all the time
cache: Arc<Mutex<HashMap<String, Option<Authentication>>>>,

/// Netrc Storage that will be used if the is no key store application available.
pub netrc_storage: netrc_storage::NetRcStorage,
}

impl AuthenticationStorage {
/// Create a new authentication storage with the given store key
pub fn new(store_key: &str, fallback_folder: &Path) -> AuthenticationStorage {
let fallback_location = fallback_folder.join(format!("{}_auth_store.json", store_key));

let netrc_storage = match netrc_storage::NetRcStorage::from_env() {
Ok(storage) => storage,
Err((path, err)) => {
tracing::warn!("error reading netrc file from {}: {}", path.display(), err);
netrc_storage::NetRcStorage::default()
}
};

AuthenticationStorage {
store_key: store_key.to_string(),
fallback_storage: fallback_storage::FallbackStorage::new(fallback_location),
cache: Arc::new(Mutex::new(HashMap::new())),
netrc_storage,
}
}
}
Expand Down Expand Up @@ -112,7 +126,19 @@ impl AuthenticationStorage {
self.fallback_storage.path.display()
);
match self.fallback_storage.get_password(host)? {
None => return Ok(None),
None => {
// Fallback to netrc file
tracing::debug!(
"Unable to retrieve credentials for from fallback {}: {}, using netrc credential storage",
host,
e,
);
match self.netrc_storage.get_password(host) {
Ok(None) => return Ok(None),
Ok(Some(password)) => password,
Err(_) => return Ok(None),
}
}
Some(password) => password,
}
}
Expand Down