Support workload identity in remote exec-server auth (#38610)

## Why

Remote exec-server registry requests need to refresh managed credentials before sending a request. Static auth-header resolution cannot perform the asynchronous token exchange required by workload identity.

## What changed

- Add asynchronous auth-header resolution to `AuthProvider`, with the existing static-header behavior as the default.
- Resolve fresh managed credentials for each remote environment registry request while preserving the expected account and workspace identity.
- Load the cloud configuration bundle during remote exec-server startup when workload identity is selected.

## Testing

- Update the managed-auth and environment-registry auth tests to exercise asynchronous header resolution.

GitOrigin-RevId: 5d60f1127467aaacdb5d1a8f3d92278bc4bf2e29
This commit is contained in:
cooper-oai
2026-08-14 17:22:54 +00:00
committed by copyberry
parent c0d59bf614
commit fb5aa093e0
6 changed files with 163 additions and 61 deletions

View File

@@ -129,18 +129,20 @@ impl EnvironmentRegistryClient {
environment_id: &str,
executor_public_key: &NoiseChannelPublicKey,
) -> Result<EnvironmentRegistryRegistrationResponse, ExecServerError> {
let url = endpoint_url(
&self.base_url,
&format!("/cloud/environment/{environment_id}/register"),
);
let body = EnvironmentRegistryRegistrationRequest {
security_profile: NOISE_RELAY_SECURITY_PROFILE.to_string(),
executor_public_key: executor_public_key.clone(),
};
let response = self
.http
.post(endpoint_url(
&self.base_url,
&format!("/cloud/environment/{environment_id}/register"),
))
.headers(self.auth_provider.to_auth_headers())
.post(url)
.headers(self.resolve_auth_headers().await?)
.headers(current_trace_context_headers())
.json(&EnvironmentRegistryRegistrationRequest {
security_profile: NOISE_RELAY_SECURITY_PROFILE.to_string(),
executor_public_key: executor_public_key.clone(),
})
.json(&body)
.send()
.await?;
let response: EnvironmentRegistryRegistrationResponse =
@@ -185,15 +187,17 @@ impl EnvironmentRegistryClient {
environment_id: &str,
harness_public_key: NoiseChannelPublicKey,
) -> Result<NoiseRendezvousConnectBundle, ExecServerError> {
let url = endpoint_url(
&self.base_url,
&format!("/cloud/environment/{environment_id}/connect"),
);
let body = EnvironmentRegistryConnectRequest { harness_public_key };
let response = self
.http
.post(endpoint_url(
&self.base_url,
&format!("/cloud/environment/{environment_id}/connect"),
))
.headers(self.auth_provider.to_auth_headers())
.post(url)
.headers(self.resolve_auth_headers().await?)
.headers(current_trace_context_headers())
.json(&EnvironmentRegistryConnectRequest { harness_public_key })
.json(&body)
.timeout(self.connect_timeout)
.send()
.await?;
@@ -227,6 +231,17 @@ impl EnvironmentRegistryClient {
})
}
async fn resolve_auth_headers(&self) -> Result<HeaderMap, ExecServerError> {
self.auth_provider
.resolve_auth_headers()
.await
.map_err(|error| {
ExecServerError::EnvironmentRegistryAuth(format!(
"failed to resolve environment registry authentication: {error}"
))
})
}
async fn parse_json_response<R>(&self, response: HttpResponse) -> Result<R, ExecServerError>
where
R: for<'de> Deserialize<'de>,
@@ -275,20 +290,22 @@ impl HarnessKeyValidator for RegistryHarnessKeyValidator {
authorization: &str,
) -> Result<(), ExecServerError> {
let environment_id = &self.environment_id;
let url = endpoint_url(
&self.client.base_url,
&format!("/cloud/environment/{environment_id}/validate"),
);
let body = EnvironmentRegistryHarnessKeyValidationRequest {
executor_registration_id: self.executor_registration_id.clone(),
harness_public_key: harness_public_key.clone(),
harness_key_authorization: authorization.to_string(),
};
let response = self
.client
.http
.post(endpoint_url(
&self.client.base_url,
&format!("/cloud/environment/{environment_id}/validate"),
))
.headers(self.client.auth_provider.to_auth_headers())
.post(url)
.headers(self.client.resolve_auth_headers().await?)
.headers(current_trace_context_headers())
.json(&EnvironmentRegistryHarnessKeyValidationRequest {
executor_registration_id: self.executor_registration_id.clone(),
harness_public_key: harness_public_key.clone(),
harness_key_authorization: authorization.to_string(),
})
.json(&body)
.send()
.await?;
let status = response.status();
@@ -791,15 +808,21 @@ mod tests {
struct StaticRegistryAuthProvider;
impl AuthProvider for StaticRegistryAuthProvider {
fn add_auth_headers(&self, headers: &mut HeaderMap) {
let _ = headers.insert(
http::header::AUTHORIZATION,
HeaderValue::from_static("Bearer registry-token"),
);
let _ = headers.insert(
"ChatGPT-Account-ID",
HeaderValue::from_static("workspace-123"),
);
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
fn resolve_auth_headers(&self) -> codex_api::AuthHeadersFuture<'_> {
Box::pin(async {
let mut headers = HeaderMap::new();
let _ = headers.insert(
http::header::AUTHORIZATION,
HeaderValue::from_static("Bearer registry-token"),
);
let _ = headers.insert(
"ChatGPT-Account-ID",
HeaderValue::from_static("workspace-123"),
);
Ok(headers)
})
}
}