swarm: unrestricted_internet flag on the agent permissions record, unset reads as on, create writes false
This commit is contained in:
parent
e77459d2cb
commit
8699857024
3 changed files with 381 additions and 107 deletions
|
|
@ -128,6 +128,7 @@ async fn post_create(
|
|||
limits: None,
|
||||
inference: None,
|
||||
permissions: None,
|
||||
unrestricted_internet: None,
|
||||
archetype: None,
|
||||
agents_md: agents_md.map(str::to_owned),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -346,7 +346,8 @@ struct AgentsMdApplyArgs {
|
|||
enum PermissionsVerb {
|
||||
/// Show the permissions the swarm grants an agent, or that it sets none.
|
||||
Get(InferenceAgentArgs),
|
||||
/// Replace the permissions the swarm grants an agent.
|
||||
/// Replace the permissions the swarm grants an agent; the internet flag
|
||||
/// stays unless given.
|
||||
Set(PermissionsSetArgs),
|
||||
/// Clear the agent's permissions, so the default applies again.
|
||||
Clear(InferenceAgentArgs),
|
||||
|
|
@ -366,6 +367,10 @@ struct PermissionsSetArgs {
|
|||
required = true
|
||||
)]
|
||||
permissions: Vec<String>,
|
||||
/// Whether the agent's traffic may leave the host unfiltered. Left out,
|
||||
/// the stored value stays.
|
||||
#[arg(long, value_name = "BOOL")]
|
||||
unrestricted_internet: Option<bool>,
|
||||
#[command(flatten)]
|
||||
socket: ControllerSocketArg,
|
||||
}
|
||||
|
|
@ -765,9 +770,12 @@ fn inference(remote: &RemoteArgs, command: InferenceVerb) -> Result<()> {
|
|||
fn permissions(remote: &RemoteArgs, command: PermissionsVerb) -> Result<()> {
|
||||
match command {
|
||||
PermissionsVerb::Get(a) => permissions::get(&a.socket.resolve(remote)?, &a.agent),
|
||||
PermissionsVerb::Set(a) => {
|
||||
permissions::set(&a.socket.resolve(remote)?, &a.agent, &a.permissions)
|
||||
}
|
||||
PermissionsVerb::Set(a) => permissions::set(
|
||||
&a.socket.resolve(remote)?,
|
||||
&a.agent,
|
||||
&a.permissions,
|
||||
a.unrestricted_internet,
|
||||
),
|
||||
PermissionsVerb::Clear(a) => permissions::clear(&a.socket.resolve(remote)?, &a.agent),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,14 @@ pub(crate) fn get(target: &Target, agent: &str) -> Result<()> {
|
|||
|
||||
/// Run `swarmctl agent permissions set`: store the given permissions, which
|
||||
/// replace the agent's record whole.
|
||||
pub(crate) fn set(target: &Target, agent: &str, permissions: &[String]) -> Result<()> {
|
||||
pub(crate) fn set(
|
||||
target: &Target,
|
||||
agent: &str,
|
||||
permissions: &[String],
|
||||
unrestricted_internet: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let uri = member(agent)?;
|
||||
let wanted = with_permissions(permissions)?;
|
||||
let wanted = with_permissions(permissions, unrestricted_internet)?;
|
||||
let stored: Permissions = runtime()?.block_on(send(
|
||||
target,
|
||||
hyper::Method::PUT,
|
||||
|
|
@ -61,7 +66,10 @@ pub(crate) fn set(target: &Target, agent: &str, permissions: &[String]) -> Resul
|
|||
|
||||
/// The record granting `permissions`. An empty name, as `--permissions ''`
|
||||
/// gives, is dropped, so that spelling grants nothing.
|
||||
fn with_permissions(permissions: &[String]) -> Result<Permissions> {
|
||||
fn with_permissions(
|
||||
permissions: &[String],
|
||||
unrestricted_internet: Option<bool>,
|
||||
) -> Result<Permissions> {
|
||||
let granted = permissions
|
||||
.iter()
|
||||
.filter(|name| !name.is_empty())
|
||||
|
|
@ -69,6 +77,7 @@ fn with_permissions(permissions: &[String]) -> Result<Permissions> {
|
|||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(Permissions {
|
||||
permissions: Some(granted),
|
||||
unrestricted_internet,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +121,12 @@ fn render(p: &Permissions) -> String {
|
|||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
};
|
||||
format!("permissions: {caps}\n")
|
||||
let internet = match p.unrestricted_internet {
|
||||
None => "(not set; unrestricted)",
|
||||
Some(true) => "unrestricted",
|
||||
Some(false) => "restricted",
|
||||
};
|
||||
format!("permissions: {caps}\nunrestricted_internet: {internet}\n")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -122,18 +136,48 @@ mod tests {
|
|||
#[test]
|
||||
fn get_tells_unset_from_none() {
|
||||
let unset: Permissions = serde_json::from_str("{}").unwrap();
|
||||
assert_eq!(render(&unset), "permissions: (not set; the default)\n");
|
||||
assert_eq!(
|
||||
render(&unset),
|
||||
"permissions: (not set; the default)\nunrestricted_internet: (not set; unrestricted)\n"
|
||||
);
|
||||
let none: Permissions = serde_json::from_str(r#"{"permissions":[]}"#).unwrap();
|
||||
assert_eq!(render(&none), "permissions: (none)\n");
|
||||
assert_eq!(
|
||||
render(&none),
|
||||
"permissions: (none)\nunrestricted_internet: (not set; unrestricted)\n"
|
||||
);
|
||||
let ruth: Permissions =
|
||||
serde_json::from_str(r#"{"permissions":["manage_root_agent"]}"#).unwrap();
|
||||
assert_eq!(render(&ruth), "permissions: manage_root_agent\n");
|
||||
assert_eq!(
|
||||
render(&ruth),
|
||||
"permissions: manage_root_agent\nunrestricted_internet: (not set; unrestricted)\n"
|
||||
);
|
||||
}
|
||||
|
||||
/// An explicit flag is shown and sent; left out it is not sent, so a `PUT`
|
||||
/// keeps the stored one.
|
||||
#[test]
|
||||
fn the_internet_flag_is_sent_only_when_given() {
|
||||
let off = with_permissions(&[], Some(false)).unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_string(&off).unwrap(),
|
||||
r#"{"permissions":[],"unrestricted_internet":false}"#
|
||||
);
|
||||
assert_eq!(
|
||||
render(&off),
|
||||
"permissions: (none)\nunrestricted_internet: restricted\n"
|
||||
);
|
||||
let left_out = with_permissions(&[], None).unwrap();
|
||||
assert!(
|
||||
!serde_json::to_string(&left_out)
|
||||
.unwrap()
|
||||
.contains("internet")
|
||||
);
|
||||
}
|
||||
|
||||
/// `--permissions ''` stores an empty list, not one empty name.
|
||||
#[test]
|
||||
fn an_empty_list_flag_grants_nothing() {
|
||||
let set = with_permissions(&[String::new()]).unwrap();
|
||||
let set = with_permissions(&[String::new()], None).unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_string(&set).unwrap(),
|
||||
r#"{"permissions":[]}"#
|
||||
|
|
@ -143,11 +187,11 @@ mod tests {
|
|||
/// A name that is not a permission fails locally and lists the valid ones.
|
||||
#[test]
|
||||
fn an_unknown_permission_is_refused() {
|
||||
let err = with_permissions(&["messaging".to_owned(), "fly".to_owned()]).unwrap_err();
|
||||
let err = with_permissions(&["messaging".to_owned(), "fly".to_owned()], None).unwrap_err();
|
||||
let text = err.to_string();
|
||||
assert!(text.contains("invalid permission \"fly\""), "{text}");
|
||||
assert!(text.contains("manage_root_agent"), "{text}");
|
||||
let ok = with_permissions(&["web_tools".to_owned()]).unwrap();
|
||||
let ok = with_permissions(&["web_tools".to_owned()], None).unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ok).unwrap(),
|
||||
r#"{"permissions":["web_tools"]}"#
|
||||
|
|
|
|||
Loading…
Reference in a new issue