Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: 連合をホワイトリスト制にできるように #14643

Merged
merged 1 commit into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください
- Feat: パスキーでログインボタンを実装 (#14574)
- Feat: フォローされた際のメッセージを設定できるように
- Feat: 連合をホワイトリスト制にできるように
- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
- Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680)
Expand Down
10 changes: 9 additions & 1 deletion locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,14 @@ export interface Locale extends ILocale {
* メディアサイレンスしたいサーバーのホストを改行で区切って設定します。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。ブロックしたインスタンスには影響しません。
*/
"mediaSilencedInstancesDescription": string;
/**
* 連合を許可するサーバー
*/
"federationAllowedHosts": string;
/**
* 連合を許可するサーバーのホストを改行で区切って設定します。
*/
"federationAllowedHostsDescription": string;
/**
* ミュートとブロック
*/
Expand Down Expand Up @@ -8730,7 +8738,7 @@ export interface Locale extends ILocale {
*/
"followedMessage": string;
/**
* フォローされた時に相手に表示するメッセージを設定できます
* フォローされた時に相手に表示する短いメッセージを設定できます
*/
"followedMessageDescription": string;
/**
Expand Down
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ silencedInstances: "サイレンスしたサーバー"
silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになります。ブロックしたインスタンスには影響しません。"
mediaSilencedInstances: "メディアサイレンスしたサーバー"
mediaSilencedInstancesDescription: "メディアサイレンスしたいサーバーのホストを改行で区切って設定します。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。ブロックしたインスタンスには影響しません。"
federationAllowedHosts: "連合を許可するサーバー"
federationAllowedHostsDescription: "連合を許可するサーバーのホストを改行で区切って設定します。"
muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー"
Expand Down
18 changes: 18 additions & 0 deletions packages/backend/migration/1727512908322-meta-federation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class MetaFederation1727512908322 {
name = 'MetaFederation1727512908322'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "federation" character varying(128) NOT NULL DEFAULT 'all'`);
await queryRunner.query(`ALTER TABLE "meta" ADD "federationHosts" character varying(1024) array NOT NULL DEFAULT '{}'`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "federationHosts"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "federation"`);
}
}
19 changes: 19 additions & 0 deletions packages/backend/src/core/UtilityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import RE2 from 're2';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
import { MiMeta } from '@/models/Meta.js';

@Injectable()
export class UtilityService {
constructor(
@Inject(DI.config)
private config: Config,

@Inject(DI.meta)
private meta: MiMeta,
) {
}

Expand Down Expand Up @@ -105,4 +109,19 @@ export class UtilityService {
if (host == null) return null;
return toASCII(host.toLowerCase());
}

@bindThis
public isFederationAllowedHost(host: string): boolean {
if (this.meta.federation === 'none') return false;
if (this.meta.federation === 'specified' && !this.meta.federationHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`))) return false;
if (this.isBlockedHost(this.meta.blockedHosts, host)) return false;

return true;
}

@bindThis
public isFederationAllowedUri(uri: string): boolean {
const host = this.extractDbHost(uri);
return this.isFederationAllowedHost(host);
}
}
4 changes: 2 additions & 2 deletions packages/backend/src/core/activitypub/ApInboxService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,8 @@ export class ApInboxService {
return;
}

// アナウンス先をブロックしてたら中断
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.extractDbHost(uri))) return;
// アナウンス先が許可されているかチェック
if (!this.utilityService.isFederationAllowedUri(uri)) return;

const unlock = await this.appLockService.getApLock(uri);

Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/core/activitypub/ApResolverService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class Resolver {
return await this.resolveLocal(value);
}

if (this.utilityService.isBlockedHost(this.meta.blockedHosts, host)) {
if (!this.utilityService.isFederationAllowedHost(host)) {
throw new Error('Instance is blocked');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,7 @@ export class ApNoteService {
public async resolveNote(value: string | IObject, options: { sentFrom?: URL, resolver?: Resolver } = {}): Promise<MiNote | null> {
const uri = getApId(value);

// ブロックしていたら中断
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.extractDbHost(uri))) {
if (!this.utilityService.isFederationAllowedUri(uri)) {
throw new StatusError('blocked host', 451);
}

Expand Down
13 changes: 13 additions & 0 deletions packages/backend/src/models/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,4 +630,17 @@ export class MiMeta {
nullable: true,
})
public urlPreviewUserAgent: string | null;

@Column('varchar', {
length: 128,
default: 'all',
})
public federation: 'all' | 'specified' | 'none';

@Column('varchar', {
length: 1024,
array: true,
default: '{}',
})
public federationHosts: string[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ export class DeliverProcessorService {
public async process(job: Bull.Job<DeliverJobData>): Promise<string> {
const { host } = new URL(job.data.to);

// ブロックしてたら中断
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, this.utilityService.toPuny(host))) {
if (!this.utilityService.isFederationAllowedUri(job.data.to)) {
return 'skip (blocked)';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ export class InboxProcessorService implements OnApplicationShutdown {

const host = this.utilityService.toPuny(new URL(signature.keyId).hostname);

// ブロックしてたら中断
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, host)) {
if (!this.utilityService.isFederationAllowedHost(host)) {
return `Blocked request: ${host}`;
}

Expand Down Expand Up @@ -175,9 +174,8 @@ export class InboxProcessorService implements OnApplicationShutdown {
throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`);
}

// ブロックしてたら中断
const ldHost = this.utilityService.extractDbHost(authUser.user.uri);
if (this.utilityService.isBlockedHost(this.meta.blockedHosts, ldHost)) {
if (!this.utilityService.isFederationAllowedHost(ldHost)) {
throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
}
} else {
Expand Down
14 changes: 14 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,18 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
federation: {
type: 'string',
optional: false, nullable: false,
},
federationHosts: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
},
},
} as const;
Expand Down Expand Up @@ -630,6 +642,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
urlPreviewUserAgent: instance.urlPreviewUserAgent,
urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
federation: instance.federation,
federationHosts: instance.federationHosts,
};
});
}
Expand Down
18 changes: 18 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/update-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ export const paramDef = {
urlPreviewRequireContentLength: { type: 'boolean' },
urlPreviewUserAgent: { type: 'string', nullable: true },
urlPreviewSummaryProxyUrl: { type: 'string', nullable: true },
federation: {
type: 'string',
enum: ['all', 'none', 'specified'],
},
federationHosts: {
type: 'array',
items: {
type: 'string',
},
},
},
required: [],
} as const;
Expand Down Expand Up @@ -637,6 +647,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.urlPreviewSummaryProxyUrl = value === '' ? null : value;
}

if (ps.federation !== undefined) {
set.federation = ps.federation;
}

if (Array.isArray(ps.federationHosts)) {
set.blockedHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase());
}

const before = await this.metaService.fetch(true);

await this.metaService.update(set);
Expand Down
8 changes: 1 addition & 7 deletions packages/backend/src/server/api/endpoints/ap/show.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { ApiError } from '../../error.js';
import { MiMeta } from '@/models/_.js';
import { DI } from '@/di-symbols.js';

export const meta = {
tags: ['federation'],
Expand Down Expand Up @@ -89,9 +87,6 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.meta)
private serverSettings: MiMeta,

private utilityService: UtilityService,
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
Expand All @@ -115,8 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
*/
@bindThis
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
// ブロックしてたら中断
if (this.utilityService.isBlockedHost(this.serverSettings.blockedHosts, this.utilityService.extractDbHost(uri))) return null;
if (!this.utilityService.isFederationAllowedUri(uri)) return null;

let local = await this.mergePack(me, ...await Promise.all([
this.apDbResolverService.getUserFromApId(uri),
Expand Down
37 changes: 37 additions & 0 deletions packages/frontend/src/pages/admin/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,31 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</MkFolder>

<MkFolder>
<template #icon><i class="ti ti-planet"></i></template>
<template #label>{{ i18n.ts.federation }}</template>
<template v-if="federationForm.savedState.federation === 'all'" #suffix>{{ i18n.ts.all }}</template>
<template v-else-if="federationForm.savedState.federation === 'specified'" #suffix>{{ i18n.ts.specifyHost }}</template>
<template v-else-if="federationForm.savedState.federation === 'none'" #suffix>{{ i18n.ts.none }}</template>
<template v-if="federationForm.modified.value" #footer>
<MkFormFooter :form="federationForm"/>
</template>

<div class="_gaps">
<MkRadios v-model="federationForm.state.federation">
<template #label>{{ i18n.ts.behavior }}<span v-if="federationForm.modifiedStates.federation" class="_modified">{{ i18n.ts.modified }}</span></template>
<option value="all">{{ i18n.ts.all }}</option>
<option value="specified">{{ i18n.ts.specifyHost }}</option>
<option value="none">{{ i18n.ts.none }}</option>
</MkRadios>

<MkTextarea v-if="federationForm.state.federation === 'specified'" v-model="federationForm.state.federationHosts">
<template #label>{{ i18n.ts.federationAllowedHosts }}<span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template>
<template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template>
</MkTextarea>
</div>
</MkFolder>

<MkFolder>
<template #icon><i class="ti ti-ghost"></i></template>
<template #label>{{ i18n.ts.proxyAccount }}</template>
Expand Down Expand Up @@ -248,6 +273,7 @@ import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import { useForm } from '@/scripts/use-form.js';
import MkFormFooter from '@/components/MkFormFooter.vue';
import MkRadios from '@/components/MkRadios.vue';

const meta = await misskeyApi('admin/meta');

Expand Down Expand Up @@ -341,6 +367,17 @@ const urlPreviewForm = useForm({
fetchInstance(true);
});

const federationForm = useForm({
federation: meta.federation,
federationHosts: meta.federationHosts.join('\n'),
}, async (state) => {
await os.apiWithDialog('admin/update-meta', {
federation: state.federation,
federationHosts: state.federationHosts.split('\n'),
});
fetchInstance(true);
});

function chooseProxyAccount() {
os.selectUser({ localOnly: true }).then(user => {
proxyAccount.value = user;
Expand Down
Loading