make squid's allowed domains editable at runtime in the swarm ui
This commit is contained in:
parent
86e80250bb
commit
c9abc2a42b
2 changed files with 2307 additions and 35 deletions
98
swarm-queue-client/src/egress_allow.rs
Normal file
98
swarm-queue-client/src/egress_allow.rs
Normal 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
|
||||
)
|
||||
}
|
||||
|
|
@ -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`;
|
||||
|
|
|
|||
Loading…
Reference in a new issue