Enable masked AWS credentials for local proxy signing (#56554)

## What changed

- Expose `aws_sigv4` actions in version 2 MITM permissions configuration and the config schema, rejecting multiple signing actions per hook.
- Give local managed commands placeholder AWS credentials for the selected signing pair, and mask configured credentials in environment values and protected shell snapshots.
- Set `AWS_CA_BUNDLE` to the managed proxy CA bundle when unset, and require `features.credential_masking` for local AWS signing hooks.
- Validate placeholders against every configured signing pair to prevent overlap with real credentials.

## Testing

Extend configuration, signing, and TLS forwarding tests to cover action resolution, invalid signing configuration, credential masking, CA trust, snapshot protection, and exclusion of local AWS defaults from remote environments.

GitOrigin-RevId: 81dfdea30ebde74829882b38891ea41e9737058a
This commit is contained in:
viyatb-oai
2026-10-23 18:40:55 +00:00
committed by copyberry
parent c483edf177
commit 15fbcac045
15 changed files with 480 additions and 40 deletions
+49 -4
View File
@@ -1,3 +1,6 @@
use crate::aws_sigv4::AwsSigV4Signer;
use crate::aws_sigv4::mask_local_aws_env;
use crate::aws_sigv4::mask_local_aws_text;
use crate::config::NetworkDomainPermission;
use crate::config::NetworkMode;
use crate::config::NetworkProxyConfig;
@@ -189,6 +192,19 @@ pub struct ConfigState {
pub blocked_total: u64,
}
impl ConfigState {
fn local_aws_signers(&self) -> Vec<Arc<AwsSigV4Signer>> {
if !self.config.enabled || !self.config.credential_broker || !self.config.mitm {
return Vec::new();
}
self.mitm_hooks
.values()
.flatten()
.filter_map(|hook| hook.actions.aws_sigv4.clone())
.collect()
}
}
pub trait ConfigReloader: Send + Sync {
/// Human-readable description of where config is loaded from, for logs.
fn source_label(&self) -> String;
@@ -278,6 +294,7 @@ pub struct NetworkProxyState {
pub(crate) process_log_metadata: crate::NetworkProxyProcessLogMetadata,
/// Shared by this proxy's listeners; rebinding leaves other proxies' cached brokers intact.
credential_broker: Arc<std::sync::RwLock<CredentialBroker>>,
local_aws_signers: Arc<std::sync::RwLock<Vec<Arc<AwsSigV4Signer>>>>,
audit_metadata: NetworkProxyAuditMetadata,
execution_attributions: Arc<Mutex<HashMap<String, ExecutionAttribution>>>,
environment_id: Option<Arc<str>>,
@@ -317,6 +334,7 @@ impl Clone for NetworkProxyState {
launch_span_context: self.launch_span_context.clone(),
process_log_metadata: self.process_log_metadata.clone(),
credential_broker: self.credential_broker.clone(),
local_aws_signers: self.local_aws_signers.clone(),
audit_metadata: self.audit_metadata.clone(),
execution_attributions: self.execution_attributions.clone(),
environment_id: self.environment_id.clone(),
@@ -424,6 +442,7 @@ impl NetworkProxyState {
credential_broker.configure(&local_binding_policy.resolved_config(&state.config));
Self {
credential_broker: Arc::new(std::sync::RwLock::new(credential_broker)),
local_aws_signers: Arc::new(std::sync::RwLock::new(state.local_aws_signers())),
state: Arc::new(RwLock::new(state)),
local_binding_policy,
proxy_private_ips_via_upstream: false,
@@ -561,6 +580,27 @@ impl NetworkProxyState {
Ok(())
}
fn install_config(&self, current: &mut ConfigState, next: ConfigState) {
let mut signers = self
.local_aws_signers
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*signers = next.local_aws_signers();
*current = next;
}
pub(crate) fn mask_local_aws_credentials(
&self,
env: &mut HashMap<String, String>,
ca_path: Option<&Path>,
) {
let signers = self
.local_aws_signers
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
mask_local_aws_env(&signers, env, ca_path);
}
pub fn virtualize_child_credentials(&self, env: &mut HashMap<String, String>) {
self.virtualize_child_credentials_for_environment(env, self.environment_id());
}
@@ -624,6 +664,11 @@ impl NetworkProxyState {
text: &mut String,
env: &HashMap<String, String>,
) -> bool {
let signers = self
.local_aws_signers
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
mask_local_aws_text(&signers, text);
self.credential_broker().virtualize_text(text, env)
}
@@ -742,7 +787,7 @@ impl NetworkProxyState {
// Log policy diffs without dumping potentially sensitive config values.
log_policy_changes(&guard.config, &new_state.config);
new_state.blocked = guard.blocked.clone();
*guard = new_state;
self.install_config(&mut guard, new_state);
}
let source = self.reloader.source_label();
info!("reloaded config from {source}");
@@ -764,7 +809,7 @@ impl NetworkProxyState {
log_policy_changes(&guard.config, &new_state.config);
new_state.blocked = guard.blocked.clone();
new_state.blocked_total = guard.blocked_total;
*guard = new_state;
self.install_config(&mut guard, new_state);
info!("updated network proxy config state");
Ok(())
}
@@ -1176,7 +1221,7 @@ impl NetworkProxyState {
}
log_policy_changes(&guard.config, &candidate);
*guard = new_state;
self.install_config(&mut guard, new_state);
info!("updated network {list_name} with {normalized_host}");
return Ok(());
}
@@ -1193,7 +1238,7 @@ impl NetworkProxyState {
log_policy_changes(&guard.config, &new_state.config);
new_state.blocked = guard.blocked.clone();
new_state.blocked_total = guard.blocked_total;
*guard = new_state;
self.install_config(&mut guard, new_state);
}
let source = self.reloader.source_label();
info!("reloaded config from {source}");