Block subdomains of blocked hosts (#9310)

This commit is contained in:
Kainoa Kanter 2022-12-26 09:12:17 +00:00
commit ebe0abe955
10 changed files with 54 additions and 21 deletions

View File

@ -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<boolean> {
const { blockedHosts } = meta ?? await fetchMeta();
return blockedHosts.some(blockedHost => host === blockedHost || host.endsWith('.' + blockedHost));
}

View File

@ -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<Instace['host']>): Array<Instance['host']> {
export async function skippedInstances(hosts: Instance['host'][]): Promise<Instance['host'][]> {
// 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<Instace['host']>): Array<Ins
* @param host punycoded instance host
* @returns whether the given host should be skipped
*/
export async function shouldSkipInstance(host: Instance['host']): boolean {
export async function shouldSkipInstance(host: Instance['host']): Promise<boolean> {
const skipped = await skippedInstances([host]);
return skipped.length > 0;
}

View File

@ -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,

View File

@ -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<InboxJobData>): Promise<string> => {
// 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<InboxJobData>): Promise<string> => {
// ブロックしてたら中断
const ldHost = extractDbHost(authUser.user.uri);
if (meta.blockedHosts.includes(ldHost)) {
if (await shouldBlockInstance(ldHost, meta)) {
return `Blocked request: ${ldHost}`;
}
} else {

View File

@ -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<number> {
@ -22,7 +23,7 @@ export default async function checkFetch(req: IncomingMessage): Promise<number>
const keyId = new URL(signature.keyId);
const host = toPuny(keyId.hostname);
if (meta.blockedHosts.includes(host)) {
if (await shouldBlockInstance(host, meta)) {
return 403;
}

View File

@ -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);

View File

@ -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<void> {
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)) {

View File

@ -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);

View File

@ -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';
@ -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<string>;
@ -31,9 +32,7 @@ export default class Resolver {
}
public async resolveCollection(value: string | IObject): Promise<ICollection | IOrderedCollection> {
const collection = typeof value === 'string'
? await this.resolve(value)
: value;
const collection = await this.resolve(value);
if (isCollectionOrOrderedCollection(collection)) {
return collection;
@ -48,6 +47,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;
}
@ -72,7 +77,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');
}

View File

@ -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<SchemaType<typeof meta['res']> | 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();