From 32872181ddf14cfbf006dc93d04eeacac5eaf7a0 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Thu, 25 Jul 2024 10:37:23 -0400 Subject: [PATCH 1/4] feat: implement `attachLdSignatureForRelays` to control signing of Relayed activities --- .config/ci.yml | 8 +- .config/docker_example.yml | 4 +- .config/example.yml | 4 +- chart/files/default.yml | 4 +- packages/backend/src/config.ts | 11 ++- .../src/core/activitypub/ApRendererService.ts | 97 +++++++++++++------ 6 files changed, 89 insertions(+), 39 deletions(-) diff --git a/.config/ci.yml b/.config/ci.yml index c381d21d92..02081e5971 100644 --- a/.config/ci.yml +++ b/.config/ci.yml @@ -106,7 +106,7 @@ redis: # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── -# You can set scope to local (default value) or global +# You can set scope to local (default value) or global # (include notes from remote). #meilisearch: @@ -198,13 +198,15 @@ proxyRemoteFiles: true # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 #videoThumbnailGenerator: https://example.com -# Sign to ActivityPub GET request (default: true) +# Sign outgoing ActivityPub GET request (default: true) signToActivityPubGet: true +# Sign outgoing ActivityPub Activities (default: true) +attachLdSignatureForRelays: true # check that inbound ActivityPub GET requests are signed ("authorized fetch") checkActivityPubGetSignature: false # For security reasons, uploading attachments from the intranet is prohibited, -# but exceptions can be made from the following settings. Default value is "undefined". +# but exceptions can be made from the following settings. Default value is "undefined". # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). #allowedPrivateNetworks: [ # '127.0.0.1/32' diff --git a/.config/docker_example.yml b/.config/docker_example.yml index c22bd83c2e..375753e79f 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -270,8 +270,10 @@ proxyRemoteFiles: true # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 #videoThumbnailGenerator: https://example.com -# Sign to ActivityPub GET request (default: true) +# Sign outgoing ActivityPub GET request (default: true) signToActivityPubGet: true +# Sign outgoing ActivityPub Activities (default: true) +attachLdSignatureForRelays: true # check that inbound ActivityPub GET requests are signed ("authorized fetch") checkActivityPubGetSignature: false diff --git a/.config/example.yml b/.config/example.yml index ae55b983bb..4b6aaae63b 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -285,8 +285,10 @@ proxyRemoteFiles: true # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 #videoThumbnailGenerator: https://example.com -# Sign to ActivityPub GET request (default: true) +# Sign outgoing ActivityPub GET request (default: true) signToActivityPubGet: true +# Sign outgoing ActivityPub Activities (default: true) +attachLdSignatureForRelays: true # check that inbound ActivityPub GET requests are signed ("authorized fetch") checkActivityPubGetSignature: false diff --git a/chart/files/default.yml b/chart/files/default.yml index 2e1381ec57..7c94bcbea3 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -208,8 +208,10 @@ id: "aidx" # Media Proxy #mediaProxy: https://example.com/proxy -# Sign to ActivityPub GET request (default: true) +# Sign outgoing ActivityPub GET request (default: true) signToActivityPubGet: true +# Sign outgoing ActivityPub Activities (default: true) +attachLdSignatureForRelays: true # check that inbound ActivityPub GET requests are signed ("authorized fetch") checkActivityPubGetSignature: false diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 58c4d028aa..10a63f8ae2 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -4,12 +4,12 @@ */ import * as fs from 'node:fs'; -import { fileURLToPath } from 'node:url'; -import { dirname, resolve } from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {dirname, resolve} from 'node:path'; import * as yaml from 'js-yaml'; -import { globSync } from 'glob'; +import {globSync} from 'glob'; import * as Sentry from '@sentry/node'; -import type { RedisOptions } from 'ioredis'; +import type {RedisOptions} from 'ioredis'; type RedisOptionsSource = Partial & { host: string; @@ -95,6 +95,7 @@ type Source = { customMOTD?: string[]; signToActivityPubGet?: boolean; + attachLdSignatureForRelays?: boolean; checkActivityPubGetSignature?: boolean; perChannelMaxNoteCacheCount?: number; @@ -161,6 +162,7 @@ export type Config = { proxyRemoteFiles: boolean | undefined; customMOTD: string[] | undefined; signToActivityPubGet: boolean; + attachLdSignatureForRelays: boolean; checkActivityPubGetSignature: boolean | undefined; version: string; @@ -291,6 +293,7 @@ export function loadConfig(): Config { proxyRemoteFiles: config.proxyRemoteFiles, customMOTD: config.customMOTD, signToActivityPubGet: config.signToActivityPubGet ?? true, + attachLdSignatureForRelays: config.attachLdSignatureForRelays ?? true, checkActivityPubGetSignature: config.checkActivityPubGetSignature, mediaProxy: externalMediaProxy ?? internalMediaProxy, externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy, diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 90784fdc1d..28c5dcf150 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -3,36 +3,69 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { createPublicKey, randomUUID } from 'node:crypto'; -import { Inject, Injectable } from '@nestjs/common'; -import { In } from 'typeorm'; +import {createPublicKey, randomUUID} from 'node:crypto'; +import {Inject, Injectable} from '@nestjs/common'; +import {In} from 'typeorm'; import * as mfm from '@transfem-org/sfm-js'; -import { DI } from '@/di-symbols.js'; -import type { Config } from '@/config.js'; -import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; -import type { IMentionedRemoteUsers, MiNote } from '@/models/Note.js'; -import type { MiBlocking } from '@/models/Blocking.js'; -import type { MiRelay } from '@/models/Relay.js'; -import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiNoteReaction } from '@/models/NoteReaction.js'; -import type { MiEmoji } from '@/models/Emoji.js'; -import type { MiPoll } from '@/models/Poll.js'; -import type { MiPollVote } from '@/models/PollVote.js'; -import { UserKeypairService } from '@/core/UserKeypairService.js'; -import { MfmService } from '@/core/MfmService.js'; -import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; -import type { MiUserKeypair } from '@/models/UserKeypair.js'; -import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, InstancesRepository } from '@/models/_.js'; -import { bindThis } from '@/decorators.js'; -import { CustomEmojiService } from '@/core/CustomEmojiService.js'; -import { isNotNull } from '@/misc/is-not-null.js'; -import { IdService } from '@/core/IdService.js'; -import { MetaService } from '../MetaService.js'; -import { JsonLdService } from './JsonLdService.js'; -import { ApMfmService } from './ApMfmService.js'; -import { CONTEXT } from './misc/contexts.js'; -import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js'; +import {DI} from '@/di-symbols.js'; +import type {Config} from '@/config.js'; +import type {MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser} from '@/models/User.js'; +import type {IMentionedRemoteUsers, MiNote} from '@/models/Note.js'; +import type {MiBlocking} from '@/models/Blocking.js'; +import type {MiRelay} from '@/models/Relay.js'; +import type {MiDriveFile} from '@/models/DriveFile.js'; +import type {MiNoteReaction} from '@/models/NoteReaction.js'; +import type {MiEmoji} from '@/models/Emoji.js'; +import type {MiPoll} from '@/models/Poll.js'; +import type {MiPollVote} from '@/models/PollVote.js'; +import {UserKeypairService} from '@/core/UserKeypairService.js'; +import {MfmService} from '@/core/MfmService.js'; +import {UserEntityService} from '@/core/entities/UserEntityService.js'; +import {DriveFileEntityService} from '@/core/entities/DriveFileEntityService.js'; +import type {MiUserKeypair} from '@/models/UserKeypair.js'; +import type { + DriveFilesRepository, + InstancesRepository, + NotesRepository, + PollsRepository, + UserProfilesRepository, + UsersRepository +} from '@/models/_.js'; +import {bindThis} from '@/decorators.js'; +import {CustomEmojiService} from '@/core/CustomEmojiService.js'; +import {isNotNull} from '@/misc/is-not-null.js'; +import {IdService} from '@/core/IdService.js'; +import {MetaService} from '../MetaService.js'; +import {JsonLdService} from './JsonLdService.js'; +import {ApMfmService} from './ApMfmService.js'; +import {CONTEXT} from './misc/contexts.js'; +import type { + IAccept, + IActivity, + IAdd, + IAnnounce, + IApDocument, + IApEmoji, + IApHashtag, + IApImage, + IApMention, + IBlock, + ICreate, + IDelete, + IFlag, + IFollow, + IKey, + ILike, + IMove, + IObject, + IPost, + IQuestion, + IReject, + IRemove, + ITombstone, + IUndo, + IUpdate +} from './type.js'; @Injectable() export class ApRendererService { @@ -793,6 +826,12 @@ export class ApRendererService { @bindThis public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise { + // When using authorized fetch, Linked Data signatures are often undesired (as it can allow blocked instances to bypass the check). + // We allow admins to disable LD signatures for increased privacy, at the expense of increased incoming fetch (GET) requests. + if (!this.config.attachLdSignatureForRelays) { + return activity; + } + const keypair = await this.userKeypairService.getUserKeypair(user.id); const jsonLd = this.jsonLdService.use(); From fecdff7fa092ca9d8ca0cd50b5694eb3f8279a91 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Fri, 26 Jul 2024 09:42:49 -0400 Subject: [PATCH 2/4] revert import changes --- .../src/core/activitypub/ApRendererService.ts | 91 ++++++------------- 1 file changed, 29 insertions(+), 62 deletions(-) diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 28c5dcf150..8db9199e5d 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -3,69 +3,36 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import {createPublicKey, randomUUID} from 'node:crypto'; -import {Inject, Injectable} from '@nestjs/common'; -import {In} from 'typeorm'; +import { createPublicKey, randomUUID } from 'node:crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; import * as mfm from '@transfem-org/sfm-js'; -import {DI} from '@/di-symbols.js'; -import type {Config} from '@/config.js'; -import type {MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser} from '@/models/User.js'; -import type {IMentionedRemoteUsers, MiNote} from '@/models/Note.js'; -import type {MiBlocking} from '@/models/Blocking.js'; -import type {MiRelay} from '@/models/Relay.js'; -import type {MiDriveFile} from '@/models/DriveFile.js'; -import type {MiNoteReaction} from '@/models/NoteReaction.js'; -import type {MiEmoji} from '@/models/Emoji.js'; -import type {MiPoll} from '@/models/Poll.js'; -import type {MiPollVote} from '@/models/PollVote.js'; -import {UserKeypairService} from '@/core/UserKeypairService.js'; -import {MfmService} from '@/core/MfmService.js'; -import {UserEntityService} from '@/core/entities/UserEntityService.js'; -import {DriveFileEntityService} from '@/core/entities/DriveFileEntityService.js'; -import type {MiUserKeypair} from '@/models/UserKeypair.js'; -import type { - DriveFilesRepository, - InstancesRepository, - NotesRepository, - PollsRepository, - UserProfilesRepository, - UsersRepository -} from '@/models/_.js'; -import {bindThis} from '@/decorators.js'; -import {CustomEmojiService} from '@/core/CustomEmojiService.js'; -import {isNotNull} from '@/misc/is-not-null.js'; -import {IdService} from '@/core/IdService.js'; -import {MetaService} from '../MetaService.js'; -import {JsonLdService} from './JsonLdService.js'; -import {ApMfmService} from './ApMfmService.js'; -import {CONTEXT} from './misc/contexts.js'; -import type { - IAccept, - IActivity, - IAdd, - IAnnounce, - IApDocument, - IApEmoji, - IApHashtag, - IApImage, - IApMention, - IBlock, - ICreate, - IDelete, - IFlag, - IFollow, - IKey, - ILike, - IMove, - IObject, - IPost, - IQuestion, - IReject, - IRemove, - ITombstone, - IUndo, - IUpdate -} from './type.js'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; +import type { IMentionedRemoteUsers, MiNote } from '@/models/Note.js'; +import type { MiBlocking } from '@/models/Blocking.js'; +import type { MiRelay } from '@/models/Relay.js'; +import type { MiDriveFile } from '@/models/DriveFile.js'; +import type { MiNoteReaction } from '@/models/NoteReaction.js'; +import type { MiEmoji } from '@/models/Emoji.js'; +import type { MiPoll } from '@/models/Poll.js'; +import type { MiPollVote } from '@/models/PollVote.js'; +import { UserKeypairService } from '@/core/UserKeypairService.js'; +import { MfmService } from '@/core/MfmService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; +import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; +import type { MiUserKeypair } from '@/models/UserKeypair.js'; +import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, InstancesRepository } from '@/models/_.js'; +import { bindThis } from '@/decorators.js'; +import { CustomEmojiService } from '@/core/CustomEmojiService.js'; +import { isNotNull } from '@/misc/is-not-null.js'; +import { IdService } from '@/core/IdService.js'; +import { MetaService } from '../MetaService.js'; +import { JsonLdService } from './JsonLdService.js'; +import { ApMfmService } from './ApMfmService.js'; +import { CONTEXT } from './misc/contexts.js'; +import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js'; @Injectable() export class ApRendererService { From 916509dd6a6216277b2439a7e622890c563c4a78 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Fri, 26 Jul 2024 10:17:02 -0400 Subject: [PATCH 3/4] revert more import changes --- packages/backend/src/config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 10a63f8ae2..c8170a6a50 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -4,12 +4,12 @@ */ import * as fs from 'node:fs'; -import {fileURLToPath} from 'node:url'; -import {dirname, resolve} from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; import * as yaml from 'js-yaml'; -import {globSync} from 'glob'; +import { globSync } from 'glob'; import * as Sentry from '@sentry/node'; -import type {RedisOptions} from 'ioredis'; +import type { RedisOptions } from 'ioredis'; type RedisOptionsSource = Partial & { host: string; From 378408226b5e8968313058de4862b9916d08d6e0 Mon Sep 17 00:00:00 2001 From: Hazel K Date: Fri, 26 Jul 2024 22:45:07 -0400 Subject: [PATCH 4/4] tweak wording --- .config/ci.yml | 3 +++ .config/docker_example.yml | 3 +++ .config/example.yml | 3 +++ chart/files/default.yml | 3 +++ packages/backend/src/core/activitypub/ApRendererService.ts | 5 +++-- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.config/ci.yml b/.config/ci.yml index 02081e5971..44092d3662 100644 --- a/.config/ci.yml +++ b/.config/ci.yml @@ -201,6 +201,9 @@ proxyRemoteFiles: true # Sign outgoing ActivityPub GET request (default: true) signToActivityPubGet: true # Sign outgoing ActivityPub Activities (default: true) +# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity. +# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances. +# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests. attachLdSignatureForRelays: true # check that inbound ActivityPub GET requests are signed ("authorized fetch") checkActivityPubGetSignature: false diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 375753e79f..f4645d672d 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -273,6 +273,9 @@ proxyRemoteFiles: true # Sign outgoing ActivityPub GET request (default: true) signToActivityPubGet: true # Sign outgoing ActivityPub Activities (default: true) +# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity. +# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances. +# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests. attachLdSignatureForRelays: true # check that inbound ActivityPub GET requests are signed ("authorized fetch") checkActivityPubGetSignature: false diff --git a/.config/example.yml b/.config/example.yml index 4b6aaae63b..21e85b7b89 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -288,6 +288,9 @@ proxyRemoteFiles: true # Sign outgoing ActivityPub GET request (default: true) signToActivityPubGet: true # Sign outgoing ActivityPub Activities (default: true) +# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity. +# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances. +# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests. attachLdSignatureForRelays: true # check that inbound ActivityPub GET requests are signed ("authorized fetch") checkActivityPubGetSignature: false diff --git a/chart/files/default.yml b/chart/files/default.yml index 7c94bcbea3..aab7ed6ce1 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -211,6 +211,9 @@ id: "aidx" # Sign outgoing ActivityPub GET request (default: true) signToActivityPubGet: true # Sign outgoing ActivityPub Activities (default: true) +# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity. +# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances. +# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests. attachLdSignatureForRelays: true # check that inbound ActivityPub GET requests are signed ("authorized fetch") checkActivityPubGetSignature: false diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 8db9199e5d..98fc647a83 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -793,8 +793,9 @@ export class ApRendererService { @bindThis public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise { - // When using authorized fetch, Linked Data signatures are often undesired (as it can allow blocked instances to bypass the check). - // We allow admins to disable LD signatures for increased privacy, at the expense of increased incoming fetch (GET) requests. + // Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity. + // When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances. + // This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests. if (!this.config.attachLdSignatureForRelays) { return activity; }