forked from FoundKeyGang/FoundKey
server: add wildcard matching to blocked hosts
This adds in wildcard matching. For instance: - `*.bad.tld` will match: `very.bad.tld` - `bad.*` will match: `bad.something` - `*.bad.*` will match: `very.bad.evil` Changelog: Changed
This commit is contained in:
parent
a96fae65c2
commit
075e251822
5 changed files with 47 additions and 26 deletions
|
@ -187,7 +187,7 @@ clearCachedFiles: "Clear cache"
|
||||||
clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?"
|
clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote files?"
|
||||||
blockedInstances: "Blocked Instances"
|
blockedInstances: "Blocked Instances"
|
||||||
blockedInstancesDescription: "List the hostnames of the instances that you want to\
|
blockedInstancesDescription: "List the hostnames of the instances that you want to\
|
||||||
\ block. Listed instances will no longer be able to communicate with this instance."
|
\ block. Listed instances will no longer be able to communicate with this instance. You can use an asterisk (*) as a placeholder for zero or more character(s)."
|
||||||
muteAndBlock: "Mutes and Blocks"
|
muteAndBlock: "Mutes and Blocks"
|
||||||
mutedUsers: "Muted users"
|
mutedUsers: "Muted users"
|
||||||
blockedUsers: "Blocked users"
|
blockedUsers: "Blocked users"
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { Brackets } from 'typeorm';
|
|
||||||
import { db } from '@/db/postgre.js';
|
import { db } from '@/db/postgre.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { Instances } from '@/models/index.js';
|
|
||||||
import { Instance } from '@/models/entities/instance.js';
|
import { Instance } from '@/models/entities/instance.js';
|
||||||
import { DAY } from '@/const.js';
|
import { DAY } from '@/const.js';
|
||||||
|
|
||||||
|
@ -9,19 +7,42 @@ import { DAY } from '@/const.js';
|
||||||
// "dead" and should no longer get activities delivered to it.
|
// "dead" and should no longer get activities delivered to it.
|
||||||
const deadThreshold = 7 * DAY;
|
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
|
||||||
|
* @returns whether the given host should be blocked
|
||||||
|
*/
|
||||||
|
export async function shouldBlockInstance(host: string): Promise<boolean> {
|
||||||
|
const { blockedHosts } = await fetchMeta();
|
||||||
|
return blockedHosts.some(blockedHost => matchHost(host, blockedHost));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the subset of hosts which should be skipped.
|
* Returns the subset of hosts which should be skipped.
|
||||||
*
|
*
|
||||||
* @param hosts array of punycoded instance hosts
|
* @param hosts array of punycoded instance hosts
|
||||||
* @returns array of punycoed instance hosts that should be skipped (subset of hosts parameter)
|
* @returns array of punycoded 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: Array<Instance['host']>): Promise<Array<Instance['host']>> {
|
||||||
// first check for blocked instances since that info may already be in memory
|
const skipped = hosts.filter(host => shouldBlockInstance(host));
|
||||||
const { blockedHosts } = await fetchMeta();
|
|
||||||
|
|
||||||
const skipped = hosts.filter(host => blockedHosts.includes(host));
|
|
||||||
// if possible return early and skip accessing the database
|
// if possible return early and skip accessing the database
|
||||||
if (skipped.length === hosts.length) return hosts;
|
if (skipped.length === hosts.length) return hosts;
|
||||||
|
|
||||||
const deadTime = new Date(Date.now() - deadThreshold);
|
const deadTime = new Date(Date.now() - deadThreshold);
|
||||||
|
|
||||||
|
@ -32,10 +53,10 @@ export async function skippedInstances(hosts: Array<Instace['host']>): Array<Ins
|
||||||
deadTime.toISOString(),
|
deadTime.toISOString(),
|
||||||
// don't check hosts again that we already know are suspended
|
// don't check hosts again that we already know are suspended
|
||||||
// also avoids adding duplicates to the list
|
// also avoids adding duplicates to the list
|
||||||
hosts.filter(host => !skipped.includes(host) && !host.includes(',')).join(','),
|
hosts.filter(host => !skipped.some(blockedHost => matchHost(host, blockedHost)) && !host.includes(',')).join(','),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.then(res => res.map(row => row.host))
|
.then(res => res.map(row => row.host)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +68,7 @@ export async function skippedInstances(hosts: Array<Instace['host']>): Array<Ins
|
||||||
* @param host punycoded instance host
|
* @param host punycoded instance host
|
||||||
* @returns whether the given host should be skipped
|
* @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]);
|
const skipped = await skippedInstances([host]);
|
||||||
return skipped.length > 0;
|
return skipped.length > 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import Logger from '@/services/logger.js';
|
||||||
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
|
import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
|
||||||
import { Instances } from '@/models/index.js';
|
import { Instances } from '@/models/index.js';
|
||||||
import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js';
|
import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
|
||||||
import { toPuny, extractDbHost } from '@/misc/convert-host.js';
|
import { toPuny, extractDbHost } from '@/misc/convert-host.js';
|
||||||
import { getApId } from '@/remote/activitypub/type.js';
|
import { getApId } from '@/remote/activitypub/type.js';
|
||||||
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
|
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
|
||||||
|
@ -17,6 +16,7 @@ import { StatusError } from '@/misc/fetch.js';
|
||||||
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
import { CacheableRemoteUser } from '@/models/entities/user.js';
|
||||||
import { UserPublickey } from '@/models/entities/user-publickey.js';
|
import { UserPublickey } from '@/models/entities/user-publickey.js';
|
||||||
import { InboxJobData } from '@/queue/types.js';
|
import { InboxJobData } from '@/queue/types.js';
|
||||||
|
import { shouldBlockInstance } from '@/misc/skipped-instances.js';
|
||||||
|
|
||||||
const logger = new Logger('inbox');
|
const logger = new Logger('inbox');
|
||||||
|
|
||||||
|
@ -33,9 +33,8 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
||||||
|
|
||||||
const host = toPuny(new URL(signature.keyId).hostname);
|
const host = toPuny(new URL(signature.keyId).hostname);
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// Stop if the host is blocked.
|
||||||
const meta = await fetchMeta();
|
if (await shouldBlockInstance(host)) {
|
||||||
if (meta.blockedHosts.includes(host)) {
|
|
||||||
return `Blocked request: ${host}`;
|
return `Blocked request: ${host}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,9 +116,9 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
||||||
return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`;
|
return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ブロックしてたら中断
|
// Stop if the host is blocked.
|
||||||
const ldHost = extractDbHost(authUser.user.uri);
|
const ldHost = extractDbHost(authUser.user.uri);
|
||||||
if (meta.blockedHosts.includes(ldHost)) {
|
if (await shouldBlockInstance(ldHost)) {
|
||||||
return `Blocked request: ${ldHost}`;
|
return `Blocked request: ${ldHost}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import renderQuestion from '@/remote/activitypub/renderer/question.js';
|
||||||
import renderCreate from '@/remote/activitypub/renderer/create.js';
|
import renderCreate from '@/remote/activitypub/renderer/create.js';
|
||||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||||
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
||||||
|
import { shouldBlockInstance } from '@/misc/skipped-instances.js';
|
||||||
import { signedGet } from './request.js';
|
import { signedGet } from './request.js';
|
||||||
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
|
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
|
||||||
import { parseUri } from './db-resolver.js';
|
import { parseUri } from './db-resolver.js';
|
||||||
|
@ -67,8 +68,7 @@ export default class Resolver {
|
||||||
return await this.resolveLocal(value);
|
return await this.resolveLocal(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = await fetchMeta();
|
if (await shouldBlockInstance(host)) {
|
||||||
if (meta.blockedHosts.includes(host)) {
|
|
||||||
throw new Error('Instance is blocked');
|
throw new Error('Instance is blocked');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import config from '@/config/index.js';
|
|
||||||
import { createPerson } from '@/remote/activitypub/models/person.js';
|
import { createPerson } from '@/remote/activitypub/models/person.js';
|
||||||
import { createNote } from '@/remote/activitypub/models/note.js';
|
import { createNote } from '@/remote/activitypub/models/note.js';
|
||||||
import DbResolver from '@/remote/activitypub/db-resolver.js';
|
import DbResolver from '@/remote/activitypub/db-resolver.js';
|
||||||
|
@ -7,10 +6,10 @@ import { extractDbHost } from '@/misc/convert-host.js';
|
||||||
import { Users, Notes } from '@/models/index.js';
|
import { Users, Notes } from '@/models/index.js';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
import { CacheableLocalUser, User } from '@/models/entities/user.js';
|
import { CacheableLocalUser, User } from '@/models/entities/user.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
|
||||||
import { isActor, isPost, getApId } from '@/remote/activitypub/type.js';
|
import { isActor, isPost, getApId } from '@/remote/activitypub/type.js';
|
||||||
import { SchemaType } from '@/misc/schema.js';
|
import { SchemaType } from '@/misc/schema.js';
|
||||||
import { HOUR } from '@/const.js';
|
import { HOUR } from '@/const.js';
|
||||||
|
import { shouldBlockInstance } from '@/misc/skipped-instances.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
@ -85,9 +84,11 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
* URIからUserかNoteを解決する
|
* URIからUserかNoteを解決する
|
||||||
*/
|
*/
|
||||||
async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
|
||||||
// ブロックしてたら中断
|
// Stop if the host is blocked.
|
||||||
const fetchedMeta = await fetchMeta();
|
const host = extractDbHost(uri);
|
||||||
if (fetchedMeta.blockedHosts.includes(extractDbHost(uri))) return null;
|
if (await shouldBlockInstance(host)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const dbResolver = new DbResolver();
|
const dbResolver = new DbResolver();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue