use super::*;
use crate::{
sandbox_egress::tests::Harness,
traits::{CreateSandboxRequest, Sandbox as SandboxTrait},
};
use alien_azure_clients::azure::sandbox_data_plane::{CreateSandbox, MockSandboxDataPlaneApi};
use alien_client_core::ErrorData as ClientError;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use std::sync::Mutex;
type Record = Arc<Mutex<Option<Sandbox>>>;
type Request = Arc<Mutex<Option<CreateSandbox>>>;
fn readback_policy(policy: &EgressPolicy) -> EgressPolicy {
let mut value = serde_json::to_value(policy).unwrap();
value["enforcementMode"] = serde_json::json!("Enforced");
value["http"] = value.clone();
serde_json::from_value(value).unwrap()
}
fn provider(manager: SandboxEgressManager, corrupt: bool) -> (AzureSandbox, Record, Request) {
let record: Record = Arc::new(Mutex::new(None));
let request: Request = Arc::new(Mutex::new(None));
let mut client = MockSandboxDataPlaneApi::new();
let saved = record.clone();
let submitted = request.clone();
client
.expect_create_sandbox()
.times(1)
.returning(move |_, req| {
assert_eq!(
req.customer_vnet_connection_name.as_deref(),
Some("default")
);
let mut labels = req.labels.clone();
if corrupt {
labels.remove(IDENTITY_LABEL);
}
let sandbox = Sandbox {
id: "vendor-id".into(),
labels,
vnet_connection_name: Some("default".into()),
egress_policy: req.egress.as_ref().map(readback_policy),
state: Some("Running".into()),
};
*submitted.lock().unwrap() = Some(req);
*saved.lock().unwrap() = Some(sandbox.clone());
Ok(sandbox)
});
let saved = record.clone();
client.expect_get_sandbox().returning(move |_, id| {
assert_eq!(id, "vendor-id");
saved.lock().unwrap().clone().ok_or_else(|| {
AlienError::new(ClientError::HttpResponseError {
message: "not found".into(),
url: "https://example.invalid/sandboxes/vendor-id".into(),
http_status: 404,
http_request_text: None,
http_response_text: None,
})
})
});
let saved = record.clone();
client
.expect_delete_sandbox()
.times(1)
.returning(move |_, id| {
assert_eq!(id, "vendor-id");
saved.lock().unwrap().take();
Ok(())
});
let sandbox = AzureSandbox::new(
Arc::new(client),
"group".into(),
"ubuntu".into(),
alien_core::SandboxEgress::Internet,
None,
"1000m".into(),
"2048Mi".into(),
None,
)
.with_egress_manager(
Arc::new(manager),
Some(RuntimePolicy::Allow {
allow: vec!["api.example.com".into()],
}),
);
(sandbox, record, request)
}
#[tokio::test]
async fn durable_identity_drives_create_reconnect_update_and_termination() {
let h = Harness::new().await;
let (sandbox, record, request) = provider(h.reopen().await, false);
let created = sandbox
.create(CreateSandboxRequest {
env: BTreeMap::from([
("HTTPS_PROXY".into(), "http://wrong.invalid".into()),
("no_proxy".into(), "*".into()),
]),
..Default::default()
})
.await
.unwrap();
assert_eq!(created.sandbox_id, "vendor-id");
let req = request.lock().unwrap().clone().unwrap();
let id = req.labels[IDENTITY_LABEL].clone();
assert_ne!(id, "vendor-id");
let proxy = req.environment["HTTPS_PROXY"]
.strip_prefix("http://alien:")
.unwrap();
let token = proxy.split('@').next().unwrap();
let claims: serde_json::Value = serde_json::from_slice(
&URL_SAFE_NO_PAD
.decode(token.split('.').nth(1).unwrap())
.unwrap(),
)
.unwrap();
assert_eq!(claims["sub"], id);
assert_eq!(
claims["exp"].as_i64().unwrap(),
req.labels[SANDBOX_EXPIRY_LABEL].parse::<i64>().unwrap() / 1000
);
assert!(
claims["exp"].as_i64().unwrap() - claims["iat"].as_i64().unwrap()
<= i64::from(alien_core::AZURE_GATEWAY_MAX_LIFETIME_SECONDS)
);
assert_eq!(req.environment["no_proxy"], "localhost,127.0.0.1");
assert_eq!(
req.environment["http_proxy"],
req.environment["HTTPS_PROXY"]
);
assert_eq!(
h.policy(&id),
Some(RuntimePolicy::Allow {
allow: vec!["api.example.com".into()]
})
);
assert_eq!(h.policy("vendor-id"), None);
let floor = req.egress.unwrap();
assert_eq!(floor.default_action, "Allow");
assert_eq!(floor.traffic_inspection.as_deref(), Some("Partial"));
assert_eq!(floor.host_rules[0].pattern, "127.0.0.1:3128");
assert_eq!(floor.host_rules.len(), 3);
assert_eq!(floor.host_rules[1].action, "Allow");
assert_eq!(
floor.host_rules[1].pattern,
h.reopen().await.gateway_admin_authority()
);
assert_eq!(floor.host_rules[2].action, "Deny");
assert_eq!(floor.host_rules[2].pattern, "*");
let reopened = AzureSandbox {
egress_manager: Some(Arc::new(h.reopen().await)),
..sandbox
};
let empty = RuntimePolicy::Allow { allow: vec![] };
reopened
.update_egress("vendor-id", empty.clone())
.await
.unwrap();
assert_eq!(h.policy(&id), Some(empty.clone()));
let preserved = reopened
.get_or_create(CreateSandboxRequest {
sandbox_id: Some("vendor-id".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(preserved.sandbox.sandbox_id, "vendor-id");
assert_eq!(h.policy(&id), Some(empty));
let resolved = reopened
.get_or_create(CreateSandboxRequest {
sandbox_id: Some("vendor-id".into()),
egress: Some(RuntimePolicy::Internet),
..Default::default()
})
.await
.unwrap();
assert_eq!(resolved.sandbox.sandbox_id, "vendor-id");
assert_eq!(h.policy(&id), Some(RuntimePolicy::Internet));
record.lock().unwrap().as_mut().unwrap().state = Some("Suspended".into());
reopened
.update_egress("vendor-id", RuntimePolicy::Allow { allow: vec![] })
.await
.unwrap();
assert_eq!(h.policy(&id), Some(RuntimePolicy::Allow { allow: vec![] }));
reopened.terminate("vendor-id").await.unwrap();
assert!(record.lock().unwrap().is_none());
assert_eq!(h.policy(&id), None);
}
#[tokio::test]
async fn total_gateway_state_loss_requires_an_explicit_policy() {
let h = Harness::new().await;
let (sandbox, _, request) = provider(h.reopen().await, false);
sandbox
.create(CreateSandboxRequest::default())
.await
.unwrap();
let id = request.lock().unwrap().as_ref().unwrap().labels[IDENTITY_LABEL].clone();
let empty = RuntimePolicy::Allow { allow: vec![] };
sandbox
.update_egress("vendor-id", empty.clone())
.await
.unwrap();
h.lose_policy_state();
h.refuse_writes(true);
assert!(sandbox.get("vendor-id").await.unwrap().is_some());
let found = sandbox
.get_or_create(CreateSandboxRequest {
sandbox_id: Some("vendor-id".into()),
..Default::default()
})
.await
.unwrap();
assert_eq!(found.sandbox.sandbox_id, "vendor-id");
assert_eq!(h.policy(&id), None);
h.refuse_writes(false);
sandbox
.update_egress("vendor-id", empty.clone())
.await
.unwrap();
assert_eq!(h.policy(&id), Some(empty.clone()));
h.lose_policy_state();
sandbox
.get_or_create(CreateSandboxRequest {
sandbox_id: Some("vendor-id".into()),
egress: Some(empty.clone()),
..Default::default()
})
.await
.unwrap();
assert_eq!(h.policy(&id), Some(empty));
sandbox.terminate("vendor-id").await.unwrap();
}
#[tokio::test]
async fn missing_identity_on_readback_discards_the_sandbox_without_publishing_policy() {
let h = Harness::new().await;
let (sandbox, record, request) = provider(h.reopen().await, true);
let error = sandbox
.create(CreateSandboxRequest::default())
.await
.unwrap_err();
assert_eq!(error.code, "SANDBOX_NOT_AS_DECLARED");
assert!(record.lock().unwrap().is_none());
let id = request.lock().unwrap().as_ref().unwrap().labels[IDENTITY_LABEL].clone();
assert_eq!(h.policy(&id), None);
}
#[tokio::test]
async fn a_changed_native_floor_is_refused_before_policy_updates() {
let h = Harness::new().await;
let (sandbox, record, request) = provider(h.reopen().await, false);
sandbox
.create(CreateSandboxRequest::default())
.await
.unwrap();
let id = request.lock().unwrap().as_ref().unwrap().labels[IDENTITY_LABEL].clone();
record
.lock()
.unwrap()
.as_mut()
.unwrap()
.egress_policy
.as_mut()
.unwrap()
.host_rules
.reverse();
let error = sandbox
.update_egress("vendor-id", RuntimePolicy::Internet)
.await
.unwrap_err();
assert_eq!(error.code, "SANDBOX_NOT_AS_DECLARED");
assert_eq!(
h.policy(&id),
Some(RuntimePolicy::Allow {
allow: vec!["api.example.com".into()]
})
);
sandbox.terminate("vendor-id").await.unwrap();
assert_eq!(h.policy(&id), None);
}
#[tokio::test]
async fn admin_failure_discards_create_and_does_not_prevent_termination() {
let h = Harness::new().await;
let (sandbox, record, request) = provider(h.reopen().await, false);
h.refuse_writes(true);
sandbox
.create(CreateSandboxRequest::default())
.await
.expect_err("policy registration refused");
assert!(record.lock().unwrap().is_none());
let id = request.lock().unwrap().as_ref().unwrap().labels[IDENTITY_LABEL].clone();
assert_eq!(h.policy(&id), None);
h.refuse_writes(false);
let (sandbox, record, request) = provider(h.reopen().await, false);
sandbox
.create(CreateSandboxRequest::default())
.await
.unwrap();
let id = request.lock().unwrap().as_ref().unwrap().labels[IDENTITY_LABEL].clone();
h.refuse_writes(true);
sandbox
.terminate("vendor-id")
.await
.expect_err("revocation failure reported");
assert!(
record.lock().unwrap().is_none(),
"vendor deletion still completes"
);
assert!(
h.policy(&id).is_some(),
"failed revocation is not claimed as success"
);
}
#[tokio::test]
async fn gateway_readback_refuses_changed_or_unknown_enforcement() {
let h = Harness::new().await;
let (sandbox, record, _) = provider(h.reopen().await, false);
sandbox
.create(CreateSandboxRequest::default())
.await
.unwrap();
let expected = record
.lock()
.unwrap()
.as_ref()
.unwrap()
.egress_policy
.clone()
.unwrap();
let changes = [
("/enforcementMode", serde_json::json!("Audit")),
("/http/enforcementMode", serde_json::json!("Audit")),
("/http/defaultAction", serde_json::json!("Deny")),
("/http/hostRules/2/action", serde_json::json!("Allow")),
("/hostRules/0/pattern", serde_json::json!("*")),
("/http", serde_json::json!(null)),
];
for (path, value) in changes {
let mut altered = serde_json::to_value(&expected).unwrap();
*altered.pointer_mut(path).unwrap() = value;
record.lock().unwrap().as_mut().unwrap().egress_policy =
Some(serde_json::from_value(altered).unwrap());
let error = sandbox.get("vendor-id").await.unwrap_err();
assert_eq!(error.code, "SANDBOX_NOT_AS_DECLARED", "{path}");
}
for path in ["", "/http"] {
let mut altered = serde_json::to_value(&expected).unwrap();
altered.pointer_mut(path).unwrap()["bypass"] = serde_json::json!(true);
record.lock().unwrap().as_mut().unwrap().egress_policy =
Some(serde_json::from_value(altered).unwrap());
let error = sandbox.get("vendor-id").await.unwrap_err();
assert_eq!(error.code, "SANDBOX_NOT_AS_DECLARED", "{path}/bypass");
}
record.lock().unwrap().as_mut().unwrap().egress_policy = Some(expected);
assert!(sandbox.get("vendor-id").await.unwrap().is_some());
sandbox.terminate("vendor-id").await.unwrap();
}