mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Broker credential aliases in child environments (#40484)
## What changed - Discover supported credentials inherited from the parent even when their canonical provider variable is filtered from the child environment. - Replace matching values and credentials embedded in longer child environment values with broker dummies, then restore only aliases that the broker virtualized. - Preserve provider and host bindings when selecting credentials for outbound requests, including GitHub Enterprise and custom OpenAI hosts. - Expose helpers for identifying provider and binding variables, plus an API for virtualizing credential-bearing text before it is persisted. ## Testing - Extend credential broker tests for filtered parent variables, exact and embedded aliases, text virtualization, user overrides, and host-bound GitHub and OpenAI credentials. GitOrigin-RevId: 55d673911900470aab4e206d0f4f5e62ed3611ae
This commit is contained in:
1 parent
4f6d43c267
commit
0280d73c1e
8 files changed
+566
-42
No files matched your search
@@ -9,6 +9,7 @@ use std::sync::RwLock;
|
|||||||
|
|
||||||
pub const CREDENTIAL_BROKER_ACTIVE_ENV_KEY: &str = "CODEX_NETWORK_PROXY_CREDENTIAL_BROKER_ACTIVE";
|
pub const CREDENTIAL_BROKER_ACTIVE_ENV_KEY: &str = "CODEX_NETWORK_PROXY_CREDENTIAL_BROKER_ACTIVE";
|
||||||
pub(crate) const BROKERED_CREDENTIALS_ENV_KEY: &str = "CODEX_NETWORK_PROXY_BROKERED_CREDENTIALS";
|
pub(crate) const BROKERED_CREDENTIALS_ENV_KEY: &str = "CODEX_NETWORK_PROXY_BROKERED_CREDENTIALS";
|
||||||
|
const MIN_EMBEDDED_CREDENTIAL_LENGTH: usize = 16;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct CredentialBroker {
|
pub(crate) struct CredentialBroker {
|
||||||
@@ -20,6 +21,7 @@ struct CredentialBrokerState {
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
openai_api_host: Option<String>,
|
openai_api_host: Option<String>,
|
||||||
credentials: Vec<CredentialRecord>,
|
credentials: Vec<CredentialRecord>,
|
||||||
|
credential_aliases: Vec<CredentialAlias>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CredentialRecord {
|
struct CredentialRecord {
|
||||||
@@ -30,6 +32,11 @@ struct CredentialRecord {
|
|||||||
dummy_value: String,
|
dummy_value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CredentialAlias {
|
||||||
|
env_var: String,
|
||||||
|
dummy_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
fn env_key_matches(candidate: &str, expected: &str) -> bool {
|
fn env_key_matches(candidate: &str, expected: &str) -> bool {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
candidate.eq_ignore_ascii_case(expected)
|
candidate.eq_ignore_ascii_case(expected)
|
||||||
@@ -78,6 +85,7 @@ impl CredentialBroker {
|
|||||||
if state.enabled != config.credential_broker {
|
if state.enabled != config.credential_broker {
|
||||||
state.enabled = config.credential_broker;
|
state.enabled = config.credential_broker;
|
||||||
state.credentials.clear();
|
state.credentials.clear();
|
||||||
|
state.credential_aliases.clear();
|
||||||
}
|
}
|
||||||
if state.openai_api_host != config.credential_broker_openai_host {
|
if state.openai_api_host != config.credential_broker_openai_host {
|
||||||
state
|
state
|
||||||
@@ -89,6 +97,45 @@ impl CredentialBroker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn discover_parent_credentials(
|
||||||
|
&self,
|
||||||
|
parent_env: &HashMap<String, String>,
|
||||||
|
child_env: &HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let mut state = self.write_state();
|
||||||
|
if !state.enabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for provider in providers::credential_providers() {
|
||||||
|
for source in provider.sources() {
|
||||||
|
let Some(host_binding) =
|
||||||
|
(source.host_binding)(child_env, state.openai_api_host.as_deref())
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for env_var in source.env_vars {
|
||||||
|
let Some(real_value) =
|
||||||
|
brokerable_credential_value(parent_env, &state, env_var, provider)
|
||||||
|
.map(str::to_string)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if env_value(child_env, env_var) == Some(real_value.as_str()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if child_env.values().any(|value| {
|
||||||
|
value == &real_value
|
||||||
|
|| real_value.len() >= MIN_EMBEDDED_CREDENTIAL_LENGTH
|
||||||
|
&& value.contains(&real_value)
|
||||||
|
}) {
|
||||||
|
state.register(env_var, provider, host_binding.clone(), &real_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn virtualize_child_env(&self, env: &mut HashMap<String, String>) {
|
pub(crate) fn virtualize_child_env(&self, env: &mut HashMap<String, String>) {
|
||||||
let mut state = self.write_state();
|
let mut state = self.write_state();
|
||||||
if !state.enabled {
|
if !state.enabled {
|
||||||
@@ -100,45 +147,105 @@ impl CredentialBroker {
|
|||||||
|
|
||||||
for provider in providers::credential_providers() {
|
for provider in providers::credential_providers() {
|
||||||
for source in provider.sources() {
|
for source in provider.sources() {
|
||||||
if let Some(host_binding) =
|
let Some(host_binding) =
|
||||||
(source.host_binding)(env, state.openai_api_host.as_deref())
|
(source.host_binding)(env, state.openai_api_host.as_deref())
|
||||||
{
|
else {
|
||||||
for env_var in source.env_vars {
|
continue;
|
||||||
virtualize_env_var(
|
};
|
||||||
env,
|
for env_var in source.env_vars {
|
||||||
&mut state,
|
virtualize_env_var(env, &mut state, env_var, provider, host_binding.clone());
|
||||||
env_var,
|
|
||||||
provider,
|
|
||||||
host_binding.clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let credentials = prioritized_credentials(&state, env);
|
||||||
|
let mut credential_aliases = Vec::new();
|
||||||
|
for (key, value) in env.iter_mut() {
|
||||||
|
let mut virtualized = false;
|
||||||
|
for credential in &credentials {
|
||||||
|
if value == &credential.real_value {
|
||||||
|
value.clone_from(&credential.dummy_value);
|
||||||
|
virtualized = true;
|
||||||
|
} else if credential.real_value.len() >= MIN_EMBEDDED_CREDENTIAL_LENGTH
|
||||||
|
&& value.contains(&credential.real_value)
|
||||||
|
{
|
||||||
|
*value = value.replace(&credential.real_value, &credential.dummy_value);
|
||||||
|
virtualized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if virtualized {
|
||||||
|
credential_aliases.push(CredentialAlias {
|
||||||
|
env_var: key.clone(),
|
||||||
|
dummy_value: value.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for alias in credential_aliases {
|
||||||
|
if !state.credential_aliases.iter().any(|existing| {
|
||||||
|
env_key_matches(&existing.env_var, &alias.env_var)
|
||||||
|
&& existing.dummy_value == alias.dummy_value
|
||||||
|
}) {
|
||||||
|
state.credential_aliases.push(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
update_brokered_credentials_marker(&state, env);
|
update_brokered_credentials_marker(&state, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn restore_child_env(
|
pub(crate) fn restore_child_env(
|
||||||
&self,
|
&self,
|
||||||
env: &mut HashMap<String, String>,
|
env: &mut HashMap<String, String>,
|
||||||
command: &mut [String],
|
_command: &mut [String],
|
||||||
) {
|
) {
|
||||||
let state = self.read_state();
|
let state = self.read_state();
|
||||||
if !state.enabled || env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY) != Some("1") {
|
if !state.enabled || env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY) != Some("1") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (key, value) in env {
|
let credentials = state
|
||||||
if let Some(credential) = state.credentials.iter().find(|credential| {
|
.credentials
|
||||||
env_key_matches(key, &credential.env_var) && value == &credential.dummy_value
|
.iter()
|
||||||
}) {
|
.filter(|credential| {
|
||||||
for argument in command.iter_mut() {
|
env.iter().any(|(key, value)| {
|
||||||
if argument.contains(&credential.dummy_value) {
|
!env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY)
|
||||||
*argument =
|
&& !env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY)
|
||||||
argument.replace(&credential.dummy_value, &credential.real_value);
|
&& (value == &credential.dummy_value
|
||||||
}
|
|| credential.real_value.len() >= MIN_EMBEDDED_CREDENTIAL_LENGTH
|
||||||
|
&& value.contains(&credential.dummy_value))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
for (key, value) in env.iter_mut() {
|
||||||
|
if env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY)
|
||||||
|
|| env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let canonical_credential = state
|
||||||
|
.credentials
|
||||||
|
.iter()
|
||||||
|
.any(|credential| env_key_matches(key, &credential.env_var));
|
||||||
|
if !canonical_credential
|
||||||
|
&& !state.credential_aliases.iter().any(|alias| {
|
||||||
|
env_key_matches(key, &alias.env_var) && value == &alias.dummy_value
|
||||||
|
})
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for credential in &credentials {
|
||||||
|
if canonical_credential
|
||||||
|
&& !state.credentials.iter().any(|candidate| {
|
||||||
|
env_key_matches(key, &candidate.env_var)
|
||||||
|
&& std::ptr::eq(candidate.provider, credential.provider)
|
||||||
|
&& candidate.host_binding == credential.host_binding
|
||||||
|
&& candidate.real_value == credential.real_value
|
||||||
|
})
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if value == &credential.dummy_value {
|
||||||
|
value.clone_from(&credential.real_value);
|
||||||
|
} else if credential.real_value.len() >= MIN_EMBEDDED_CREDENTIAL_LENGTH {
|
||||||
|
*value = value.replace(&credential.dummy_value, &credential.real_value);
|
||||||
}
|
}
|
||||||
*value = credential.real_value.clone();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,6 +260,49 @@ impl CredentialBroker {
|
|||||||
.any(|credential| credential.matches_host(&normalized_host))
|
.any(|credential| credential.matches_host(&normalized_host))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn virtualize_text(&self, text: &mut String, env: &HashMap<String, String>) -> bool {
|
||||||
|
let state = self.read_state();
|
||||||
|
if !state.enabled {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let allowed_keys = brokered_credential_dummy_env_keys(env);
|
||||||
|
let credentials = prioritized_credentials(&state, env);
|
||||||
|
let mut allowed = true;
|
||||||
|
for credential in &credentials {
|
||||||
|
let contains_real = text.as_str() == credential.real_value
|
||||||
|
|| credential.real_value.len() >= MIN_EMBEDDED_CREDENTIAL_LENGTH
|
||||||
|
&& text.contains(&credential.real_value);
|
||||||
|
let contains_dummy = text.as_str() == credential.dummy_value
|
||||||
|
|| credential.dummy_value.len() >= MIN_EMBEDDED_CREDENTIAL_LENGTH
|
||||||
|
&& text.contains(&credential.dummy_value);
|
||||||
|
if !contains_real && !contains_dummy {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let replacement = credentials.iter().copied().find(|candidate| {
|
||||||
|
std::ptr::eq(candidate.provider, credential.provider)
|
||||||
|
&& candidate.real_value == credential.real_value
|
||||||
|
&& allowed_keys.iter().any(|key| {
|
||||||
|
env_key_matches(key, &candidate.env_var)
|
||||||
|
&& env_value(env, key) == Some(candidate.dummy_value.as_str())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if replacement.is_none() {
|
||||||
|
allowed = false;
|
||||||
|
}
|
||||||
|
let replacement = replacement.map_or("", |candidate| candidate.dummy_value.as_str());
|
||||||
|
if contains_real {
|
||||||
|
*text = text.replace(&credential.real_value, replacement);
|
||||||
|
}
|
||||||
|
if contains_dummy && credential.dummy_value != replacement {
|
||||||
|
*text = text.replace(&credential.dummy_value, replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn inject_request_headers(&self, host: &str, headers: &mut HeaderMap) {
|
pub(crate) fn inject_request_headers(&self, host: &str, headers: &mut HeaderMap) {
|
||||||
let normalized_host = normalize_host(host);
|
let normalized_host = normalize_host(host);
|
||||||
let state = self.read_state();
|
let state = self.read_state();
|
||||||
@@ -160,12 +310,8 @@ impl CredentialBroker {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let matching_credentials = state
|
let Some((credential, header_value)) =
|
||||||
.credentials
|
select_credential(headers, &normalized_host, &state.credentials)
|
||||||
.iter()
|
|
||||||
.filter(|credential| credential.matches_host(&normalized_host))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let Some((credential, header_value)) = select_credential(headers, &matching_credentials)
|
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -194,11 +340,32 @@ fn virtualize_env_var(
|
|||||||
provider: &'static providers::CredentialProvider,
|
provider: &'static providers::CredentialProvider,
|
||||||
host_binding: providers::CredentialHostBinding,
|
host_binding: providers::CredentialHostBinding,
|
||||||
) {
|
) {
|
||||||
let Some(real_value) = brokerable_credential_value(env, state, env_var, provider) else {
|
let Some(real_value) = brokerable_credential_value(env, state, env_var, provider)
|
||||||
|
.map(str::to_string)
|
||||||
|
.or_else(|| {
|
||||||
|
let dummy = env_value(env, env_var)?;
|
||||||
|
state
|
||||||
|
.credentials
|
||||||
|
.iter()
|
||||||
|
.find(|credential| {
|
||||||
|
credential.dummy_value == dummy
|
||||||
|
&& std::ptr::eq(credential.provider, provider)
|
||||||
|
&& !env_key_matches(&credential.env_var, env_var)
|
||||||
|
&& credential.host_binding == host_binding
|
||||||
|
&& state.credentials.iter().any(|candidate| {
|
||||||
|
env_key_matches(&candidate.env_var, env_var)
|
||||||
|
&& std::ptr::eq(candidate.provider, provider)
|
||||||
|
&& candidate.host_binding == host_binding
|
||||||
|
&& candidate.real_value == credential.real_value
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|credential| credential.real_value.clone())
|
||||||
|
})
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let dummy_value = state.register(env_var, provider, host_binding, real_value);
|
let dummy_value = state.register(env_var, provider, host_binding, &real_value);
|
||||||
set_env_value(env, env_var, dummy_value);
|
set_env_value(env, env_var, dummy_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,21 +428,60 @@ impl CredentialRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prioritized_credentials<'a>(
|
||||||
|
state: &'a CredentialBrokerState,
|
||||||
|
env: &HashMap<String, String>,
|
||||||
|
) -> Vec<&'a CredentialRecord> {
|
||||||
|
let mut credentials = state.credentials.iter().collect::<Vec<_>>();
|
||||||
|
credentials.sort_unstable_by_key(|credential| {
|
||||||
|
let active = env_entry(env, &credential.env_var)
|
||||||
|
.is_some_and(|(_, value)| value == credential.dummy_value);
|
||||||
|
let has_matching_host_binding = credential.provider.sources().iter().any(|source| {
|
||||||
|
!source.binding_env_vars.is_empty()
|
||||||
|
&& (source.host_binding)(env, state.openai_api_host.as_deref())
|
||||||
|
.is_some_and(|binding| binding == credential.host_binding)
|
||||||
|
});
|
||||||
|
(
|
||||||
|
std::cmp::Reverse(credential.real_value.len()),
|
||||||
|
std::cmp::Reverse(active),
|
||||||
|
std::cmp::Reverse(has_matching_host_binding),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
credentials
|
||||||
|
}
|
||||||
|
|
||||||
fn select_credential<'a>(
|
fn select_credential<'a>(
|
||||||
headers: &HeaderMap,
|
headers: &HeaderMap,
|
||||||
matching_credentials: &[&'a CredentialRecord],
|
host: &str,
|
||||||
|
credentials: &'a [CredentialRecord],
|
||||||
) -> Option<(&'a CredentialRecord, rama_http::HeaderValue)> {
|
) -> Option<(&'a CredentialRecord, rama_http::HeaderValue)> {
|
||||||
let mut translated_matches = matching_credentials
|
let mut translated_matches = credentials
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.filter(|credential| credential.matches_host(host))
|
||||||
.filter_map(|credential| {
|
.filter_map(|credential| {
|
||||||
credential
|
credentials
|
||||||
.provider
|
.iter()
|
||||||
.translate_request_header(headers, &credential.dummy_value, &credential.real_value)
|
.filter(|candidate| {
|
||||||
|
std::ptr::eq(candidate.provider, credential.provider)
|
||||||
|
&& candidate.real_value == credential.real_value
|
||||||
|
&& candidate.matches_host(host)
|
||||||
|
})
|
||||||
|
.find_map(|candidate| {
|
||||||
|
credential.provider.translate_request_header(
|
||||||
|
headers,
|
||||||
|
&candidate.dummy_value,
|
||||||
|
&credential.real_value,
|
||||||
|
)
|
||||||
|
})
|
||||||
.map(|header_value| (credential, header_value))
|
.map(|header_value| (credential, header_value))
|
||||||
});
|
});
|
||||||
let matched = translated_matches.next()?;
|
let matched = translated_matches.next()?;
|
||||||
translated_matches.next().is_none().then_some(matched)
|
translated_matches
|
||||||
|
.all(|(credential, _)| {
|
||||||
|
std::ptr::eq(credential.provider, matched.0.provider)
|
||||||
|
&& credential.real_value == matched.0.real_value
|
||||||
|
})
|
||||||
|
.then_some(matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_brokered_credentials_marker(
|
fn update_brokered_credentials_marker(
|
||||||
@@ -285,7 +491,14 @@ fn update_brokered_credentials_marker(
|
|||||||
let brokered = providers::credential_env_keys()
|
let brokered = providers::credential_env_keys()
|
||||||
.filter_map(|key| {
|
.filter_map(|key| {
|
||||||
let (actual_key, value) = env_entry(env, key)?;
|
let (actual_key, value) = env_entry(env, key)?;
|
||||||
state.is_dummy_value(value).then_some((actual_key, value))
|
state
|
||||||
|
.credentials
|
||||||
|
.iter()
|
||||||
|
.any(|credential| {
|
||||||
|
env_key_matches(actual_key, &credential.env_var)
|
||||||
|
&& value == credential.dummy_value
|
||||||
|
})
|
||||||
|
.then_some((actual_key, value))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
match serde_json::to_string(&brokered) {
|
match serde_json::to_string(&brokered) {
|
||||||
@@ -317,15 +530,56 @@ pub fn brokered_credential_dummy_env_keys(env: &HashMap<String, String>) -> Vec<
|
|||||||
(actual_value == dummy_value.as_str()).then(|| actual_key.to_string())
|
(actual_value == dummy_value.as_str()).then(|| actual_key.to_string())
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
keys.sort_unstable();
|
let context_bound = |key: &str| {
|
||||||
|
providers::credential_providers().any(|provider| {
|
||||||
|
provider.sources().iter().any(|source| {
|
||||||
|
source
|
||||||
|
.env_vars
|
||||||
|
.iter()
|
||||||
|
.any(|candidate| env_key_matches(key, candidate))
|
||||||
|
&& source
|
||||||
|
.binding_env_vars
|
||||||
|
.iter()
|
||||||
|
.any(|context| env_value(env, context).is_some())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
keys.sort_unstable_by(|left, right| {
|
||||||
|
context_bound(right)
|
||||||
|
.cmp(&context_bound(left))
|
||||||
|
.then_with(|| left.cmp(right))
|
||||||
|
});
|
||||||
keys
|
keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns environment keys used to bind currently brokered credentials to hosts.
|
||||||
|
pub fn brokered_credential_binding_env_keys(
|
||||||
|
env: &HashMap<String, String>,
|
||||||
|
) -> impl Iterator<Item = &'static str> {
|
||||||
|
let brokered_keys = brokered_credential_dummy_env_keys(env);
|
||||||
|
providers::credential_binding_env_keys(&brokered_keys)
|
||||||
|
.filter(|key| env_value(env, key).is_some())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns environment keys used to bind registered credential providers to their destinations.
|
/// Returns environment keys used to bind registered credential providers to their destinations.
|
||||||
pub fn credential_broker_provider_context_env_keys() -> impl Iterator<Item = &'static str> {
|
pub fn credential_broker_provider_context_env_keys() -> impl Iterator<Item = &'static str> {
|
||||||
providers::credential_providers().flat_map(|provider| provider.context_env_vars.iter().copied())
|
providers::credential_providers().flat_map(|provider| provider.context_env_vars.iter().copied())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether an environment key belongs to a supported credential provider.
|
||||||
|
pub fn is_credential_broker_provider_env_key(key: &str) -> bool {
|
||||||
|
providers::credential_providers().any(|provider| {
|
||||||
|
provider
|
||||||
|
.sources()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|source| source.env_vars.iter().copied())
|
||||||
|
.chain(provider.context_env_vars.iter().copied())
|
||||||
|
.any(|candidate| env_key_matches(key, candidate))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns credential keys plus provider context keys already present in an environment with an
|
/// Returns credential keys plus provider context keys already present in an environment with an
|
||||||
/// active broker.
|
/// active broker.
|
||||||
pub fn brokered_credential_env_keys(
|
pub fn brokered_credential_env_keys(
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ pub(super) enum CredentialHostBinding {
|
|||||||
|
|
||||||
pub(super) struct CredentialSource {
|
pub(super) struct CredentialSource {
|
||||||
pub(super) env_vars: &'static [&'static str],
|
pub(super) env_vars: &'static [&'static str],
|
||||||
|
pub(super) binding_env_vars: &'static [&'static str],
|
||||||
pub(super) host_binding:
|
pub(super) host_binding:
|
||||||
fn(&HashMap<String, String>, Option<&str>) -> Option<CredentialHostBinding>,
|
fn(&HashMap<String, String>, Option<&str>) -> Option<CredentialHostBinding>,
|
||||||
}
|
}
|
||||||
@@ -102,6 +103,21 @@ pub(super) fn credential_context_env_keys(
|
|||||||
.flat_map(|provider| provider.context_env_vars.iter().copied())
|
.flat_map(|provider| provider.context_env_vars.iter().copied())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn credential_binding_env_keys(
|
||||||
|
brokered_keys: &[String],
|
||||||
|
) -> impl Iterator<Item = &'static str> + '_ {
|
||||||
|
credential_providers()
|
||||||
|
.flat_map(CredentialProvider::sources)
|
||||||
|
.filter(move |source| {
|
||||||
|
source.env_vars.iter().any(|key| {
|
||||||
|
brokered_keys
|
||||||
|
.iter()
|
||||||
|
.any(|brokered_key| super::env_key_matches(brokered_key, key))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flat_map(|source| source.binding_env_vars.iter().copied())
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn credential_env_keys() -> impl Iterator<Item = &'static str> {
|
pub(super) fn credential_env_keys() -> impl Iterator<Item = &'static str> {
|
||||||
credential_providers()
|
credential_providers()
|
||||||
.flat_map(CredentialProvider::sources)
|
.flat_map(CredentialProvider::sources)
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ pub(super) static PROVIDER: CredentialProvider = CredentialProvider {
|
|||||||
sources: &[
|
sources: &[
|
||||||
CredentialSource {
|
CredentialSource {
|
||||||
env_vars: GITHUB_CLOUD_TOKEN_ENV_VARS,
|
env_vars: GITHUB_CLOUD_TOKEN_ENV_VARS,
|
||||||
|
binding_env_vars: &[],
|
||||||
host_binding: github_cloud_binding,
|
host_binding: github_cloud_binding,
|
||||||
},
|
},
|
||||||
CredentialSource {
|
CredentialSource {
|
||||||
env_vars: GITHUB_ENTERPRISE_TOKEN_ENV_VARS,
|
env_vars: GITHUB_ENTERPRISE_TOKEN_ENV_VARS,
|
||||||
|
binding_env_vars: &[GH_HOST_ENV_VAR],
|
||||||
host_binding: github_enterprise_binding,
|
host_binding: github_enterprise_binding,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub(super) static PROVIDER: CredentialProvider = CredentialProvider {
|
|||||||
context_env_vars: &[OPENAI_BASE_URL_ENV_VAR],
|
context_env_vars: &[OPENAI_BASE_URL_ENV_VAR],
|
||||||
sources: &[CredentialSource {
|
sources: &[CredentialSource {
|
||||||
env_vars: OPENAI_API_KEY_ENV_VARS,
|
env_vars: OPENAI_API_KEY_ENV_VARS,
|
||||||
|
binding_env_vars: &[OPENAI_BASE_URL_ENV_VAR],
|
||||||
host_binding,
|
host_binding,
|
||||||
}],
|
}],
|
||||||
reset_on_configuration_change: true,
|
reset_on_configuration_change: true,
|
||||||
|
|||||||
@@ -51,10 +51,13 @@ fn virtualize_child_env_replaces_supported_credentials() {
|
|||||||
let broker = CredentialBroker::new(/*enabled*/ true);
|
let broker = CredentialBroker::new(/*enabled*/ true);
|
||||||
let github_token = "github_pat_11AA0bbCC_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH";
|
let github_token = "github_pat_11AA0bbCC_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGH";
|
||||||
let openai_api_key = "sk-proj-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
let openai_api_key = "sk-proj-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
||||||
|
let authorization = format!("Bearer {github_token}");
|
||||||
let mut env = env_map([
|
let mut env = env_map([
|
||||||
("GH_TOKEN", github_token),
|
("GH_TOKEN", github_token),
|
||||||
|
("HOMEBREW_GITHUB_API_TOKEN", github_token),
|
||||||
|
("AUTH_HEADER", authorization.as_str()),
|
||||||
("OPENAI_API_KEY", openai_api_key),
|
("OPENAI_API_KEY", openai_api_key),
|
||||||
("GH_ENTERPRISE_TOKEN", "ghp-enterprise-real"),
|
("GH_ENTERPRISE_TOKEN", github_token),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
broker.virtualize_child_env(&mut env);
|
broker.virtualize_child_env(&mut env);
|
||||||
@@ -63,12 +66,37 @@ fn virtualize_child_env_replaces_supported_credentials() {
|
|||||||
let openai_dummy = env.get("OPENAI_API_KEY").expect("dummy OpenAI API key");
|
let openai_dummy = env.get("OPENAI_API_KEY").expect("dummy OpenAI API key");
|
||||||
assert_credential_shape(github_token, github_dummy, "github_pat_");
|
assert_credential_shape(github_token, github_dummy, "github_pat_");
|
||||||
assert_credential_shape(openai_api_key, openai_dummy, "sk-proj-");
|
assert_credential_shape(openai_api_key, openai_dummy, "sk-proj-");
|
||||||
|
assert_eq!(env.get("HOMEBREW_GITHUB_API_TOKEN"), Some(github_dummy));
|
||||||
|
assert_eq!(env.get("GH_ENTERPRISE_TOKEN"), Some(github_dummy));
|
||||||
|
assert_eq!(
|
||||||
|
env.get("AUTH_HEADER"),
|
||||||
|
Some(&format!("Bearer {github_dummy}"))
|
||||||
|
);
|
||||||
|
let mut persisted_credentials = format!("{github_token}\n{openai_api_key}");
|
||||||
|
assert!(broker.virtualize_text(&mut persisted_credentials, &env));
|
||||||
|
assert_eq!(
|
||||||
|
persisted_credentials,
|
||||||
|
format!("{github_dummy}\n{openai_dummy}")
|
||||||
|
);
|
||||||
|
let mut filtered_env = env.clone();
|
||||||
|
filtered_env.remove("OPENAI_API_KEY");
|
||||||
|
let mut excluded_credentials = format!("{github_token}\n{openai_api_key}");
|
||||||
|
assert!(!broker.virtualize_text(&mut excluded_credentials, &filtered_env));
|
||||||
|
assert_eq!(excluded_credentials, format!("{github_dummy}\n"));
|
||||||
|
let mut excluded_dummies = format!("{github_dummy}\n{openai_dummy}");
|
||||||
|
assert!(!broker.virtualize_text(&mut excluded_dummies, &filtered_env));
|
||||||
|
assert_eq!(excluded_dummies, format!("{github_dummy}\n"));
|
||||||
let mut command = vec![
|
let mut command = vec![
|
||||||
format!("Authorization: Bearer {github_dummy}"),
|
format!("Authorization: Bearer {github_dummy}"),
|
||||||
format!("Authorization: Bearer {openai_dummy}"),
|
format!("Authorization: Bearer {openai_dummy}"),
|
||||||
];
|
];
|
||||||
|
let github_dummy = github_dummy.clone();
|
||||||
let openai_dummy = openai_dummy.clone();
|
let openai_dummy = openai_dummy.clone();
|
||||||
env.insert("OPENAI_API_KEY".to_string(), "sk-user-override".to_string());
|
env.insert("OPENAI_API_KEY".to_string(), "sk-user-override".to_string());
|
||||||
|
env.insert(
|
||||||
|
"GIT_CONFIG_VALUE_0".to_string(),
|
||||||
|
format!("Authorization: Bearer {github_dummy}"),
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
brokered_credential_dummy_env_keys(&env),
|
brokered_credential_dummy_env_keys(&env),
|
||||||
vec!["GH_TOKEN".to_string()]
|
vec!["GH_TOKEN".to_string()]
|
||||||
@@ -76,17 +104,36 @@ fn virtualize_child_env_replaces_supported_credentials() {
|
|||||||
|
|
||||||
broker.restore_child_env(&mut env, &mut command);
|
broker.restore_child_env(&mut env, &mut command);
|
||||||
assert_eq!(env.get("GH_TOKEN").map(String::as_str), Some(github_token));
|
assert_eq!(env.get("GH_TOKEN").map(String::as_str), Some(github_token));
|
||||||
|
assert_eq!(
|
||||||
|
env.get("HOMEBREW_GITHUB_API_TOKEN").map(String::as_str),
|
||||||
|
Some(github_token)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
env.get("GH_ENTERPRISE_TOKEN").map(String::as_str),
|
||||||
|
Some(github_token)
|
||||||
|
);
|
||||||
|
assert_eq!(env.get("AUTH_HEADER"), Some(&authorization));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
env.get("OPENAI_API_KEY").map(String::as_str),
|
env.get("OPENAI_API_KEY").map(String::as_str),
|
||||||
Some("sk-user-override")
|
Some("sk-user-override")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
env.get("GIT_CONFIG_VALUE_0"),
|
||||||
|
Some(&format!("Authorization: Bearer {github_dummy}"))
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
command,
|
command,
|
||||||
vec![
|
vec![
|
||||||
format!("Authorization: Bearer {github_token}"),
|
format!("Authorization: Bearer {github_dummy}"),
|
||||||
format!("Authorization: Bearer {openai_dummy}"),
|
format!("Authorization: Bearer {openai_dummy}"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
env.insert("GH_TOKEN".to_string(), openai_dummy.clone());
|
||||||
|
env.insert("OPENAI_API_KEY".to_string(), github_dummy.clone());
|
||||||
|
broker.restore_child_env(&mut env, &mut []);
|
||||||
|
assert_eq!(env.get("GH_TOKEN"), Some(&openai_dummy));
|
||||||
|
assert_eq!(env.get("OPENAI_API_KEY"), Some(&github_dummy));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -130,6 +177,119 @@ fn virtualize_child_env_preserves_live_dummy_mappings() {
|
|||||||
|
|
||||||
assert_eq!(authorization(&first_headers), Some("Bearer ghp-real-one"));
|
assert_eq!(authorization(&first_headers), Some("Bearer ghp-real-one"));
|
||||||
assert_eq!(authorization(&second_headers), Some("Bearer ghp-real-two"));
|
assert_eq!(authorization(&second_headers), Some("Bearer ghp-real-two"));
|
||||||
|
|
||||||
|
let mut alias_only = env_map([("HOMEBREW_GITHUB_API_TOKEN", "ghp-real-one")]);
|
||||||
|
broker.virtualize_child_env(&mut alias_only);
|
||||||
|
assert_eq!(
|
||||||
|
alias_only.get("HOMEBREW_GITHUB_API_TOKEN"),
|
||||||
|
Some(first_dummy)
|
||||||
|
);
|
||||||
|
broker.restore_child_env(&mut alias_only, &mut []);
|
||||||
|
assert_eq!(alias_only["HOMEBREW_GITHUB_API_TOKEN"], "ghp-real-one");
|
||||||
|
|
||||||
|
let mut overridden = env_map([
|
||||||
|
("GH_TOKEN", "ghp-real-two"),
|
||||||
|
("HOMEBREW_GITHUB_API_TOKEN", "ghp-real-one"),
|
||||||
|
]);
|
||||||
|
broker.virtualize_child_env(&mut overridden);
|
||||||
|
assert_eq!(overridden.get("GH_TOKEN"), Some(second_dummy));
|
||||||
|
assert_eq!(
|
||||||
|
overridden.get("HOMEBREW_GITHUB_API_TOKEN"),
|
||||||
|
Some(first_dummy)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut cloud_alias = env_map([
|
||||||
|
("GH_TOKEN", "ghp-real-one"),
|
||||||
|
("GITHUB_TOKEN", "ghp-real-one"),
|
||||||
|
]);
|
||||||
|
broker.virtualize_child_env(&mut cloud_alias);
|
||||||
|
cloud_alias.insert("GITHUB_TOKEN".to_string(), first_dummy.clone());
|
||||||
|
broker.restore_child_env(&mut cloud_alias, &mut []);
|
||||||
|
assert_eq!(cloud_alias["GITHUB_TOKEN"], "ghp-real-one");
|
||||||
|
|
||||||
|
let mut distinct_credentials = env_map([
|
||||||
|
("GH_TOKEN", "ghp-primary-secret"),
|
||||||
|
("GITHUB_TOKEN", "ghp-secondary-secret"),
|
||||||
|
]);
|
||||||
|
broker.virtualize_child_env(&mut distinct_credentials);
|
||||||
|
let secondary_dummy = distinct_credentials["GITHUB_TOKEN"].clone();
|
||||||
|
distinct_credentials.remove("GITHUB_TOKEN");
|
||||||
|
distinct_credentials.insert("GH_TOKEN".to_string(), secondary_dummy.clone());
|
||||||
|
broker.virtualize_child_env(&mut distinct_credentials);
|
||||||
|
broker.restore_child_env(&mut distinct_credentials, &mut []);
|
||||||
|
assert_eq!(distinct_credentials["GH_TOKEN"], secondary_dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn virtualize_child_env_replaces_aliases_of_filtered_parent_credentials() {
|
||||||
|
let broker = CredentialBroker::new(/*enabled*/ true);
|
||||||
|
let github_token = "ghp_abcdefghijklmnopqrstuvwxyz1234567890";
|
||||||
|
let authorization_header = format!("Bearer {github_token}");
|
||||||
|
let parent_env = env_map([
|
||||||
|
("GH_TOKEN", github_token),
|
||||||
|
("HOMEBREW_GITHUB_API_TOKEN", github_token),
|
||||||
|
]);
|
||||||
|
let mut child_env = env_map([
|
||||||
|
("HOMEBREW_GITHUB_API_TOKEN", github_token),
|
||||||
|
("AUTH_HEADER", authorization_header.as_str()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
broker.discover_parent_credentials(&parent_env, &child_env);
|
||||||
|
broker.virtualize_child_env(&mut child_env);
|
||||||
|
|
||||||
|
let dummy = child_env["HOMEBREW_GITHUB_API_TOKEN"].clone();
|
||||||
|
assert_ne!(dummy, github_token);
|
||||||
|
assert_eq!(child_env["AUTH_HEADER"], format!("Bearer {dummy}"));
|
||||||
|
assert!(!child_env.contains_key("GH_TOKEN"));
|
||||||
|
|
||||||
|
let mut headers = headers_with_bearer(&dummy);
|
||||||
|
broker.inject_request_headers("api.github.com", &mut headers);
|
||||||
|
assert_eq!(authorization(&headers), Some(authorization_header.as_str()));
|
||||||
|
|
||||||
|
broker.restore_child_env(&mut child_env, &mut []);
|
||||||
|
assert_eq!(child_env["HOMEBREW_GITHUB_API_TOKEN"], github_token);
|
||||||
|
assert_eq!(child_env["AUTH_HEADER"], authorization_header);
|
||||||
|
assert!(!child_env.contains_key("GH_TOKEN"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn virtualize_child_env_binds_filtered_enterprise_credentials_to_child_host() {
|
||||||
|
let github_token = "ghp_abcdefghijklmnopqrstuvwxyz1234567890";
|
||||||
|
let authorization_header = format!("Bearer {github_token}");
|
||||||
|
|
||||||
|
for (parent_host, include_cloud_token) in [
|
||||||
|
(None, false),
|
||||||
|
(Some("github.previous.example"), false),
|
||||||
|
(None, true),
|
||||||
|
] {
|
||||||
|
let broker = CredentialBroker::new(/*enabled*/ true);
|
||||||
|
let mut parent_env = env_map([("GH_ENTERPRISE_TOKEN", github_token)]);
|
||||||
|
if let Some(parent_host) = parent_host {
|
||||||
|
parent_env.insert("GH_HOST".to_string(), parent_host.to_string());
|
||||||
|
}
|
||||||
|
if include_cloud_token {
|
||||||
|
parent_env.insert("GH_TOKEN".to_string(), github_token.to_string());
|
||||||
|
}
|
||||||
|
let mut child_env = env_map([
|
||||||
|
("GH_HOST", "github.current.example"),
|
||||||
|
("AUTH_HEADER", authorization_header.as_str()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
broker.discover_parent_credentials(&parent_env, &child_env);
|
||||||
|
broker.virtualize_child_env(&mut child_env);
|
||||||
|
|
||||||
|
assert_ne!(child_env["AUTH_HEADER"], authorization_header);
|
||||||
|
let mut headers = headers_with_authorization(&child_env["AUTH_HEADER"]);
|
||||||
|
broker.inject_request_headers("github.current.example", &mut headers);
|
||||||
|
assert_eq!(authorization(&headers), Some(authorization_header.as_str()));
|
||||||
|
|
||||||
|
let mut previous_headers = headers_with_authorization(&child_env["AUTH_HEADER"]);
|
||||||
|
broker.inject_request_headers("github.previous.example", &mut previous_headers);
|
||||||
|
assert_eq!(
|
||||||
|
authorization(&previous_headers),
|
||||||
|
Some(child_env["AUTH_HEADER"].as_str())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -192,6 +352,12 @@ fn virtualize_child_env_preserves_unbound_enterprise_token() {
|
|||||||
assert_eq!(env["GH_ENTERPRISE_TOKEN"], "ghp-enterprise-real");
|
assert_eq!(env["GH_ENTERPRISE_TOKEN"], "ghp-enterprise-real");
|
||||||
assert_eq!(headers, headers_with_bearer(inert_token));
|
assert_eq!(headers, headers_with_bearer(inert_token));
|
||||||
assert!(!broker.host_requires_mitm("attacker.example"));
|
assert!(!broker.host_requires_mitm("attacker.example"));
|
||||||
|
|
||||||
|
env.insert("GH_HOST".to_string(), "github.example.com".to_string());
|
||||||
|
broker.virtualize_child_env(&mut env);
|
||||||
|
let mut headers = headers_with_bearer(&env["GH_ENTERPRISE_TOKEN"]);
|
||||||
|
broker.inject_request_headers("github.example.com", &mut headers);
|
||||||
|
assert_eq!(authorization(&headers), Some("Bearer ghp-enterprise-real"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -294,6 +460,7 @@ fn openai_credentials_bind_only_to_default_and_configured_trusted_hosts() {
|
|||||||
]);
|
]);
|
||||||
broker.virtualize_child_env(&mut env);
|
broker.virtualize_child_env(&mut env);
|
||||||
assert!(brokered_credential_env_keys(&env).any(|key| key == "OPENAI_BASE_URL"));
|
assert!(brokered_credential_env_keys(&env).any(|key| key == "OPENAI_BASE_URL"));
|
||||||
|
assert!(brokered_credential_binding_env_keys(&env).any(|key| key == "OPENAI_BASE_URL"));
|
||||||
let dummy = &env["OPENAI_API_KEY"];
|
let dummy = &env["OPENAI_API_KEY"];
|
||||||
|
|
||||||
for (host, expected_credential) in [
|
for (host, expected_credential) in [
|
||||||
@@ -330,6 +497,7 @@ fn github_cloud_credentials_match_ghe_com_host_hint() {
|
|||||||
let broker = CredentialBroker::new(/*enabled*/ true);
|
let broker = CredentialBroker::new(/*enabled*/ true);
|
||||||
let mut env = env_map([("GH_HOST", "astemu.ghe.com"), ("GH_TOKEN", "ghp-real")]);
|
let mut env = env_map([("GH_HOST", "astemu.ghe.com"), ("GH_TOKEN", "ghp-real")]);
|
||||||
broker.virtualize_child_env(&mut env);
|
broker.virtualize_child_env(&mut env);
|
||||||
|
assert!(!brokered_credential_binding_env_keys(&env).any(|key| key == "GH_HOST"));
|
||||||
let github_token = env.get("GH_TOKEN").expect("dummy GitHub token");
|
let github_token = env.get("GH_TOKEN").expect("dummy GitHub token");
|
||||||
let mut headers = headers_with_bearer(github_token);
|
let mut headers = headers_with_bearer(github_token);
|
||||||
|
|
||||||
@@ -362,18 +530,77 @@ fn github_enterprise_credentials_bind_to_gh_host() {
|
|||||||
let broker = CredentialBroker::new(/*enabled*/ true);
|
let broker = CredentialBroker::new(/*enabled*/ true);
|
||||||
let mut env = env_map([
|
let mut env = env_map([
|
||||||
("GH_HOST", "github.example.com"),
|
("GH_HOST", "github.example.com"),
|
||||||
|
("GH_TOKEN", "ghp-enterprise-real"),
|
||||||
("GH_ENTERPRISE_TOKEN", "ghp-enterprise-real"),
|
("GH_ENTERPRISE_TOKEN", "ghp-enterprise-real"),
|
||||||
|
("AUTH_HEADER", "Bearer ghp-enterprise-real"),
|
||||||
]);
|
]);
|
||||||
broker.virtualize_child_env(&mut env);
|
broker.virtualize_child_env(&mut env);
|
||||||
|
let github_dummy = env["GH_TOKEN"].clone();
|
||||||
assert!(brokered_credential_env_keys(&env).any(|key| key == "GH_HOST"));
|
assert!(brokered_credential_env_keys(&env).any(|key| key == "GH_HOST"));
|
||||||
|
assert!(brokered_credential_binding_env_keys(&env).any(|key| key == "GH_HOST"));
|
||||||
let github_token = env
|
let github_token = env
|
||||||
.get("GH_ENTERPRISE_TOKEN")
|
.get("GH_ENTERPRISE_TOKEN")
|
||||||
.expect("dummy GitHub enterprise token");
|
.expect("dummy GitHub enterprise token");
|
||||||
|
assert_ne!(github_token, &github_dummy);
|
||||||
let mut headers = headers_with_bearer(github_token);
|
let mut headers = headers_with_bearer(github_token);
|
||||||
|
|
||||||
broker.inject_request_headers("github.example.com", &mut headers);
|
broker.inject_request_headers("github.example.com", &mut headers);
|
||||||
|
|
||||||
assert_eq!(authorization(&headers), Some("Bearer ghp-enterprise-real"));
|
assert_eq!(authorization(&headers), Some("Bearer ghp-enterprise-real"));
|
||||||
|
assert_eq!(env["AUTH_HEADER"], format!("Bearer {github_token}"));
|
||||||
|
let mut alias_headers = headers_with_authorization(&env["AUTH_HEADER"]);
|
||||||
|
broker.inject_request_headers("github.example.com", &mut alias_headers);
|
||||||
|
assert_eq!(
|
||||||
|
authorization(&alias_headers),
|
||||||
|
Some("Bearer ghp-enterprise-real")
|
||||||
|
);
|
||||||
|
let mut persisted_alias = "Bearer ghp-enterprise-real".to_string();
|
||||||
|
assert!(broker.virtualize_text(&mut persisted_alias, &env));
|
||||||
|
assert_eq!(persisted_alias, format!("Bearer {github_token}"));
|
||||||
|
assert_eq!(
|
||||||
|
brokered_credential_dummy_env_keys(&env).first(),
|
||||||
|
Some(&"GH_ENTERPRISE_TOKEN".to_string())
|
||||||
|
);
|
||||||
|
let mut cloud_headers = headers_with_bearer(&github_dummy);
|
||||||
|
broker.inject_request_headers("github.example.com", &mut cloud_headers);
|
||||||
|
assert_eq!(cloud_headers, headers_with_bearer(&github_dummy));
|
||||||
|
let mut enterprise_headers = headers_with_bearer(github_token);
|
||||||
|
broker.inject_request_headers("api.github.com", &mut enterprise_headers);
|
||||||
|
assert_eq!(enterprise_headers, headers_with_bearer(github_token));
|
||||||
|
let mut cloud_only = env_map([
|
||||||
|
("GH_HOST", "github.example.com"),
|
||||||
|
("GH_TOKEN", "ghp-enterprise-real"),
|
||||||
|
("AUTH_HEADER", "Bearer ghp-enterprise-real"),
|
||||||
|
]);
|
||||||
|
broker.virtualize_child_env(&mut cloud_only);
|
||||||
|
assert_eq!(cloud_only["AUTH_HEADER"], format!("Bearer {github_dummy}"));
|
||||||
assert!(broker.host_requires_mitm("github.example.com"));
|
assert!(broker.host_requires_mitm("github.example.com"));
|
||||||
assert!(!broker.host_requires_mitm("api.github.com"));
|
assert!(broker.host_requires_mitm("api.github.com"));
|
||||||
|
|
||||||
|
env.insert("GH_HOST".to_string(), "attacker.example".to_string());
|
||||||
|
env.insert("GH_ENTERPRISE_TOKEN".to_string(), github_dummy.clone());
|
||||||
|
broker.virtualize_child_env(&mut env);
|
||||||
|
let mut attacker_headers = headers_with_bearer(&github_dummy);
|
||||||
|
broker.inject_request_headers("attacker.example", &mut attacker_headers);
|
||||||
|
assert_eq!(attacker_headers, headers_with_bearer(&github_dummy));
|
||||||
|
assert!(!broker.host_requires_mitm("attacker.example"));
|
||||||
|
|
||||||
|
let mut alternate_enterprise_key = env_map([
|
||||||
|
("GH_HOST", "github.alternate.example"),
|
||||||
|
("GH_TOKEN", "ghp-alternate-real"),
|
||||||
|
("GITHUB_ENTERPRISE_TOKEN", "ghp-alternate-real"),
|
||||||
|
("AUTH_HEADER", "Bearer ghp-alternate-real"),
|
||||||
|
]);
|
||||||
|
broker.virtualize_child_env(&mut alternate_enterprise_key);
|
||||||
|
assert_eq!(
|
||||||
|
alternate_enterprise_key["AUTH_HEADER"],
|
||||||
|
format!(
|
||||||
|
"Bearer {}",
|
||||||
|
alternate_enterprise_key["GITHUB_ENTERPRISE_TOKEN"]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
brokered_credential_dummy_env_keys(&alternate_enterprise_key).first(),
|
||||||
|
Some(&"GITHUB_ENTERPRISE_TOKEN".to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@ pub use config::NetworkUnixSocketPermissions;
|
|||||||
pub use config::host_and_port_from_network_addr;
|
pub use config::host_and_port_from_network_addr;
|
||||||
pub use config::managed_proxy_ports;
|
pub use config::managed_proxy_ports;
|
||||||
pub use credential_broker::CREDENTIAL_BROKER_ACTIVE_ENV_KEY;
|
pub use credential_broker::CREDENTIAL_BROKER_ACTIVE_ENV_KEY;
|
||||||
|
pub use credential_broker::brokered_credential_binding_env_keys;
|
||||||
pub use credential_broker::brokered_credential_dummy_env_keys;
|
pub use credential_broker::brokered_credential_dummy_env_keys;
|
||||||
pub use credential_broker::brokered_credential_env_keys;
|
pub use credential_broker::brokered_credential_env_keys;
|
||||||
pub use credential_broker::credential_broker_provider_context_env_keys;
|
pub use credential_broker::credential_broker_provider_context_env_keys;
|
||||||
|
pub use credential_broker::is_credential_broker_provider_env_key;
|
||||||
pub use environment_policy::EnvironmentNetworkPolicy;
|
pub use environment_policy::EnvironmentNetworkPolicy;
|
||||||
pub use mitm_hook::InjectedHeaderConfig;
|
pub use mitm_hook::InjectedHeaderConfig;
|
||||||
pub use mitm_hook::MitmHookActionsConfig;
|
pub use mitm_hook::MitmHookActionsConfig;
|
||||||
|
|||||||
@@ -1109,6 +1109,15 @@ impl NetworkProxy {
|
|||||||
self.state.restore_child_credentials(env, command);
|
self.state.restore_child_credentials(env, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replaces allowed credentials and removes credentials excluded from the environment.
|
||||||
|
pub fn virtualize_brokered_text(
|
||||||
|
&self,
|
||||||
|
text: &mut String,
|
||||||
|
env: &HashMap<String, String>,
|
||||||
|
) -> bool {
|
||||||
|
self.state.virtualize_brokered_text(text, env)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_to_env_for_environment(
|
pub fn apply_to_env_for_environment(
|
||||||
&self,
|
&self,
|
||||||
env: &mut HashMap<String, String>,
|
env: &mut HashMap<String, String>,
|
||||||
|
|||||||
@@ -427,6 +427,11 @@ impl NetworkProxyState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn virtualize_child_credentials(&self, env: &mut HashMap<String, String>) {
|
pub fn virtualize_child_credentials(&self, env: &mut HashMap<String, String>) {
|
||||||
|
let parent_env = std::env::vars_os()
|
||||||
|
.filter_map(|(key, value)| Some((key.into_string().ok()?, value.into_string().ok()?)))
|
||||||
|
.collect();
|
||||||
|
self.credential_broker
|
||||||
|
.discover_parent_credentials(&parent_env, env);
|
||||||
self.credential_broker.virtualize_child_env(env);
|
self.credential_broker.virtualize_child_env(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,6 +443,14 @@ impl NetworkProxyState {
|
|||||||
self.credential_broker.restore_child_env(env, command);
|
self.credential_broker.restore_child_env(env, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn virtualize_brokered_text(
|
||||||
|
&self,
|
||||||
|
text: &mut String,
|
||||||
|
env: &HashMap<String, String>,
|
||||||
|
) -> bool {
|
||||||
|
self.credential_broker.virtualize_text(text, env)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inject_request_credentials(&self, host: &str, headers: &mut rama_http::HeaderMap) {
|
pub fn inject_request_credentials(&self, host: &str, headers: &mut rama_http::HeaderMap) {
|
||||||
self.credential_broker.inject_request_headers(host, headers);
|
self.credential_broker.inject_request_headers(host, headers);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user