feat: simple federation policies

basically can now fediblock instances, yayy, but more interestingly just
refuse to mirror media from problematic instances or refuse deliveries
for places we want to read but who shouldn't read us
This commit is contained in:
əlemi 2024-12-29 03:25:10 +01:00
parent 5800f39c67
commit 95d1ab948f
Signed by: alemi
GPG key ID: A4895B84D311642C
4 changed files with 38 additions and 1 deletions

View file

@ -18,6 +18,9 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub files: FileStorageConfig, pub files: FileStorageConfig,
#[serde(default)]
pub reject: RejectConfig,
// TODO should i move app keys here? // TODO should i move app keys here?
} }
@ -122,6 +125,19 @@ pub struct FileStorageConfig {
pub path: String, pub path: String,
} }
#[serde_inline_default::serde_inline_default]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, serde_default::DefaultFromSerde)]
pub struct RejectConfig {
#[serde(default)]
pub everything: Vec<String>,
#[serde(default)]
pub media: Vec<String>,
#[serde(default)]
pub delivery: Vec<String>,
}
impl Config { impl Config {
pub fn load(path: Option<&std::path::PathBuf>) -> Self { pub fn load(path: Option<&std::path::PathBuf>) -> Self {
let Some(cfg_path) = path else { return Config::default() }; let Some(cfg_path) = path else { return Config::default() };

View file

@ -120,6 +120,11 @@ pub async fn cloak_proxy(
let uri = ctx.uncloak(&hmac, &uri) let uri = ctx.uncloak(&hmac, &uri)
.ok_or_else(ApiError::unauthorized)?; .ok_or_else(ApiError::unauthorized)?;
let stripped = uri.replace("https://", "").replace("http://", "");
if ctx.cfg().reject.media.iter().any(|x| stripped.starts_with(x)) {
return Err(ApiError::Status(axum::http::StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS));
}
let resp = Context::client(ctx.domain()) let resp = Context::client(ctx.domain())
.get(uri) .get(uri)
.send() .send()

View file

@ -43,7 +43,7 @@ pub async fn post(
AuthIdentity(auth): AuthIdentity, AuthIdentity(auth): AuthIdentity,
Json(activity): Json<serde_json::Value> Json(activity): Json<serde_json::Value>
) -> crate::ApiResult<StatusCode> { ) -> crate::ApiResult<StatusCode> {
let Identity::Remote { domain: _server, user: uid, .. } = auth else { let Identity::Remote { domain, user: uid, .. } = auth else {
if matches!(activity.activity_type(), Ok(ActivityType::Delete)) { if matches!(activity.activity_type(), Ok(ActivityType::Delete)) {
// this is spammy af, ignore them! // this is spammy af, ignore them!
// we basically received a delete for a user we can't fetch and verify, meaning remote // we basically received a delete for a user we can't fetch and verify, meaning remote
@ -64,6 +64,10 @@ pub async fn post(
} }
}; };
if ctx.cfg().reject.everything.contains(&domain) {
return Err(crate::ApiError::Status(StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS));
}
let aid = activity.id()?.to_string(); let aid = activity.id()?.to_string();
let server = upub::Context::server(&aid); let server = upub::Context::server(&aid);

View file

@ -185,6 +185,18 @@ pub async fn process(ctx: Context, job: &model::job::Model) -> crate::JobResult<
targets.push(relay); targets.push(relay);
} }
} }
targets
.retain(|target| {
let stripped = target.replace("https://", "").replace("http://", "");
if ctx.cfg().reject.delivery.iter().any(|x| stripped.starts_with(x)) {
tracing::warn!("rejecting delivery of {} to {target}", job.activity);
false
} else {
true
}
});
ctx.deliver(targets, &job.activity, &job.actor, &tx).await?; ctx.deliver(targets, &job.activity, &job.actor, &tx).await?;
tx.commit().await?; tx.commit().await?;