alien_core/
sandbox_setup_inputs.rs1use alien_error::AlienError;
5use serde_json::Value;
6
7use crate::remote_bindings::{
8 remote_binding_for_entry, remote_binding_is_deliverable, RemoteBindingDefinition,
9};
10use crate::sandbox_build_role::SandboxBuildRole;
11use crate::sandbox_egress::{sandbox_egress_network, sandbox_needs_connector};
12use crate::{
13 ErrorData, ResourceLifecycle, Result, Sandbox, SandboxCode, Stack, BUNDLE_REGION_TOKEN,
14};
15
16#[derive(Debug, Clone, Copy)]
18pub struct SetupAccount<'a> {
19 pub partition: &'a str,
20 pub account_id: &'a str,
21 pub region: &'a str,
22}
23
24pub const SETUP_INPUTS_COMPARISON_ACCOUNT: SetupAccount<'static> = SetupAccount {
27 partition: "aws",
28 account_id: "000000000000",
29 region: BUNDLE_REGION_TOKEN,
30};
31
32pub fn aws_sandbox_egress_network_id<'a>(
35 stack: &'a Stack,
36 sandbox: &Sandbox,
37) -> std::result::Result<Option<&'a str>, String> {
38 if !sandbox_needs_connector(stack, sandbox) {
39 return Ok(None);
40 }
41 let network = match sandbox_egress_network(stack) {
42 Ok(network) => network,
43 Err(refusal) => return Err(refusal.to_string()),
44 };
45 if network.entry.lifecycle != ResourceLifecycle::Frozen {
46 return Err(
47 "an AWS sandbox routes session traffic through a VPC egress connector; its network \
48 must be created by setup, and this one is created at runtime"
49 .to_string(),
50 );
51 }
52 Ok(Some(network.id))
53}
54
55pub fn aws_sandbox_build_role<'a>(
57 sandbox: &'a Sandbox,
58 bundle_uri: &'a str,
59 lifecycle: ResourceLifecycle,
60 account: SetupAccount<'a>,
61) -> SandboxBuildRole<'a> {
62 SandboxBuildRole::builder()
63 .sandbox_id(&sandbox.id)
64 .partition(account.partition)
65 .account_id(account.account_id)
66 .region(account.region)
67 .bundle_uri(bundle_uri)
68 .runtime_built(lifecycle == ResourceLifecycle::Live)
69 .maybe_private_base_image(sandbox.private_base_image.as_deref())
70 .build()
71}
72
73pub fn aws_sandbox_remote_grant(
75 stack: &Stack,
76 sandbox: &Sandbox,
77) -> Option<&'static RemoteBindingDefinition> {
78 stack
79 .resources
80 .get(&sandbox.id)
81 .filter(|entry| remote_binding_is_deliverable(entry))
82 .and_then(remote_binding_for_entry)
83}
84
85const SETUP_GRANTED_STORE: &str = "setup-granted-store";
87const SETUP_GRANTED_BASE_IMAGE: &str =
89 "000000000000.dkr.ecr.us-east-1.amazonaws.com/setup-granted-repository:base";
90
91fn setup_granted_bundle(image: &str) -> std::result::Result<String, String> {
93 image
94 .strip_prefix("s3://")
95 .and_then(|path| path.split_once('/'))
96 .map(|(_, key)| format!("s3://{SETUP_GRANTED_STORE}/{key}"))
97 .ok_or_else(|| format!("bundle '{image}' is not an s3://bucket/key URI"))
98}
99
100pub fn aws_sandbox_setup_inputs(
105 stack: &Stack,
106 sandbox: &Sandbox,
107 lifecycle: ResourceLifecycle,
108 account: SetupAccount<'_>,
109) -> Result<Vec<(&'static str, Value)>> {
110 let refuse = |reason: String| {
111 AlienError::new(ErrorData::OperationNotSupported {
112 operation: format!("setup inputs for sandbox '{}'", sandbox.id),
113 reason,
114 })
115 };
116 let SandboxCode::Image { image } = &sandbox.code else {
117 return Err(refuse(
118 "an AWS sandbox is built from a prebuilt s3:// bundle, not from source".to_string(),
119 ));
120 };
121 let policy = if lifecycle == ResourceLifecycle::Live {
122 let mut granted = sandbox.clone();
127 granted.private_base_image = sandbox
128 .private_base_image
129 .as_ref()
130 .map(|_| SETUP_GRANTED_BASE_IMAGE.to_string());
131 let bundle = setup_granted_bundle(image).map_err(refuse)?;
132 aws_sandbox_build_role(&granted, &bundle, lifecycle, account).policy()?
133 } else {
134 aws_sandbox_build_role(sandbox, image, lifecycle, account).policy()?
135 };
136 let network = aws_sandbox_egress_network_id(stack, sandbox).map_err(refuse)?;
137 let grant =
140 aws_sandbox_remote_grant(stack, sandbox).map(|definition| definition.permission_set);
141 Ok(vec![
142 ("egress", sandbox.setup_digest_value()),
143 ("egress network", serde_json::json!(network)),
144 (
145 "build role policy",
146 serde_json::to_value(policy).expect("a build role policy serializes to JSON"),
147 ),
148 ("remote grant", serde_json::json!(grant)),
149 ])
150}
151
152pub fn comparable_aws_sandbox_setup_inputs(
155 stack: &Stack,
156 sandbox: &Sandbox,
157 lifecycle: ResourceLifecycle,
158) -> Vec<(&'static str, Value)> {
159 aws_sandbox_setup_inputs(stack, sandbox, lifecycle, SETUP_INPUTS_COMPARISON_ACCOUNT)
160 .unwrap_or_else(|_| {
161 vec![("configuration", {
162 let mut value =
163 serde_json::to_value(sandbox).expect("a sandbox serializes to JSON");
164 value["egress"] = sandbox.setup_digest_value();
165 value
166 .as_object_mut()
167 .expect("sandbox config is an object")
168 .remove("defaultPolicy");
169 value
170 })]
171 })
172}