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

fix: redact secrets in the canonical_name functions #801

Merged
merged 12 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions crates/rattler_conda_types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ tracing = { workspace = true }
typed-path = { workspace = true }
url = { workspace = true, features = ["serde"] }
indexmap = { workspace = true }
rattler_redaction = { path = "../rattler_redaction" }
wolfv marked this conversation as resolved.
Show resolved Hide resolved

[dev-dependencies]
rand = { workspace = true }
Expand Down
67 changes: 59 additions & 8 deletions crates/rattler_conda_types/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use typed_path::{Utf8NativePathBuf, Utf8TypedPath, Utf8TypedPathBuf};
use url::Url;

use rattler_redaction::Redact;

use super::{ParsePlatformError, Platform};
use crate::utils::{path::is_path, url::parse_scheme};

Expand Down Expand Up @@ -51,12 +53,23 @@
}
}

/// Strip the channel alias if the base url is "under" the channel alias.
/// This returns the name of the channel (for example "conda-forge" for
/// "https://conda.anaconda.org/conda-forge" when the channel alias is

Check failure on line 58 in crates/rattler_conda_types/src/channel/mod.rs

View workflow job for this annotation

GitHub Actions / Check intra-doc links

this URL is not a hyperlink
/// "https://conda.anaconda.org").

Check failure on line 59 in crates/rattler_conda_types/src/channel/mod.rs

View workflow job for this annotation

GitHub Actions / Check intra-doc links

this URL is not a hyperlink
pub fn strip_channel_alias(&self, base_url: &Url) -> Option<String> {
base_url
.as_str()
.strip_prefix(self.channel_alias.as_str())
.map(|s| s.trim_end_matches('/').to_string())
}

/// Returns the canonical name of a channel with the given base url.
pub fn canonical_name(&self, base_url: &Url) -> NamedChannelOrUrl {
pub fn canonical_name(&self, base_url: &Url) -> String {
if let Some(stripped) = base_url.as_str().strip_prefix(self.channel_alias.as_str()) {
NamedChannelOrUrl::Name(stripped.trim_matches('/').to_string())
stripped.trim_end_matches('/').to_string()
} else {
NamedChannelOrUrl::Url(base_url.clone())
base_url.clone().redact().to_string()
}
}
}
Expand Down Expand Up @@ -102,10 +115,9 @@
pub fn into_channel(self, config: &ChannelConfig) -> Channel {
let name = match &self {
NamedChannelOrUrl::Name(name) => Some(name.clone()),
NamedChannelOrUrl::Url(base_url) => match config.canonical_name(base_url) {
NamedChannelOrUrl::Name(name) => Some(name),
NamedChannelOrUrl::Url(_) => None,
},
NamedChannelOrUrl::Url(base_url) => {
config.strip_channel_alias(base_url).map(|name| name)

Check failure on line 119 in crates/rattler_conda_types/src/channel/mod.rs

View workflow job for this annotation

GitHub Actions / Format and Lint

unnecessary map of the identity function
}
};
let base_url = self.into_base_url(config);
Channel {
Expand Down Expand Up @@ -348,7 +360,7 @@

/// Returns the canonical name of the channel
pub fn canonical_name(&self) -> String {
self.base_url.to_string()
self.base_url.clone().redact().to_string()
}
}

Expand Down Expand Up @@ -648,6 +660,28 @@
assert!(!is_path("conda-forge/label/rust_dev"));
}

#[test]
fn channel_canonical_name() {
let config = ChannelConfig::default_with_root_dir(std::env::current_dir().unwrap());
let channel = Channel::from_str("http://localhost:1234", &config).unwrap();

assert_eq!(channel.canonical_name(), "http://localhost:1234/");

let channel = Channel::from_str("http://user:password@localhost:1234", &config).unwrap();

assert_eq!(
channel.canonical_name(),
"http://user:********@localhost:1234/"
);

let channel =
Channel::from_str("http://localhost:1234/t/secretfoo/blablub", &config).unwrap();
assert_eq!(
channel.canonical_name(),
"http://localhost:1234/t/********/blablub/"
);
}

#[test]
fn config_canonical_name() {
let channel_config = ChannelConfig {
Expand All @@ -666,5 +700,22 @@
.as_str(),
"https://prefix.dev/conda-forge"
);
assert_eq!(
channel_config
.canonical_name(
&Url::from_str("https://prefix.dev/t/mysecrettoken/conda-forge/").unwrap()
)
.as_str(),
"https://prefix.dev/t/********/conda-forge"
);

assert_eq!(
channel_config
.canonical_name(
&Url::from_str("https://user:secret@prefix.dev/conda-forge/").unwrap()
)
.as_str(),
"https://user:********@prefix.dev/conda-forge"
);
}
}
4 changes: 0 additions & 4 deletions crates/rattler_networking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,3 @@ pub mod authentication_storage;
pub mod mirror_middleware;
pub mod oci_middleware;
pub mod retry_policies;

mod redaction;

pub use redaction::{redact_known_secrets_from_url, Redact, DEFAULT_REDACTION_STR};
1 change: 1 addition & 0 deletions crates/rattler_package_streaming/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ num_cpus = { workspace = true }
rattler_conda_types = { path="../rattler_conda_types", version = "0.26.3", default-features = false }
rattler_digest = { path="../rattler_digest", version = "1.0.0", default-features = false }
rattler_networking = { path="../rattler_networking", version = "0.20.10", default-features = false }
rattler_redaction = { path="../rattler_redaction", features = ["reqwest", "reqwest-middleware"] }
reqwest = { workspace = true, features = ["stream"], optional = true }
reqwest-middleware = { workspace = true, optional = true }
serde_json = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_package_streaming/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use zip::result::ZipError;
use rattler_digest::{Md5Hash, Sha256Hash};

#[cfg(feature = "reqwest")]
use rattler_networking::Redact;
use rattler_redaction::Redact;

pub mod read;
pub mod seek;
Expand Down
16 changes: 16 additions & 0 deletions crates/rattler_redaction/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "rattler_redaction"
version = "0.20.10"
edition.workspace = true
authors = ["Wolf Vollprecht <w.vollprecht@gmail.com>"]
description = "Redact sensitive information from URLs (ie. conda tokens)"
categories.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
readme.workspace = true

[dependencies]
url = { workspace = true }
reqwest = { workspace = true, optional = true }
reqwest-middleware = { workspace = true, optional = true }
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use itertools::Itertools;
use url::Url;

/// A default string to use for redaction.
Expand All @@ -22,21 +21,32 @@ pub const DEFAULT_REDACTION_STR: &str = "********";
/// let redacted_url = redact_known_secrets_from_url(&url, DEFAULT_REDACTION_STR).unwrap_or(url);
/// ```
pub fn redact_known_secrets_from_url(url: &Url, redaction: &str) -> Option<Url> {
let mut url = url.clone();
if url.password().is_some() {
url.set_password(Some(redaction)).ok()?;
}

let mut segments = url.path_segments()?;
match (segments.next(), segments.next()) {
(Some("t"), Some(_)) => {
let remainder = segments.collect_vec();
let redacted_path = format!(
"t/{redaction}{seperator}{remainder}",
seperator = if remainder.is_empty() { "" } else { "/" },
remainder = remainder.iter().format("/")
let remainder = segments.collect::<Vec<_>>();
let mut redacted_path = format!(
"t/{redaction}{separator}",
separator = if remainder.is_empty() { "" } else { "/" },
);

let mut url = url.clone();
for (idx, segment) in remainder.iter().enumerate() {
redacted_path.push_str(segment);
// if the original url ends with a slash, we need to add it to the redacted path
if idx < remainder.len() - 1 || url.path().ends_with('/') {
redacted_path.push('/');
}
}

url.set_path(&redacted_path);
Some(url)
}
_ => None,
_ => Some(url),
}
}

Expand All @@ -46,6 +56,7 @@ pub trait Redact {
fn redact(self) -> Self;
}

#[cfg(feature = "reqwest-middleware")]
impl Redact for reqwest_middleware::Error {
fn redact(self) -> Self {
if let Some(url) = self.url() {
Expand All @@ -58,6 +69,7 @@ impl Redact for reqwest_middleware::Error {
}
}

#[cfg(feature = "reqwest")]
impl Redact for reqwest::Error {
fn redact(self) -> Self {
if let Some(url) = self.url() {
Expand Down Expand Up @@ -99,13 +111,37 @@ mod test {
)
);

// should stay as is
assert_eq!(
redact_known_secrets_from_url(
&Url::from_str("https://conda.anaconda.org/conda-forge/noarch/repodata.json")
.unwrap(),
"helloworld"
),
None,
)
.unwrap(),
Url::from_str("https://conda.anaconda.org/conda-forge/noarch/repodata.json").unwrap(),
);

let redacted = redact_known_secrets_from_url(
&Url::from_str("https://user:secret@prefix.dev/conda-forge").unwrap(),
DEFAULT_REDACTION_STR,
)
.unwrap();

assert_eq!(
redacted.to_string(),
format!("https://user:{DEFAULT_REDACTION_STR}@prefix.dev/conda-forge")
);

let redacted = redact_known_secrets_from_url(
&Url::from_str("https://user:secret@prefix.dev/conda-forge/").unwrap(),
DEFAULT_REDACTION_STR,
)
.unwrap();

assert_eq!(
redacted.to_string(),
format!("https://user:{DEFAULT_REDACTION_STR}@prefix.dev/conda-forge/")
);
}
}
1 change: 1 addition & 0 deletions crates/rattler_repodata_gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ tracing = { workspace = true }
url = { workspace = true, features = ["serde"] }
zstd = { workspace = true }
rattler_cache = { version = "0.1.4", path = "../rattler_cache" }
rattler_redaction = { path = "../rattler_redaction", features = ["reqwest", "reqwest-middleware"] }
wolfv marked this conversation as resolved.
Show resolved Hide resolved

[target.'cfg(unix)'.dependencies]
libc = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_repodata_gateway/src/fetch/jlap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ use blake2::digest::{FixedOutput, Update};
use rattler_digest::{
parse_digest_from_hex, serde::SerializableHash, Blake2b256, Blake2b256Hash, Blake2bMac256,
};
use rattler_networking::Redact;
use rattler_redaction::Redact;
use reqwest::{
header::{HeaderMap, HeaderValue},
Response, StatusCode,
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_repodata_gateway/src/fetch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use cache_control::{Cachability, CacheControl};
use futures::{future::ready, FutureExt, TryStreamExt};
use humansize::{SizeFormatter, DECIMAL};
use rattler_digest::{compute_file_digest, Blake2b256, HashingWriter};
use rattler_networking::Redact;
use rattler_redaction::Redact;
use reqwest::{
header::{HeaderMap, HeaderValue},
Response, StatusCode,
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_repodata_gateway/src/gateway/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::fetch;
use crate::fetch::{FetchRepoDataError, RepoDataNotFoundError};
use crate::gateway::direct_url_query::DirectUrlQueryError;
use rattler_conda_types::{Channel, MatchSpec};
use rattler_networking::Redact;
use rattler_redaction::Redact;
use reqwest_middleware::Error;
use simple_spawn_blocking::Cancelled;
use std::fmt::{Display, Formatter};
Expand Down
Loading