Files
codex/codex-rs/network-proxy/src/credential_broker.rs
T
Winston Howes 1bfd383890 Add configurable credential providers to the network proxy (#44056)
## What changed

- Add `credential_providers` configuration for environment variables, credential patterns, static or environment-derived URL prefixes, and bearer, token, Basic, or custom header authentication.
- Generate matching dummy credentials and restrict replacement to authorized schemes, hosts, ports, and path prefixes. Scope credentials and destination history to each environment.
- Preserve real credentials for destinations that bypass the proxy with `allow_local_binding`, while providing snapshot redaction and alias matching.
- Validate provider definitions and preserve unchanged providers across configuration reloads.

## Testing

Add coverage for dummy generation, authentication translation, URL restrictions, environment isolation, configuration reloads, and credential restoration for local proxy bypass.

GitOrigin-RevId: 5d60ecc684cf4e9aa7637af33af12179aa0c672a
2026-09-09 04:59:01 +00:00

1169 lines
44 KiB
Rust

mod configured;
mod destination;
mod environment;
mod matching;
mod provider_config;
mod providers;
mod registry;
use crate::config::NetworkProxyConfig;
use crate::policy::normalize_host;
use environment::update_brokered_credentials_marker;
use rama_http::HeaderMap;
use registry::ActiveCredentialSource;
use registry::BrokeredCredentialProvider;
use registry::active_credential_sources;
use registry::prioritized_credentials;
use registry::select_credentials;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use url::Url;
pub use environment::brokered_credential_binding_env_keys;
pub use environment::brokered_credential_dummy_env_keys;
pub use environment::brokered_credential_env_keys;
pub use environment::brokered_credential_marker_env_keys;
pub use environment::brokered_credential_value_env_keys;
pub use environment::credential_broker_provider_context_env_keys;
pub use environment::credential_broker_provider_sources_allowed;
pub use environment::is_credential_broker_provider_env_key;
pub use provider_config::CredentialAuthMethod;
pub use provider_config::CredentialProviderConfig;
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";
const MIN_EMBEDDED_CREDENTIAL_LENGTH: usize = 16;
const BROKERED_CREDENTIAL_ALIAS_MARKER_PREFIX: &str = "@alias:";
#[derive(Clone)]
pub(crate) struct CredentialBroker {
state: Arc<RwLock<CredentialBrokerState>>,
}
#[derive(Default)]
struct CredentialBrokerState {
config_revision: u64,
enabled: bool,
allow_local_binding: bool,
openai_api_host: Option<String>,
configured_provider_configs: BTreeMap<String, CredentialProviderConfig>,
configured_providers: Vec<Arc<configured::ConfiguredCredentialProvider>>,
credentials: Vec<CredentialRecord>,
credential_owners: Vec<CredentialOwner>,
credential_aliases: Vec<CredentialAlias>,
}
struct CredentialOwner {
env_var: String,
real_value: String,
}
struct CredentialRecord {
env_var: String,
provider: BrokeredCredentialProvider,
host_binding: providers::CredentialHostBinding,
additional_host_bindings: Vec<providers::CredentialHostBinding>,
environment_id: Option<String>,
real_value: String,
dummy_value: String,
}
impl CredentialRecord {
fn should_reconcile_host_binding(&self, env: &HashMap<String, String>) -> bool {
env_contains_credential_value(env, &self.dummy_value)
|| self.invalidates_host_binding(env)
&& env.iter().any(|(key, value)| {
!env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY)
&& !env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY)
&& !crate::is_managed_proxy_env_var(key, value)
&& (value == &self.real_value
|| self
.provider
.contains_embedded_value(value, &self.real_value))
})
}
fn invalidates_host_binding(&self, env: &HashMap<String, String>) -> bool {
match &self.provider {
BrokeredCredentialProvider::Builtin(provider) => {
provider.sources().iter().any(|source| {
source
.env_vars
.iter()
.any(|key| env_key_matches(key, &self.env_var))
&& (source.invalidates_host_binding)(env)
})
}
BrokeredCredentialProvider::Configured(provider) => {
provider
.config
.url_prefix_from_env
.as_deref()
.is_some_and(|key| env_value(env, key).is_some())
&& provider.dynamic_destination(env).is_none()
}
}
}
fn observe_host_binding(
&mut self,
host_binding: providers::CredentialHostBinding,
env: &HashMap<String, String>,
) {
if self.invalidates_host_binding(env) {
self.additional_host_bindings.clear();
self.host_binding = host_binding;
return;
}
if self.host_binding == host_binding {
return;
}
// Track the latest source separately, but keep prior destinations usable by
// concurrent commands sharing this dummy in the same environment.
self.additional_host_bindings
.retain(|binding| binding != &host_binding);
self.additional_host_bindings
.push(std::mem::replace(&mut self.host_binding, host_binding));
}
fn host_bindings(&self) -> impl Iterator<Item = &providers::CredentialHostBinding> {
std::iter::once(&self.host_binding).chain(&self.additional_host_bindings)
}
fn belongs_to_environment(&self, environment_id: Option<&str>) -> bool {
self.environment_id.as_deref() == environment_id
}
}
fn source_accepts_credential(
source: &ActiveCredentialSource,
credential: &CredentialRecord,
) -> bool {
credential.provider.same_provider(&source.provider)
&& source
.env_vars
.iter()
.any(|env_var| env_key_matches(env_var, &credential.env_var))
}
fn source_tracks_credential(
source: &ActiveCredentialSource,
credential: &CredentialRecord,
env: &HashMap<String, String>,
) -> bool {
if !source_accepts_credential(source, credential) {
return false;
}
let mut source_present = false;
let contains_credential = source
.env_vars
.iter()
.filter_map(|env_var| env_value(env, env_var))
.any(|value| {
source_present = true;
let value = match &source.provider {
BrokeredCredentialProvider::Builtin(_) => value.trim(),
BrokeredCredentialProvider::Configured(_) => value,
};
value == credential.dummy_value || value == credential.real_value
});
!source_present || contains_credential
}
struct CredentialAlias {
env_var: String,
dummy_value: String,
}
enum CredentialEnvironment {
Child,
Snapshot,
}
fn env_key_matches(candidate: &str, expected: &str) -> bool {
if cfg!(windows) {
candidate.eq_ignore_ascii_case(expected)
} else {
candidate == expected
}
}
fn env_entry<'a>(env: &'a HashMap<String, String>, key: &str) -> Option<(&'a str, &'a str)> {
env.iter()
.find(|(candidate, _)| env_key_matches(candidate, key))
.map(|(key, value)| (key.as_str(), value.as_str()))
}
pub(super) fn env_value<'a>(env: &'a HashMap<String, String>, key: &str) -> Option<&'a str> {
env_entry(env, key).map(|(_, value)| value)
}
fn set_env_value(env: &mut HashMap<String, String>, key: &str, value: String) {
if cfg!(windows) {
env.retain(|candidate, _| !env_key_matches(candidate, key));
}
env.insert(key.to_string(), value);
}
fn remove_env_value(env: &mut HashMap<String, String>, key: &str) {
if cfg!(windows) {
env.retain(|candidate, _| !env_key_matches(candidate, key));
} else {
env.remove(key);
}
}
fn env_contains_credential_value(env: &HashMap<String, String>, credential: &str) -> bool {
env.iter().any(|(key, value)| {
!env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY)
&& !env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY)
&& value.contains(credential)
&& !crate::is_managed_proxy_env_var(key, value)
})
}
impl CredentialBroker {
pub(crate) fn new(enabled: bool) -> Self {
Self {
state: Arc::new(RwLock::new(CredentialBrokerState {
enabled,
..CredentialBrokerState::default()
})),
}
}
pub(crate) fn configure(&self, config: &NetworkProxyConfig) {
let mut state = self.write_state();
if state.enabled != config.credential_broker
|| state.openai_api_host != config.credential_broker_openai_host
|| state.allow_local_binding != config.allow_local_binding
|| state.configured_provider_configs != config.credential_providers
{
state.config_revision += 1;
}
state.allow_local_binding = config.allow_local_binding;
if state.enabled != config.credential_broker {
state.enabled = config.credential_broker;
state.credentials.clear();
state.credential_owners.clear();
state.credential_aliases.clear();
}
if state.openai_api_host != config.credential_broker_openai_host {
state
.openai_api_host
.clone_from(&config.credential_broker_openai_host);
state.credentials.retain(|credential| {
!matches!(
&credential.provider,
BrokeredCredentialProvider::Builtin(provider)
if provider.reset_on_configuration_change
)
});
}
if state.configured_provider_configs != config.credential_providers {
let mut configured_providers: Vec<Arc<configured::ConfiguredCredentialProvider>> =
Vec::new();
let mut provider_configs = config.credential_providers.iter().collect::<Vec<_>>();
provider_configs.sort_by_key(|(id, provider_config)| {
std::cmp::Reverse(
state.configured_provider_configs.get(id.as_str()) == Some(*provider_config)
&& state
.configured_providers
.iter()
.any(|provider| provider.id == id.as_str()),
)
});
for (id, provider_config) in provider_configs {
if configured_providers.iter().any(|existing| {
existing.config.env.iter().any(|existing_key| {
provider_config
.env
.iter()
.any(|key| env_key_matches(existing_key, key))
})
}) {
tracing::warn!(
provider = %id,
"ignoring credential provider with an overlapping environment source"
);
continue;
}
let existing = (state.configured_provider_configs.get(id) == Some(provider_config))
.then(|| {
state
.configured_providers
.iter()
.find(|provider| provider.id == *id)
.cloned()
})
.flatten();
match existing {
Some(provider) => configured_providers.push(provider),
None => {
match configured::ConfiguredCredentialProvider::compile(id, provider_config)
{
Ok(provider) => configured_providers.push(Arc::new(provider)),
Err(error) => {
tracing::warn!(provider = %id, %error, "ignoring invalid credential provider");
}
}
}
}
}
state
.credentials
.retain(|credential| match &credential.provider {
BrokeredCredentialProvider::Builtin(_) => true,
BrokeredCredentialProvider::Configured(provider) => configured_providers
.iter()
.any(|current| Arc::ptr_eq(current, provider)),
});
state.configured_providers = configured_providers;
state
.configured_provider_configs
.clone_from(&config.credential_providers);
}
}
pub(crate) fn config_revision(&self) -> u64 {
self.read_state().config_revision
}
#[cfg(test)]
pub(crate) fn discover_parent_credentials(
&self,
parent_env: &HashMap<String, String>,
child_env: &HashMap<String, String>,
) {
self.discover_parent_credentials_for_environment(
parent_env, child_env, /*environment_id*/ None,
);
}
pub(crate) fn discover_parent_credentials_for_environment(
&self,
parent_env: &HashMap<String, String>,
child_env: &HashMap<String, String>,
environment_id: Option<&str>,
) {
let mut state = self.write_state();
if !state.enabled {
return;
}
state.observe_credential_owners(parent_env);
for source in active_credential_sources(&state, child_env) {
for env_var in source.env_vars {
let Some(real_value) =
brokerable_credential_value(parent_env, &state, &env_var, &source.provider)
.map(str::to_string)
else {
continue;
};
if env_value(child_env, &env_var) != Some(real_value.as_str())
&& child_env.values().any(|value| {
value == &real_value
|| source.provider.contains_embedded_value(value, &real_value)
})
{
let _ = state.register(
&env_var,
source.provider.clone(),
source.host_binding.clone(),
environment_id,
&real_value,
parent_env,
);
}
}
}
}
#[cfg(test)]
pub(crate) fn virtualize_child_env(&self, env: &mut HashMap<String, String>) {
self.virtualize_child_env_for_environment(env, /*environment_id*/ None);
}
pub(crate) fn virtualize_child_env_for_environment(
&self,
env: &mut HashMap<String, String>,
environment_id: Option<&str>,
) {
self.virtualize_env(env, environment_id, CredentialEnvironment::Child);
}
pub(crate) fn virtualize_snapshot_env(
&self,
env: &mut HashMap<String, String>,
environment_id: Option<&str>,
) {
self.virtualize_env(env, environment_id, CredentialEnvironment::Snapshot);
}
fn virtualize_env(
&self,
env: &mut HashMap<String, String>,
environment_id: Option<&str>,
destination: CredentialEnvironment,
) {
let mut state = self.write_state();
if !state.enabled {
remove_env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY);
remove_env_value(env, BROKERED_CREDENTIALS_ENV_KEY);
return;
}
set_env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY, "1".to_string());
state.observe_credential_owners(env);
let active_sources = active_credential_sources(&state, env);
for credential in state.credentials.iter_mut().filter(|credential| {
credential.belongs_to_environment(environment_id)
&& credential.should_reconcile_host_binding(env)
}) {
if let Some(source) = active_sources
.iter()
.find(|source| source_tracks_credential(source, credential, env))
{
credential.observe_host_binding(source.host_binding.clone(), env);
}
}
let stale_credentials = state
.credentials
.iter()
.filter(|credential| {
credential.belongs_to_environment(environment_id)
&& credential.should_reconcile_host_binding(env)
&& !active_sources
.iter()
.any(|source| source_tracks_credential(source, credential, env))
})
.map(|credential| {
(
credential.dummy_value.clone(),
credential.real_value.clone(),
)
})
.collect::<Vec<_>>();
for (dummy_value, real_value) in &stale_credentials {
for value in env.values_mut() {
if value.contains(dummy_value) {
*value = value.replace(dummy_value, real_value);
}
}
}
state.credentials.retain(|credential| {
!credential.belongs_to_environment(environment_id)
|| !stale_credentials
.iter()
.any(|(dummy_value, _)| credential.dummy_value == *dummy_value)
});
let unbound_inherited_credentials = state
.credentials
.iter()
.filter(|credential| {
!credential.belongs_to_environment(environment_id)
&& env_contains_credential_value(env, &credential.dummy_value)
&& !active_sources
.iter()
.any(|source| source_accepts_credential(source, credential))
})
.map(|credential| {
(
credential.dummy_value.clone(),
credential.real_value.clone(),
)
})
.collect::<Vec<_>>();
for (dummy_value, real_value) in unbound_inherited_credentials {
for value in env.values_mut() {
if value.contains(&dummy_value) {
*value = value.replace(&dummy_value, &real_value);
}
}
}
let inherited_credentials = active_sources
.iter()
.flat_map(|source| {
state
.credentials
.iter()
.filter(|&credential| {
!credential.belongs_to_environment(environment_id)
&& source_accepts_credential(source, credential)
&& env_contains_credential_value(env, &credential.dummy_value)
})
.map(|credential| {
(
credential.env_var.clone(),
source.provider.clone(),
source.host_binding.clone(),
credential.real_value.clone(),
credential.dummy_value.clone(),
)
})
})
.collect::<Vec<_>>();
for (env_var, provider, host_binding, real_value, inherited_dummy) in inherited_credentials
{
let current_dummy = if let Some(credential) =
state.credentials.iter_mut().find(|credential| {
credential.belongs_to_environment(environment_id)
&& credential.provider.same_provider(&provider)
&& env_key_matches(&credential.env_var, &env_var)
&& credential.real_value == real_value
}) {
credential.observe_host_binding(host_binding, env);
credential.dummy_value.clone()
} else {
state.credentials.push(CredentialRecord {
env_var,
provider,
host_binding,
additional_host_bindings: Vec::new(),
environment_id: environment_id.map(str::to_string),
real_value,
dummy_value: inherited_dummy.clone(),
});
inherited_dummy.clone()
};
if current_dummy != inherited_dummy {
for value in env.values_mut() {
if value.contains(&inherited_dummy) {
*value = value.replace(&inherited_dummy, &current_dummy);
}
}
}
}
for source in &active_sources {
for env_var in &source.env_vars {
virtualize_env_var(
env,
&mut state,
env_var,
source.provider.clone(),
source.host_binding.clone(),
environment_id,
);
}
}
let provider_context_keys = credential_broker_provider_context_env_keys()
.map(str::to_string)
.chain(
state
.configured_providers
.iter()
.filter_map(|provider| provider.config.url_prefix_from_env.as_ref().cloned()),
)
.collect::<Vec<_>>();
let discoverable_values = env
.iter()
.filter(|(key, value)| {
!key.eq_ignore_ascii_case("PATH")
&& !key.to_ascii_uppercase().ends_with("_PATH")
&& !crate::is_managed_proxy_env_var(key, value)
&& !provider_context_keys
.iter()
.any(|context_key| env_key_matches(key, context_key))
})
.map(|(_, value)| value)
.collect::<Vec<_>>();
for provider in providers::credential_providers() {
let brokered_provider = BrokeredCredentialProvider::Builtin(provider);
let Some((source, host_binding)) = provider.sources().iter().rev().find_map(|source| {
(source.host_binding)(env, state.openai_api_host.as_deref())
.map(|binding| (source, binding))
}) else {
continue;
};
for value in &discoverable_values {
for prefix in provider.credential_prefixes {
for (start, _) in value.match_indices(prefix) {
let credential =
matching::builtin_credential_candidate(provider, value, start);
if credential.len() < provider.minimum_credential_len
|| state
.configured_providers
.iter()
.any(|configured| configured.matches_value(credential))
|| matching::is_operational_path_match(
value,
start,
start + credential.len(),
)
|| provider.ignored_credential_prefixes.iter().any(|ignored| {
credential.starts_with(ignored)
&& provider
.credential_watermark
.is_none_or(|watermark| !credential.contains(watermark))
})
|| state.is_dummy_value(credential)
|| state.credentials.iter().any(|existing| {
existing.belongs_to_environment(environment_id)
&& existing.provider.same_provider(&brokered_provider)
&& existing.host_binding == host_binding
&& existing.real_value == credential
})
|| source.binding_env_vars.is_empty()
&& state.credential_owners.iter().any(|existing| {
!source
.env_vars
.iter()
.any(|key| env_key_matches(key, &existing.env_var))
&& existing.real_value == credential
})
|| provider.sources().iter().any(|candidate| {
candidate
.env_vars
.iter()
.any(|key| env_value(env, key) == Some(credential))
&& (candidate.host_binding)(
env,
state.openai_api_host.as_deref(),
)
.is_none()
})
|| provider.request_header_value(credential).is_none()
{
continue;
}
let _ = state.register(
source.env_vars[0],
brokered_provider.clone(),
host_binding.clone(),
environment_id,
credential,
env,
);
}
}
}
}
let credentials = prioritized_credentials(&state, env)
.into_iter()
.filter(|credential| {
credential.belongs_to_environment(environment_id)
&& active_sources.iter().any(|source| {
credential.provider.same_provider(&source.provider)
&& credential.host_binding == source.host_binding
})
})
.collect::<Vec<_>>();
let mut credential_aliases = Vec::new();
for (key, value) in env.iter_mut() {
if crate::is_managed_proxy_env_var(key, value) {
continue;
}
let mut virtualized = false;
for credential in &credentials {
if value == &credential.real_value {
value.clone_from(&credential.dummy_value);
virtualized = true;
} else if credential
.provider
.contains_embedded_value(value, &credential.real_value)
{
*value = credential.provider.replace_embedded_value(
value,
&credential.real_value,
&credential.dummy_value,
);
virtualized = true;
} else if value.contains(&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);
}
}
if state.allow_local_binding && matches!(destination, CredentialEnvironment::Child) {
// Clients bypass the proxy for local destinations, so their credentials must stay real.
state.restore_child_env(env, |credential| {
credential.belongs_to_environment(environment_id)
&& credential
.host_bindings()
.any(providers::CredentialHostBinding::bypasses_proxy_with_local_binding)
});
}
update_brokered_credentials_marker(&state, env);
}
pub(crate) fn restore_child_env(
&self,
env: &mut HashMap<String, String>,
_command: &mut [String],
) {
let state = self.read_state();
if !state.enabled || env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY) != Some("1") {
return;
}
state.restore_child_env(env, |_| true);
}
pub(crate) fn restore_and_disable_child_env(
&self,
env: &mut HashMap<String, String>,
command: &mut [String],
) {
self.restore_child_env(env, command);
remove_env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY);
remove_env_value(env, BROKERED_CREDENTIALS_ENV_KEY);
}
pub(crate) fn child_alias_matches(
&self,
key: &str,
value: &str,
snapshot_value: &str,
environment_id: Option<&str>,
) -> bool {
let state = self.read_state();
if !state.enabled {
return false;
}
let mut expected = HashMap::from([(key.to_string(), snapshot_value.to_string())]);
state.restore_child_env(&mut expected, |_| true);
let referenced_credentials = |text: &str| {
state
.credentials
.iter()
.filter(|credential| {
text == credential.dummy_value
|| credential
.provider
.contains_embedded_value(text, &credential.dummy_value)
})
.collect::<Vec<_>>()
};
let expected_credentials = referenced_credentials(snapshot_value);
if expected_credentials.is_empty() {
return false;
}
state.credential_aliases.iter().any(|alias| {
if !env_key_matches(key, &alias.env_var) {
return false;
}
let alias_credentials = referenced_credentials(&alias.dummy_value);
let same_owner = |left: &&CredentialRecord, right: &&CredentialRecord| {
left.provider.same_provider(&right.provider)
&& env_key_matches(&left.env_var, &right.env_var)
&& left.real_value == right.real_value
};
if !expected_credentials.iter().all(|expected| {
alias_credentials
.iter()
.any(|actual| same_owner(expected, actual))
}) || !alias_credentials.iter().all(|actual| {
expected_credentials
.iter()
.any(|expected| same_owner(expected, actual))
}) {
return false;
}
let mut candidate = HashMap::from([(key.to_string(), alias.dummy_value.clone())]);
let mut identity = candidate.clone();
if state.allow_local_binding {
state.restore_child_env(&mut candidate, |credential| {
credential.belongs_to_environment(environment_id)
&& credential.host_bindings().any(
providers::CredentialHostBinding::bypasses_proxy_with_local_binding,
)
});
}
if env_value(&candidate, key) != Some(value) {
return false;
}
state.restore_child_env(&mut identity, |_| true);
identity == expected
})
}
#[cfg(test)]
pub(crate) fn host_requires_mitm(&self, host: &str, port: u16) -> bool {
self.host_requires_mitm_for_environment(host, port, /*environment_id*/ None)
}
pub(crate) fn host_requires_mitm_for_environment(
&self,
host: &str,
port: u16,
environment_id: Option<&str>,
) -> bool {
let normalized_host = normalize_host(host);
let state = self.read_state();
state.enabled
&& state.credentials.iter().any(|credential| {
credential.belongs_to_environment(environment_id)
&& credential
.host_bindings()
.any(|binding| binding.requires_mitm(&normalized_host, port))
})
}
pub(crate) fn virtualize_text(&self, text: &mut String, env: &HashMap<String, String>) -> bool {
let state = self.read_state();
matching::virtualize_text(&state, text, env)
}
pub(crate) fn restore_text(&self, text: &mut String) -> bool {
let state = self.read_state();
if !state.enabled {
return false;
}
let mut credentials = state.credentials.iter().collect::<Vec<_>>();
credentials
.sort_unstable_by_key(|credential| std::cmp::Reverse(credential.dummy_value.len()));
let mut restored = false;
for credential in credentials {
if text.as_str() == credential.dummy_value {
text.clone_from(&credential.real_value);
restored = true;
} else if credential
.provider
.contains_embedded_value(text, &credential.dummy_value)
{
*text = credential.provider.replace_embedded_value(
text,
&credential.dummy_value,
&credential.real_value,
);
restored = true;
}
}
restored
}
#[cfg(test)]
pub(crate) fn inject_request_headers(&self, destination: &str, headers: &mut HeaderMap) {
self.inject_request_headers_for_environment(
destination,
headers,
/*environment_id*/ None,
);
}
pub(crate) fn inject_request_headers_for_environment(
&self,
destination: &str,
headers: &mut HeaderMap,
environment_id: Option<&str>,
) {
let request = if destination.contains("://") {
let Ok(request) = Url::parse(destination) else {
return;
};
Some(request)
} else {
None
};
let normalized_host = normalize_host(
request
.as_ref()
.and_then(Url::host_str)
.unwrap_or(destination),
);
let state = self.read_state();
if !state.enabled {
return;
}
let credentials = select_credentials(
headers,
&normalized_host,
request.as_ref(),
&state.credentials,
environment_id,
);
if credentials.iter().any(|(credential, _, _)| {
matches!(
&credential.provider,
BrokeredCredentialProvider::Configured(_)
)
}) && let Some(request) = request.as_ref()
{
let Ok(raw_request) = destination.parse::<rama_http::Uri>() else {
return;
};
let raw_path = raw_request.path();
if raw_path != request.path()
|| !crate::authorization_path::is_safe_for_authorization(raw_path)
{
return;
}
}
for (credential, header_name, header_value) in credentials {
credential
.provider
.insert_request_header(headers, header_name, header_value);
}
}
fn read_state(&self) -> std::sync::RwLockReadGuard<'_, CredentialBrokerState> {
self.state
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write_state(&self) -> std::sync::RwLockWriteGuard<'_, CredentialBrokerState> {
self.state
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
}
fn virtualize_env_var(
env: &mut HashMap<String, String>,
state: &mut CredentialBrokerState,
env_var: &str,
provider: BrokeredCredentialProvider,
host_binding: providers::CredentialHostBinding,
environment_id: Option<&str>,
) {
let previous_dummy = env_value(env, env_var)
.filter(|value| state.is_dummy_value(value))
.map(str::to_string);
let Some(real_value) = brokerable_credential_value(env, state, env_var, &provider)
.map(str::to_string)
.or_else(|| {
let dummy = previous_dummy.as_deref()?;
state
.credentials
.iter()
.find(|credential| {
credential.dummy_value == dummy
&& credential.provider.same_provider(&provider)
&& env_key_matches(&credential.env_var, env_var)
})
.map(|credential| credential.real_value.clone())
})
else {
return;
};
if let Some(dummy_value) = state.register(
env_var,
provider,
host_binding,
environment_id,
&real_value,
env,
) {
if let Some(previous_dummy) = previous_dummy
&& previous_dummy != dummy_value
{
for value in env.values_mut() {
if value.contains(&previous_dummy) {
*value = value.replace(&previous_dummy, &dummy_value);
}
}
}
set_env_value(env, env_var, dummy_value);
}
}
fn brokerable_credential_value<'a>(
env: &'a HashMap<String, String>,
state: &CredentialBrokerState,
env_var: &str,
provider: &BrokeredCredentialProvider,
) -> Option<&'a str> {
let real_value = env_value(env, env_var)?;
let real_value = match provider {
BrokeredCredentialProvider::Builtin(_) => real_value.trim(),
BrokeredCredentialProvider::Configured(_) => real_value,
};
(!real_value.is_empty()
&& !state.is_dummy_value(real_value)
&& provider.request_header_value(real_value).is_some())
.then_some(real_value)
}
impl CredentialBrokerState {
fn restore_child_env(
&self,
env: &mut HashMap<String, String>,
should_restore: impl Fn(&CredentialRecord) -> bool,
) {
let credentials = self
.credentials
.iter()
.filter(|credential| should_restore(credential))
.filter(|credential| {
env.iter().any(|(key, value)| {
!env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY)
&& !env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY)
&& (value == &credential.dummy_value
|| credential
.provider
.contains_embedded_value(value, &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 = self
.credentials
.iter()
.any(|credential| env_key_matches(key, &credential.env_var));
if !canonical_credential
&& !self.credential_aliases.iter().any(|alias| {
env_key_matches(key, &alias.env_var) && value == &alias.dummy_value
})
{
continue;
}
for credential in &credentials {
if canonical_credential
&& !self.credentials.iter().any(|candidate| {
env_key_matches(key, &candidate.env_var)
&& candidate.provider.same_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
.provider
.contains_embedded_value(value, &credential.dummy_value)
{
*value = credential.provider.replace_embedded_value(
value,
&credential.dummy_value,
&credential.real_value,
);
}
}
}
}
fn observe_credential_owners(&mut self, env: &HashMap<String, String>) {
// Ownership is known even when a destination is missing or invalid.
let sources = providers::credential_providers()
.flat_map(|provider| {
provider.sources().iter().flat_map(move |source| {
source
.env_vars
.iter()
.map(move |key| (*key, BrokeredCredentialProvider::Builtin(provider)))
})
})
.chain(self.configured_providers.iter().flat_map(|provider| {
provider.config.env.iter().map(move |key| {
(
key.as_str(),
BrokeredCredentialProvider::Configured(Arc::clone(provider)),
)
})
}));
let owners = sources
.filter_map(|(key, provider)| {
brokerable_credential_value(env, self, key, &provider)
.map(|value| (key.to_string(), value.to_string()))
})
.collect::<Vec<_>>();
for (key, value) in owners {
self.remember_credential_owner(&key, &value);
}
}
fn remember_credential_owner(&mut self, env_var: &str, real_value: &str) {
if !self
.credential_owners
.iter()
.any(|owner| env_key_matches(&owner.env_var, env_var) && owner.real_value == real_value)
{
self.credential_owners.push(CredentialOwner {
env_var: env_var.to_string(),
real_value: real_value.to_string(),
});
}
}
fn register(
&mut self,
env_var: &str,
provider: BrokeredCredentialProvider,
host_binding: providers::CredentialHostBinding,
environment_id: Option<&str>,
real_value: &str,
existing_env: &HashMap<String, String>,
) -> Option<String> {
self.remember_credential_owner(env_var, real_value);
if let Some(existing) = self.credentials.iter_mut().find(|credential| {
env_key_matches(&credential.env_var, env_var)
&& credential.provider.same_provider(&provider)
&& credential.belongs_to_environment(environment_id)
&& credential.real_value == real_value
}) {
existing.observe_host_binding(host_binding, existing_env);
return Some(existing.dummy_value.clone());
}
let Some(dummy_value) = (0..64).find_map(|_| {
let candidate = provider.dummy_value(real_value)?;
(candidate != real_value
&& !existing_env
.values()
.any(|value| value.contains(&candidate))
&& !self.credentials.iter().any(|credential| {
credential.dummy_value == candidate || credential.real_value == candidate
}))
.then_some(candidate)
}) else {
tracing::warn!(
env_var,
"credential brokerage skipped: unable to generate a unique dummy credential"
);
return None;
};
self.credentials.push(CredentialRecord {
env_var: env_var.to_string(),
provider,
host_binding,
additional_host_bindings: Vec::new(),
environment_id: environment_id.map(str::to_string),
real_value: real_value.to_string(),
dummy_value: dummy_value.clone(),
});
Some(dummy_value)
}
fn is_dummy_value(&self, value: &str) -> bool {
self.credentials
.iter()
.any(|credential| credential.dummy_value == value)
}
}
#[cfg(test)]
#[path = "credential_broker_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "credential_broker/configured_tests.rs"]
mod configured_tests;