From c1a51547a911e407cd4cb91ed568a58a90d16ffb Mon Sep 17 00:00:00 2001 From: Norm Date: Mon, 5 Dec 2022 17:55:38 +0000 Subject: [PATCH] BREAKING: server: remove wildcard blocking and instead block subdomains (#269) Co-authored-by: Francis Dinh Reviewed-on: https://akkoma.dev/FoundKeyGang/FoundKey/pulls/269 Changelog: Changed --- locales/en-US.yml | 2 +- .../backend/src/misc/should-block-instance.ts | 16 ++++++++++ .../backend/src/misc/skipped-instances.ts | 29 +------------------ .../src/models/repositories/instance.ts | 5 ++-- .../backend/src/queue/processors/inbox.ts | 2 +- .../activitypub/kernel/announce/note.ts | 5 ++-- .../src/remote/activitypub/models/note.ts | 11 ++++--- .../src/remote/activitypub/resolver.ts | 2 +- .../src/server/api/endpoints/ap/show.ts | 2 +- 9 files changed, 30 insertions(+), 44 deletions(-) create mode 100644 packages/backend/src/misc/should-block-instance.ts diff --git a/locales/en-US.yml b/locales/en-US.yml index 109e03b94..75d2643f0 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -187,7 +187,7 @@ clearCachedFiles: "Clear cache" clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?" blockedInstances: "Blocked Instances" blockedInstancesDescription: "List the hostnames of the instances that you want to\ - \ block. Listed instances will no longer be able to communicate with this instance. Non-ASCII domain names must be encoded in punycode. You can use an asterisk (*) as a placeholder for zero or more character(s)." + \ block. Listed instances will no longer be able to communicate with this instance. Non-ASCII domain names must be encoded in punycode. Subdomains of the listed instances will also be blocked." muteAndBlock: "Mutes and Blocks" mutedUsers: "Muted users" blockedUsers: "Blocked users" 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..7396983d7 --- /dev/null +++ b/packages/backend/src/misc/should-block-instance.ts @@ -0,0 +1,16 @@ +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 Promise contatining the information from the meta table (optional) + * @returns whether the given host should be blocked + */ + +export async function shouldBlockInstance(host: Instance['host'], meta: Promise = fetchMeta()): Promise { + const { blockedHosts } = await meta; + 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 40f106746..37587c52e 100644 --- a/packages/backend/src/misc/skipped-instances.ts +++ b/packages/backend/src/misc/skipped-instances.ts @@ -2,39 +2,12 @@ import { db } from '@/db/postgre.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Instance } from '@/models/entities/instance.js'; import { DAY } from '@/const.js'; -import { Meta } from '@/models/entities/meta.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; // Threshold from last contact after which an instance will be considered // "dead" and should no longer get activities delivered to it. const deadThreshold = 7 * DAY; -/** - * Returns whether a given host matches a wildcard pattern. - * @param host punycoded instance host - * @param pattern wildcard pattern containing a punycoded instance host - * @returns whether the post matches the pattern - */ -function matchHost(host: Instance['host'], pattern: string): boolean { - // Escape all of the regex special characters. Pattern from: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping - const escape = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const re = new RegExp('^' + pattern.split('*').map(escape).join('.*') + '$'); - - return re.test(host); -} - -/** - * Returns whether a specific host (punycoded) should be blocked. - * - * @param host punycoded instance host - * @param meta a Promise contatining the information from the meta table (oprional) - * @returns whether the given host should be blocked - */ -export async function shouldBlockInstance(host: string, meta: Promise = fetchMeta()): Promise { - const { blockedHosts } = await meta; - return blockedHosts.some(blockedHost => matchHost(host, blockedHost)); -} - /** * Returns the subset of hosts which should be skipped. * diff --git a/packages/backend/src/models/repositories/instance.ts b/packages/backend/src/models/repositories/instance.ts index 5f0fd8d58..8e6a33a13 100644 --- a/packages/backend/src/models/repositories/instance.ts +++ b/packages/backend/src/models/repositories/instance.ts @@ -1,13 +1,12 @@ 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.js'; export const InstanceRepository = db.getRepository(Instance).extend({ async pack( instance: Instance, ): Promise> { - const meta = await fetchMeta(); return { id: instance.id, caughtAt: instance.caughtAt.toISOString(), @@ -20,7 +19,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 bd208546e..fc805c8c5 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -14,7 +14,7 @@ import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js'; import { getAuthUser } from '@/remote/activitypub/misc/auth-user.js'; import { StatusError } from '@/misc/fetch.js'; import { InboxJobData } from '@/queue/types.js'; -import { shouldBlockInstance } from '@/misc/skipped-instances.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; const logger = new Logger('inbox'); diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index 306ce75b1..82c232bce 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -1,7 +1,6 @@ import post from '@/services/note/create.js'; import { CacheableRemoteUser } from '@/models/entities/user.js'; import { extractDbHost } from '@/misc/convert-host.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; import { getApLock } from '@/misc/app-lock.js'; import { StatusError } from '@/misc/fetch.js'; import { Notes } from '@/models/index.js'; @@ -10,6 +9,7 @@ import { apLogger } from '@/remote/activitypub/logger.js'; import { fetchNote, resolveNote } from '@/remote/activitypub/models/note.js'; import Resolver from '@/remote/activitypub/resolver.js'; import { IAnnounce, getApId } from '@/remote/activitypub/type.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise { const uri = getApId(activity); @@ -19,8 +19,7 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac } // Cancel if the announced from host is blocked. - 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 c1aa07f05..f640add06 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -12,11 +12,11 @@ import { Emojis, Polls, MessagingMessages } from '@/models/index.js'; import { Note } from '@/models/entities/note.js'; import { Emoji } from '@/models/entities/emoji.js'; import { genId } from '@/misc/gen-id.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; import { getApLock } from '@/misc/app-lock.js'; import { createMessage } from '@/services/messages/create.js'; import { StatusError } from '@/misc/fetch.js'; import { fromHtml } from '@/mfm/from-html.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; import { parseAudience } from '../audience.js'; import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; import DbResolver from '../db-resolver.js'; @@ -28,7 +28,7 @@ import { extractApHashtags } from './tag.js'; import { extractPollFromQuestion } from './question.js'; import { extractApMentions } from './mention.js'; -export function validateNote(object: any, uri: string) { +export function validateNote(object: any, uri: string): Error | null { const expectHost = extractDbHost(uri); if (object == null) { @@ -270,14 +270,13 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): const uri = typeof value === 'string' ? value : value.id; if (uri == null) throw new Error('missing uri'); - // ブロックしてたら中断 - const meta = await fetchMeta(); - if (meta.blockedHosts.includes(extractDbHost(uri))) throw new StatusError('host blocked', 451, `host ${extractDbHost(uri)} is blocked`); + // Interrupt if blocked. + if (await shouldBlockInstance(extractDbHost(uri))) throw new StatusError('host blocked', 451, `host ${extractDbHost(uri)} is blocked`); const unlock = await getApLock(uri); try { - //#region このサーバーに既に登録されていたらそれを返す + //#region If already registered on this server, return it. const exist = await fetchNote(uri); if (exist) { diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index a38b1fd0b..e1aa6b2bd 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -11,7 +11,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/skipped-instances.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; import { signedGet } from './request.js'; import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js'; import { parseUri } from './db-resolver.js'; diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 0fb006b70..fc8fc89ac 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -9,7 +9,7 @@ import { CacheableLocalUser, User } from '@/models/entities/user.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/skipped-instances.js'; +import { shouldBlockInstance } from '@/misc/should-block-instance.js'; import define from '../../define.js'; import { ApiError } from '../../error.js';