agent_sdk_toolkit/environment/
profile.rs1use std::collections::BTreeSet;
2
3use agent_sdk_core::{
4 AgentError, DestinationKind, DestinationRef, ExecutionEnvironment, ExecutionEnvironmentBuilder,
5 ExecutionEnvironmentKind, FilesystemIsolationPolicy, IsolationCapability, IsolationClass,
6 IsolationRequirement, IsolationRequirementSnapshot, IsolationRuntimeRef,
7 NetworkIsolationPolicy, PolicyKind, PolicyRef, RuntimePackageSidecarId, SecretExposurePolicy,
8 SourceKind, SourceRef, WorkspaceMountMode,
9};
10
11use crate::environment::{EgressAllowlist, EnvironmentRuntime};
12
13#[derive(Clone, Debug)]
14pub struct AgentWorkspaceEnvironmentProfile {
20 environment_id: String,
21 isolation_class: IsolationClass,
22 preferred_runtimes: Vec<IsolationRuntimeRef>,
23 workspace_ref: Option<String>,
24 workspace_mode: WorkspaceMountMode,
25 network: NetworkIsolationPolicy,
26 egress_allowlist: Option<EgressAllowlist>,
27 required_capabilities: BTreeSet<IsolationCapability>,
28 source: SourceRef,
29 destination: DestinationRef,
30}
31
32impl AgentWorkspaceEnvironmentProfile {
33 pub fn new(environment_id: impl Into<String>) -> Self {
36 let mut required_capabilities = BTreeSet::new();
37 required_capabilities.insert(IsolationCapability::Cleanup);
38 required_capabilities.insert(IsolationCapability::ReadOnlyRoot);
39 required_capabilities.insert(IsolationCapability::ProcessTimeout);
40 required_capabilities.insert(IsolationCapability::IoRedaction);
41 required_capabilities.insert(IsolationCapability::NoNetworkGuarantee);
42 Self {
43 environment_id: environment_id.into(),
44 isolation_class: IsolationClass::Container,
45 preferred_runtimes: Vec::new(),
46 workspace_ref: None,
47 workspace_mode: WorkspaceMountMode::Snapshot,
48 network: NetworkIsolationPolicy::Disabled,
49 egress_allowlist: None,
50 required_capabilities,
51 source: SourceRef::with_kind(SourceKind::Sdk, "source.sdk.toolkit.environment"),
52 destination: DestinationRef::with_kind(
53 DestinationKind::ExternalRuntime,
54 "destination.toolkit.environment",
55 ),
56 }
57 }
58
59 pub fn isolation_class(mut self, isolation_class: IsolationClass) -> Self {
61 self.isolation_class = isolation_class;
62 self
63 }
64
65 pub fn prefer_runtime(mut self, runtime_ref: impl Into<IsolationRuntimeRef>) -> Self {
68 self.preferred_runtimes.push(runtime_ref.into());
69 self
70 }
71
72 pub fn runtime(mut self, runtime: EnvironmentRuntime) -> Self {
80 self.isolation_class = runtime.isolation_class();
81 self.preferred_runtimes.push(runtime.into());
82 self
83 }
84
85 pub fn workspace(mut self, workspace_ref: impl Into<String>) -> Self {
87 self.workspace_ref = Some(workspace_ref.into());
88 self.workspace_mode = WorkspaceMountMode::Snapshot;
89 self
90 }
91
92 pub fn workspace_with_mode(
94 mut self,
95 workspace_ref: impl Into<String>,
96 mode: WorkspaceMountMode,
97 ) -> Self {
98 self.workspace_ref = Some(workspace_ref.into());
99 self.workspace_mode = mode;
100 self
101 }
102
103 pub fn no_network(mut self) -> Self {
105 self.network = NetworkIsolationPolicy::Disabled;
106 self.egress_allowlist = None;
107 self.required_capabilities
108 .remove(&IsolationCapability::EgressAllowlist);
109 self.required_capabilities
110 .insert(IsolationCapability::NoNetworkGuarantee);
111 self
112 }
113
114 pub fn egress_allowlist(mut self, allowlist: EgressAllowlist) -> Self {
116 if allowlist.is_empty() {
117 return self.no_network();
118 }
119 self.egress_allowlist = Some(allowlist);
120 self.required_capabilities
121 .remove(&IsolationCapability::NoNetworkGuarantee);
122 self.required_capabilities
123 .insert(IsolationCapability::EgressAllowlist);
124 self
125 }
126
127 pub fn require_capability(mut self, capability: IsolationCapability) -> Self {
129 self.required_capabilities.insert(capability);
130 self
131 }
132
133 pub fn source(mut self, source: SourceRef) -> Self {
135 self.source = source;
136 self
137 }
138
139 pub fn destination(mut self, destination: DestinationRef) -> Self {
141 self.destination = destination;
142 self
143 }
144
145 pub fn build(self) -> Result<AgentWorkspaceEnvironment, AgentError> {
147 let environment_id = self.environment_id;
148 let network = if let Some(allowlist) = &self.egress_allowlist {
149 allowlist.network_policy()?
150 } else {
151 self.network
152 };
153 let mut requirement = IsolationRequirement::at_least(self.isolation_class);
154 for runtime_ref in self.preferred_runtimes {
155 requirement = requirement.prefer(runtime_ref);
156 }
157 requirement = requirement.require_capabilities(self.required_capabilities);
158
159 let mut builder = ExecutionEnvironment::require(requirement)
160 .environment_id(environment_id.as_str())
161 .network(network)
162 .secrets(SecretExposurePolicy::no_ambient())
163 .ephemeral()
164 .source(self.source)
165 .destination(self.destination);
166
167 builder = apply_workspace(builder, self.workspace_ref, self.workspace_mode);
168
169 let mut environment = builder.build()?;
170 environment.spec.kind = environment_kind_for_class(self.isolation_class);
171
172 Ok(AgentWorkspaceEnvironment {
173 environment,
174 egress_allowlist: self.egress_allowlist,
175 })
176 }
177}
178
179#[derive(Clone, Debug)]
180pub struct AgentWorkspaceEnvironment {
182 pub environment: ExecutionEnvironment,
184 pub egress_allowlist: Option<EgressAllowlist>,
186}
187
188impl AgentWorkspaceEnvironment {
189 pub fn environment(&self) -> &ExecutionEnvironment {
191 &self.environment
192 }
193
194 pub fn into_environment(self) -> ExecutionEnvironment {
196 self.environment
197 }
198
199 pub fn egress_allowlist(&self) -> Option<&EgressAllowlist> {
201 self.egress_allowlist.as_ref()
202 }
203
204 pub fn isolation_snapshot(
206 &self,
207 sidecar_id: RuntimePackageSidecarId,
208 redaction_policy_ref: PolicyRef,
209 cleanup_policy_ref: PolicyRef,
210 child_lifecycle_policy_ref: PolicyRef,
211 ) -> IsolationRequirementSnapshot {
212 IsolationRequirementSnapshot::from_environment(
213 sidecar_id,
214 &self.environment,
215 redaction_policy_ref,
216 cleanup_policy_ref,
217 child_lifecycle_policy_ref,
218 )
219 }
220
221 pub fn default_isolation_snapshot(&self) -> IsolationRequirementSnapshot {
223 self.isolation_snapshot(
224 RuntimePackageSidecarId::new("sidecar.toolkit.environment.isolation"),
225 PolicyRef::with_kind(
226 PolicyKind::RuntimePackage,
227 "policy.toolkit.environment.redaction",
228 ),
229 PolicyRef::with_kind(
230 PolicyKind::RuntimePackage,
231 "policy.toolkit.environment.cleanup",
232 ),
233 PolicyRef::with_kind(
234 PolicyKind::RuntimePackage,
235 "policy.toolkit.environment.child_lifecycle",
236 ),
237 )
238 }
239}
240
241fn apply_workspace(
242 builder: ExecutionEnvironmentBuilder,
243 workspace_ref: Option<String>,
244 mode: WorkspaceMountMode,
245) -> ExecutionEnvironmentBuilder {
246 if let Some(workspace_ref) = workspace_ref {
247 builder.workspace(workspace_ref, mode)
248 } else {
249 builder.filesystem(FilesystemIsolationPolicy::no_workspace())
250 }
251}
252
253fn environment_kind_for_class(isolation_class: IsolationClass) -> ExecutionEnvironmentKind {
254 match isolation_class {
255 IsolationClass::HostProcess => ExecutionEnvironmentKind::HostProcess,
256 IsolationClass::Sandbox => ExecutionEnvironmentKind::Sandbox,
257 IsolationClass::Container => ExecutionEnvironmentKind::Container,
258 IsolationClass::LightweightVm => ExecutionEnvironmentKind::LightweightVm,
259 IsolationClass::RemoteSandbox => ExecutionEnvironmentKind::RemoteSandbox,
260 }
261}