Implement instance whitelist

This commit is contained in:
Felix 2020-04-17 19:34:18 +02:00
parent c5ced6fa5e
commit b1b97db11a
8 changed files with 46 additions and 20 deletions

View file

@ -25,6 +25,7 @@ services:
- LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__ENABLED=true
- LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_FEDERATION__TLS_ENABLED=false
- LEMMY_FEDERATION__INSTANCE_WHITELIST=lemmy_beta
- LEMMY_PORT=8540 - LEMMY_PORT=8540
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
@ -58,6 +59,7 @@ services:
- LEMMY_FRONT_END_DIR=/app/dist - LEMMY_FRONT_END_DIR=/app/dist
- LEMMY_FEDERATION__ENABLED=true - LEMMY_FEDERATION__ENABLED=true
- LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_FEDERATION__TLS_ENABLED=false
- LEMMY_FEDERATION__INSTANCE_WHITELIST=lemmy_alpha
- LEMMY_PORT=8550 - LEMMY_PORT=8550
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy - LEMMY_SETUP__ADMIN_PASSWORD=lemmy

View file

@ -56,6 +56,8 @@
enabled: false enabled: false
# whether tls is required for activitypub. only disable this for debugging, never for producion. # whether tls is required for activitypub. only disable this for debugging, never for producion.
tls_enabled: true tls_enabled: true
# comma seperated list of instances with which federation is allowed
instance_whitelist: ""
} }
# # email sending configuration # # email sending configuration
# email: { # email: {

View file

@ -1,3 +1,4 @@
use crate::apub::is_apub_id_valid;
use crate::db::community::Community; use crate::db::community::Community;
use crate::db::community_view::CommunityFollowerView; use crate::db::community_view::CommunityFollowerView;
use crate::db::post::Post; use crate::db::post::Post;
@ -36,9 +37,12 @@ where
A: Serialize + Debug, A: Serialize + Debug,
{ {
let json = serde_json::to_string(&activity)?; let json = serde_json::to_string(&activity)?;
debug!("Sending activitypub activity {}", json); debug!("Sending activitypub activity {} to {:?}", json, to);
for t in to { for t in to {
debug!("Sending activity to: {}", t); if is_apub_id_valid(&t) {
debug!("Not sending activity to {} (invalid or blacklisted)", t);
continue;
}
let res = Request::post(t) let res = Request::post(t)
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.body(json.to_owned())? .body(json.to_owned())?

View file

@ -17,22 +17,18 @@ pub enum CommunityAcceptedObjects {
Follow(Follow), Follow(Follow),
} }
#[derive(Deserialize)]
pub struct Params {
community_name: String,
}
/// Handler for all incoming activities to community inboxes. /// Handler for all incoming activities to community inboxes.
pub async fn community_inbox( pub async fn community_inbox(
input: web::Json<CommunityAcceptedObjects>, input: web::Json<CommunityAcceptedObjects>,
params: web::Query<Params>, path: web::Path<String>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>, db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let input = input.into_inner(); let input = input.into_inner();
let conn = &db.get().unwrap(); let conn = &db.get().unwrap();
debug!( debug!(
"Community {} received activity {:?}", "Community {} received activity {:?}",
&params.community_name, &input &path.into_inner(),
&input
); );
match input { match input {
CommunityAcceptedObjects::Follow(f) => handle_follow(&f, conn), CommunityAcceptedObjects::Follow(f) => handle_follow(&f, conn),

View file

@ -8,7 +8,6 @@ use crate::db::user::{UserForm, User_};
use crate::db::user_view::UserView; use crate::db::user_view::UserView;
use crate::db::{Crud, SearchType}; use crate::db::{Crud, SearchType};
use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown}; use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
use crate::settings::Settings;
use activitystreams::collection::OrderedCollection; use activitystreams::collection::OrderedCollection;
use activitystreams::object::Page; use activitystreams::object::Page;
use activitystreams::BaseBox; use activitystreams::BaseBox;
@ -68,8 +67,8 @@ pub fn fetch_remote_object<Response>(url: &Url) -> Result<Response, Error>
where where
Response: for<'de> Deserialize<'de>, Response: for<'de> Deserialize<'de>,
{ {
if Settings::get().federation.tls_enabled && url.scheme() != "https" { if !is_apub_id_valid(&url.to_string()) {
return Err(format_err!("Activitypub uri is insecure: {}", url)); return Err(format_err!("Activitypub uri invalid or blocked: {}", url));
} }
// TODO: this function should return a future // TODO: this function should return a future
let timeout = Duration::from_secs(60); let timeout = Duration::from_secs(60);
@ -86,7 +85,7 @@ where
/// The types of ActivityPub objects that can be fetched directly by searching for their ID. /// The types of ActivityPub objects that can be fetched directly by searching for their ID.
#[serde(untagged)] #[serde(untagged)]
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, Debug)]
pub enum SearchAcceptedObjects { pub enum SearchAcceptedObjects {
Person(Box<PersonExt>), Person(Box<PersonExt>),
Group(Box<GroupExt>), Group(Box<GroupExt>),

View file

@ -88,3 +88,26 @@ pub fn gen_keypair_str() -> (String, String) {
fn vec_bytes_to_str(bytes: Vec<u8>) -> String { fn vec_bytes_to_str(bytes: Vec<u8>) -> String {
String::from_utf8_lossy(&bytes).into_owned() String::from_utf8_lossy(&bytes).into_owned()
} }
// Checks if the ID has a valid format, correct scheme, and is in the whitelist.
fn is_apub_id_valid(apub_id: &str) -> bool {
let url = match Url::parse(apub_id) {
Ok(u) => u,
Err(_) => return false,
};
if url.scheme() != get_apub_protocol_string() {
return false;
}
let whitelist: Vec<String> = Settings::get()
.federation
.instance_whitelist
.split(',')
.map(|d| d.to_string())
.collect();
match url.domain() {
Some(d) => whitelist.contains(&d.to_owned()),
None => false,
}
}

View file

@ -17,20 +17,19 @@ pub enum UserAcceptedObjects {
Accept(Accept), Accept(Accept),
} }
#[derive(Deserialize)]
pub struct Params {
user_name: String,
}
/// Handler for all incoming activities to user inboxes. /// Handler for all incoming activities to user inboxes.
pub async fn user_inbox( pub async fn user_inbox(
input: web::Json<UserAcceptedObjects>, input: web::Json<UserAcceptedObjects>,
params: web::Query<Params>, path: web::Path<String>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>, db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let input = input.into_inner(); let input = input.into_inner();
let conn = &db.get().unwrap(); let conn = &db.get().unwrap();
debug!("User {} received activity: {:?}", &params.user_name, &input); debug!(
"User {} received activity: {:?}",
&path.into_inner(),
&input
);
match input { match input {
UserAcceptedObjects::Create(c) => handle_create(&c, conn), UserAcceptedObjects::Create(c) => handle_create(&c, conn),

View file

@ -64,6 +64,7 @@ pub struct Database {
pub struct Federation { pub struct Federation {
pub enabled: bool, pub enabled: bool,
pub tls_enabled: bool, pub tls_enabled: bool,
pub instance_whitelist: String,
} }
lazy_static! { lazy_static! {