Watch
0
0
Fork
You've already forked hyperhive
0

make squid's allowed domains editable at runtime in the swarm ui

This commit is contained in:
atlas 2026-10-28 20:39:22 +01:00 • committed by mara
commit c9abc2a42b
2 changed files with 2307 additions and 35 deletions

View file

@ -0,0 +1,98 @@
//! The connection side of the egress allow-list KV bucket: opening it for
//! writing or reading, and the hive's direct read. The bucket's names, subjects
//! and stored type are `swarm_wire::egress_allow`.
/// Open the allow-list bucket for writing, creating it if nothing has yet.
///
/// **Controller-side only.** `history: 1`: a hive wants the current value.
#[cfg(feature = "kv")]
pub async fn open_or_create(
client: &async_nats::Client,
) -> Result<async_nats::jetstream::kv::Store, crate::Error> {
let js = async_nats::jetstream::new(client.clone());
crate::kv::open_or_create(
&js,
swarm_wire::egress_allow::BUCKET,
"egress-allow",
"The destinations the swarm lets agents reach through their hive's proxy".to_owned(),
)
.await
}
/// Open the allow-list bucket for reading, or `None` when nothing has created
/// it yet, which reads the same as no list.
///
/// **Controller-side**, for reads that must not create the bucket.
///
/// # Errors
///
/// [`crate::Error::OpenBucket`] for every failure other than the bucket not
/// existing.
#[cfg(feature = "kv")]
pub async fn open(
client: &async_nats::Client,
) -> Result<Option<async_nats::jetstream::kv::Store>, crate::Error> {
let js = async_nats::jetstream::new(client.clone());
match js.get_key_value(swarm_wire::egress_allow::BUCKET).await {
Ok(store) => Ok(Some(store)),
Err(e) if crate::wanted::is_bucket_not_found(&e) => Ok(None),
Err(source) => Err(crate::Error::OpenBucket {
bucket: swarm_wire::egress_allow::BUCKET.to_owned(),
source,
}),
}
}
/// What reading the list found.
#[cfg(feature = "egress-allow")]
#[derive(Debug)]
pub enum Read {
/// The controller has written a list, which may be empty.
Current(swarm_wire::egress_allow::AllowList),
/// There is no list to read: no bucket answers, or the key was never
/// written or has been deleted. A controller that predates the bucket reads
/// this way, and so does a queue that is not serving the stream right now,
/// so it says nothing about what the swarm wants.
NotSet,
}
/// Read the current list with one direct get.
///
/// # Errors
///
/// A failed request or an undecodable value. A request the queue refused for
/// permissions reaches the client as a timeout, not as a refusal.
#[cfg(feature = "egress-allow")]
pub async fn read(
client: &async_nats::Client,
) -> Result<Read, Box<dyn std::error::Error + Send + Sync>> {
use async_nats::jetstream::stream::DirectGetErrorKind;
use swarm_wire::egress_allow::{STREAM, key_subject};
let js = async_nats::jetstream::new(client.clone());
let stream = js.get_stream_no_info(STREAM).await?;
let message = match stream.direct_get_last_for_subject(key_subject()).await {
Ok(message) => message,
Err(e) if matches!(e.kind(), DirectGetErrorKind::NotFound) => return Ok(Read::NotSet),
Err(e) if is_no_responders(&e) => return Ok(Read::NotSet),
Err(e) => return Err(e.into()),
};
// A KV delete or purge is stored as an empty marker carrying this header.
let removed = message
.headers
.get("KV-Operation")
.is_some_and(|op| matches!(op.as_str(), "DEL" | "PURGE"));
if removed {
return Ok(Read::NotSet);
}
Ok(Read::Current(serde_json::from_slice(&message.payload)?))
}
#[cfg(feature = "egress-allow")]
fn is_no_responders(e: &async_nats::jetstream::stream::DirectGetError) -> bool {
matches!(
e.kind(),
async_nats::jetstream::stream::DirectGetErrorKind::ErrorResponse(status, _)
if status == async_nats::StatusCode::NO_RESPONDERS
)
}

View file

@ -232,6 +232,12 @@ pub mod agent_inference;
/// and reading needs `agent-limits`.
pub mod agent_limits;
/// Opening and reading the bucket swarm-controller keeps the egress allow-list
/// in — one key, read by every hive. The names, the stored type and the
/// validation are `swarm_wire::egress_allow`; writing needs `kv` and reading
/// needs `egress-allow`.
pub mod egress_allow;
/// Opening and reading the bucket swarm-controller sets each agent's
/// permissions in — one key per agent, read by the hive that runs it. The names,
/// the stored type and the import messages are `swarm_wire::agent_permissions`;