From d9b7219404da2790d5ded06356c9c8490f161485 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Sat, 24 Dec 2022 14:39:54 -0500 Subject: [PATCH 1/3] Block subdomains of blocked hosts --- .../backend/src/misc/should-block-instance.ts | 15 +++++++++++++++ packages/backend/src/misc/skipped-instances.ts | 12 +++++++----- .../backend/src/models/repositories/instance.ts | 3 ++- packages/backend/src/queue/processors/inbox.ts | 5 +++-- .../backend/src/remote/activitypub/check-fetch.ts | 3 ++- .../remote/activitypub/kernel/announce/note.ts | 5 ++--- .../backend/src/remote/activitypub/models/note.ts | 4 ++-- .../backend/src/remote/activitypub/resolver.ts | 3 ++- .../backend/src/server/api/endpoints/ap/show.ts | 4 ++-- 9 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 packages/backend/src/misc/should-block-instance.ts diff --git a/packages/backend/src/misc/should-block-instance.ts b/packages/backend/src/misc/should-block-instance.ts new file mode 100644 index 000000000..ddd25eeee --- /dev/null +++ b/packages/backend/src/misc/should-block-instance.ts @@ -0,0 +1,15 @@ +import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Instance } from '@/models/entities/instance.js'; +import { Meta } from '@/models/entities/meta.js'; + +/** + * Returns whether a specific host (punycoded) should be blocked. + * + * @param host punycoded instance host + * @param meta a resolved Meta table + * @returns whether the given host should be blocked + */ +export async function shouldBlockInstance(host: Instance['host'], meta?: Meta): Promise { + const { blockedHosts } = meta ?? await fetchMeta(); + return blockedHosts.some(blockedHost => host === blockedHost || host.endsWith('.' + blockedHost)); +} diff --git a/packages/backend/src/misc/skipped-instances.ts b/packages/backend/src/misc/skipped-instances.ts index a89ca2e3f..4d239255d 100644 --- a/packages/backend/src/misc/skipped-instances.ts +++ b/packages/backend/src/misc/skipped-instances.ts @@ -3,6 +3,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js'; import { Instances } from '@/models/index.js'; import { Instance } from '@/models/entities/instance.js'; import { DAY } from '@/const.js'; +import { shouldBlockInstance } from './should-block-instance'; // Threshold from last contact after which an instance will be considered // "dead" and should no longer get activities delivered to it. @@ -14,11 +15,12 @@ const deadThreshold = 7 * DAY; * @param hosts array of punycoded instance hosts * @returns array of punycoed instance hosts that should be skipped (subset of hosts parameter) */ -export async function skippedInstances(hosts: Array): Array { +export async function skippedInstances(hosts: Instance['host'][]): Promise { // first check for blocked instances since that info may already be in memory - const { blockedHosts } = await fetchMeta(); - - const skipped = hosts.filter(host => blockedHosts.includes(host)); + const meta = await fetchMeta(); + const shouldSkip = await Promise.all(hosts.map(host => shouldBlockInstance(host, meta))); + const skipped = hosts.filter((_, i) => shouldSkip[i]); + // if possible return early and skip accessing the database if (skipped.length === hosts.length) return hosts; @@ -47,7 +49,7 @@ export async function skippedInstances(hosts: Array): Array { const skipped = await skippedInstances([host]); return skipped.length > 0; } diff --git a/packages/backend/src/models/repositories/instance.ts b/packages/backend/src/models/repositories/instance.ts index 5f0fd8d58..8e9db7cda 100644 --- a/packages/backend/src/models/repositories/instance.ts +++ b/packages/backend/src/models/repositories/instance.ts @@ -2,6 +2,7 @@ import { db } from '@/db/postgre.js'; import { Instance } from '@/models/entities/instance.js'; import { Packed } from '@/misc/schema.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance'; export const InstanceRepository = db.getRepository(Instance).extend({ async pack( @@ -20,7 +21,7 @@ export const InstanceRepository = db.getRepository(Instance).extend({ lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(), isNotResponding: instance.isNotResponding, isSuspended: instance.isSuspended, - isBlocked: meta.blockedHosts.includes(instance.host), + isBlocked: await shouldBlockInstance(instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, openRegistrations: instance.openRegistrations, diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 33949672c..27a335791 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -17,6 +17,7 @@ import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js'; import { StatusError } from '@/misc/fetch.js'; import { CacheableRemoteUser } from '@/models/entities/user.js'; import { UserPublickey } from '@/models/entities/user-publickey.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; const logger = new Logger('inbox'); @@ -34,7 +35,7 @@ export default async (job: Bull.Job): Promise => { // interrupt if blocked const meta = await fetchMeta(); - if (meta.blockedHosts.includes(host)) { + if (await shouldBlockInstance(host, meta)) { return `Blocked request: ${host}`; } @@ -123,7 +124,7 @@ export default async (job: Bull.Job): Promise => { // ブロックしてたら中断 const ldHost = extractDbHost(authUser.user.uri); - if (meta.blockedHosts.includes(ldHost)) { + if (await shouldBlockInstance(ldHost, meta)) { return `Blocked request: ${ldHost}`; } } else { diff --git a/packages/backend/src/remote/activitypub/check-fetch.ts b/packages/backend/src/remote/activitypub/check-fetch.ts index 8a53396b6..01797f908 100644 --- a/packages/backend/src/remote/activitypub/check-fetch.ts +++ b/packages/backend/src/remote/activitypub/check-fetch.ts @@ -6,6 +6,7 @@ import { URL } from 'url'; import { toPuny } from '@/misc/convert-host.js'; import DbResolver from '@/remote/activitypub/db-resolver.js'; import { getApId } from '@/remote/activitypub/type.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance'; export default async function checkFetch(req: IncomingMessage): Promise { @@ -22,7 +23,7 @@ export default async function checkFetch(req: IncomingMessage): Promise const keyId = new URL(signature.keyId); const host = toPuny(keyId.hostname); - if (meta.blockedHosts.includes(host)) { + if (await shouldBlockInstance(host, meta)) { return 403; } diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index d06265941..464a8d13a 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -5,11 +5,11 @@ import { IAnnounce, getApId } from '../../type.js'; import { fetchNote, resolveNote } from '../../models/note.js'; import { apLogger } from '../../logger.js'; import { extractDbHost } from '@/misc/convert-host.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; import { getApLock } from '@/misc/app-lock.js'; import { parseAudience } from '../../audience.js'; import { StatusError } from '@/misc/fetch.js'; import { Notes } from '@/models/index.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; const logger = apLogger; @@ -24,8 +24,7 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac } // Interrupt if you block the announcement destination - const meta = await fetchMeta(); - if (meta.blockedHosts.includes(extractDbHost(uri))) return; + if (await shouldBlockInstance(extractDbHost(uri))) return; const unlock = await getApLock(uri); diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 64dc965ec..1c885e04d 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -27,6 +27,7 @@ import { parseAudience } from '../audience.js'; import { extractApMentions } from './mention.js'; import DbResolver from '../db-resolver.js'; import { StatusError } from '@/misc/fetch.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; const logger = apLogger; @@ -275,8 +276,7 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): if (uri == null) throw new Error('missing uri'); // Abort if origin host is blocked - const meta = await fetchMeta(); - if (meta.blockedHosts.includes(extractDbHost(uri))) throw new StatusError('host blocked', 451, `host ${extractDbHost(uri)} is blocked`); + if (await shouldBlockInstance(extractDbHost(uri))) throw new StatusError('host blocked', 451, `host ${extractDbHost(uri)} is blocked`); const unlock = await getApLock(uri); diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index 94227e4db..8958c892a 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -15,6 +15,7 @@ import renderQuestion from '@/remote/activitypub/renderer/question.js'; import renderCreate from '@/remote/activitypub/renderer/create.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderFollow from '@/remote/activitypub/renderer/follow.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; export default class Resolver { private history: Set; @@ -72,7 +73,7 @@ export default class Resolver { } const meta = await fetchMeta(); - if (meta.blockedHosts.includes(host)) { + if (await shouldBlockInstance(host, meta)) { throw new Error('Instance is blocked'); } diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index b65f5d078..6d432d63b 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -13,6 +13,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js'; import { isActor, isPost, getApId } from '@/remote/activitypub/type.js'; import { SchemaType } from '@/misc/schema.js'; import { HOUR } from '@/const.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; export const meta = { tags: ['federation'], @@ -92,8 +93,7 @@ export default define(meta, paramDef, async (ps, me) => { */ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise | null> { // Wait if blocked. - const fetchedMeta = await fetchMeta(); - if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null; + if (await shouldBlockInstance(extractDbHost(uri))) return null; const dbResolver = new DbResolver(); From d2066d0d86db24c3af2634af4bece35e35941fb7 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Sun, 25 Dec 2022 15:10:33 -0500 Subject: [PATCH 2/3] add checks to resolver and performOneActivity --- packages/backend/src/remote/activitypub/kernel/index.ts | 9 +++++++++ packages/backend/src/remote/activitypub/resolver.ts | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index b8af3dc48..e24af2b38 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -18,6 +18,7 @@ import { isCollection, isFlag, isMove, + getApId, } from '../type.js'; import { apLogger } from '../logger.js'; import Resolver from '../resolver.js'; @@ -37,6 +38,8 @@ import block from './block/index.js'; import flag from './flag/index.js'; import move from './move/index.js'; import type { IObject } from '../type.js'; +import { extractDbHost } from '@/misc/convert-host.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; export async function performActivity(actor: CacheableRemoteUser, activity: IObject) { if (isCollectionOrOrderedCollection(activity)) { @@ -59,6 +62,12 @@ export async function performActivity(actor: CacheableRemoteUser, activity: IObj async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { if (actor.isSuspended) return; + if (typeof activity.id !== 'undefined') { + const host = extractDbHost(getApId(activity)); + if (await shouldBlockInstance(host)) return; + } + + if (isCreate(activity)) { await create(actor, activity); } else if (isDelete(activity)) { diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index 8958c892a..e4fdedac9 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -5,7 +5,7 @@ import { getInstanceActor } from '@/services/instance-actor.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { extractDbHost, isSelfHost } from '@/misc/convert-host.js'; import { signedGet } from './request.js'; -import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js'; +import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection, getApId } from './type.js'; import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js'; import { parseUri } from './db-resolver.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; @@ -49,6 +49,12 @@ export default class Resolver { } if (typeof value !== 'string') { + if (typeof value.id !== 'undefined') { + const host = extractDbHost(getApId(value)); + if (await shouldBlockInstance(host)) { + throw new Error('instance is blocked'); + } + } return value; } From d7390e09ffd87feee5734cc416a25f105c6ebf29 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 25 Dec 2022 00:01:58 +0100 Subject: [PATCH 3/3] activitypub: block check for resolving collections --- packages/backend/src/remote/activitypub/resolver.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index e4fdedac9..26ec4e8a6 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -32,9 +32,7 @@ export default class Resolver { } public async resolveCollection(value: string | IObject): Promise { - const collection = typeof value === 'string' - ? await this.resolve(value) - : value; + const collection = await this.resolve(value); if (isCollectionOrOrderedCollection(collection)) { return collection;