feat: implement attachLdSignatureForRelays to control signing of Relayed activities

This commit is contained in:
Hazel K 2024-07-25 10:37:23 -04:00
parent c344705d67
commit 32872181dd
6 changed files with 89 additions and 39 deletions

View file

@ -106,7 +106,7 @@ redis:
# ┌───────────────────────────┐ # ┌───────────────────────────┐
#───┘ MeiliSearch configuration └───────────────────────────── #───┘ 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). # (include notes from remote).
#meilisearch: #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 # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
#videoThumbnailGenerator: https://example.com #videoThumbnailGenerator: https://example.com
# Sign to ActivityPub GET request (default: true) # Sign outgoing ActivityPub GET request (default: true)
signToActivityPubGet: true signToActivityPubGet: true
# Sign outgoing ActivityPub Activities (default: true)
attachLdSignatureForRelays: true
# check that inbound ActivityPub GET requests are signed ("authorized fetch") # check that inbound ActivityPub GET requests are signed ("authorized fetch")
checkActivityPubGetSignature: false checkActivityPubGetSignature: false
# For security reasons, uploading attachments from the intranet is prohibited, # 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)). # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
#allowedPrivateNetworks: [ #allowedPrivateNetworks: [
# '127.0.0.1/32' # '127.0.0.1/32'

View file

@ -270,8 +270,10 @@ proxyRemoteFiles: true
# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
#videoThumbnailGenerator: https://example.com #videoThumbnailGenerator: https://example.com
# Sign to ActivityPub GET request (default: true) # Sign outgoing ActivityPub GET request (default: true)
signToActivityPubGet: true signToActivityPubGet: true
# Sign outgoing ActivityPub Activities (default: true)
attachLdSignatureForRelays: true
# check that inbound ActivityPub GET requests are signed ("authorized fetch") # check that inbound ActivityPub GET requests are signed ("authorized fetch")
checkActivityPubGetSignature: false checkActivityPubGetSignature: false

View file

@ -285,8 +285,10 @@ proxyRemoteFiles: true
# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 # https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
#videoThumbnailGenerator: https://example.com #videoThumbnailGenerator: https://example.com
# Sign to ActivityPub GET request (default: true) # Sign outgoing ActivityPub GET request (default: true)
signToActivityPubGet: true signToActivityPubGet: true
# Sign outgoing ActivityPub Activities (default: true)
attachLdSignatureForRelays: true
# check that inbound ActivityPub GET requests are signed ("authorized fetch") # check that inbound ActivityPub GET requests are signed ("authorized fetch")
checkActivityPubGetSignature: false checkActivityPubGetSignature: false

View file

@ -208,8 +208,10 @@ id: "aidx"
# Media Proxy # Media Proxy
#mediaProxy: https://example.com/proxy #mediaProxy: https://example.com/proxy
# Sign to ActivityPub GET request (default: true) # Sign outgoing ActivityPub GET request (default: true)
signToActivityPubGet: true signToActivityPubGet: true
# Sign outgoing ActivityPub Activities (default: true)
attachLdSignatureForRelays: true
# check that inbound ActivityPub GET requests are signed ("authorized fetch") # check that inbound ActivityPub GET requests are signed ("authorized fetch")
checkActivityPubGetSignature: false checkActivityPubGetSignature: false

View file

@ -4,12 +4,12 @@
*/ */
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url'; import {fileURLToPath} from 'node:url';
import { dirname, resolve } from 'node:path'; import {dirname, resolve} from 'node:path';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import { globSync } from 'glob'; import {globSync} from 'glob';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import type { RedisOptions } from 'ioredis'; import type {RedisOptions} from 'ioredis';
type RedisOptionsSource = Partial<RedisOptions> & { type RedisOptionsSource = Partial<RedisOptions> & {
host: string; host: string;
@ -95,6 +95,7 @@ type Source = {
customMOTD?: string[]; customMOTD?: string[];
signToActivityPubGet?: boolean; signToActivityPubGet?: boolean;
attachLdSignatureForRelays?: boolean;
checkActivityPubGetSignature?: boolean; checkActivityPubGetSignature?: boolean;
perChannelMaxNoteCacheCount?: number; perChannelMaxNoteCacheCount?: number;
@ -161,6 +162,7 @@ export type Config = {
proxyRemoteFiles: boolean | undefined; proxyRemoteFiles: boolean | undefined;
customMOTD: string[] | undefined; customMOTD: string[] | undefined;
signToActivityPubGet: boolean; signToActivityPubGet: boolean;
attachLdSignatureForRelays: boolean;
checkActivityPubGetSignature: boolean | undefined; checkActivityPubGetSignature: boolean | undefined;
version: string; version: string;
@ -291,6 +293,7 @@ export function loadConfig(): Config {
proxyRemoteFiles: config.proxyRemoteFiles, proxyRemoteFiles: config.proxyRemoteFiles,
customMOTD: config.customMOTD, customMOTD: config.customMOTD,
signToActivityPubGet: config.signToActivityPubGet ?? true, signToActivityPubGet: config.signToActivityPubGet ?? true,
attachLdSignatureForRelays: config.attachLdSignatureForRelays ?? true,
checkActivityPubGetSignature: config.checkActivityPubGetSignature, checkActivityPubGetSignature: config.checkActivityPubGetSignature,
mediaProxy: externalMediaProxy ?? internalMediaProxy, mediaProxy: externalMediaProxy ?? internalMediaProxy,
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy, externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,

View file

@ -3,36 +3,69 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { createPublicKey, randomUUID } from 'node:crypto'; import {createPublicKey, randomUUID} from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common'; import {Inject, Injectable} from '@nestjs/common';
import { In } from 'typeorm'; import {In} from 'typeorm';
import * as mfm from '@transfem-org/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { DI } from '@/di-symbols.js'; import {DI} from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type {Config} from '@/config.js';
import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; import type {MiLocalUser, MiPartialLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser} from '@/models/User.js';
import type { IMentionedRemoteUsers, MiNote } from '@/models/Note.js'; import type {IMentionedRemoteUsers, MiNote} from '@/models/Note.js';
import type { MiBlocking } from '@/models/Blocking.js'; import type {MiBlocking} from '@/models/Blocking.js';
import type { MiRelay } from '@/models/Relay.js'; import type {MiRelay} from '@/models/Relay.js';
import type { MiDriveFile } from '@/models/DriveFile.js'; import type {MiDriveFile} from '@/models/DriveFile.js';
import type { MiNoteReaction } from '@/models/NoteReaction.js'; import type {MiNoteReaction} from '@/models/NoteReaction.js';
import type { MiEmoji } from '@/models/Emoji.js'; import type {MiEmoji} from '@/models/Emoji.js';
import type { MiPoll } from '@/models/Poll.js'; import type {MiPoll} from '@/models/Poll.js';
import type { MiPollVote } from '@/models/PollVote.js'; import type {MiPollVote} from '@/models/PollVote.js';
import { UserKeypairService } from '@/core/UserKeypairService.js'; import {UserKeypairService} from '@/core/UserKeypairService.js';
import { MfmService } from '@/core/MfmService.js'; import {MfmService} from '@/core/MfmService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import {UserEntityService} from '@/core/entities/UserEntityService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import {DriveFileEntityService} from '@/core/entities/DriveFileEntityService.js';
import type { MiUserKeypair } from '@/models/UserKeypair.js'; import type {MiUserKeypair} from '@/models/UserKeypair.js';
import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, InstancesRepository } from '@/models/_.js'; import type {
import { bindThis } from '@/decorators.js'; DriveFilesRepository,
import { CustomEmojiService } from '@/core/CustomEmojiService.js'; InstancesRepository,
import { isNotNull } from '@/misc/is-not-null.js'; NotesRepository,
import { IdService } from '@/core/IdService.js'; PollsRepository,
import { MetaService } from '../MetaService.js'; UserProfilesRepository,
import { JsonLdService } from './JsonLdService.js'; UsersRepository
import { ApMfmService } from './ApMfmService.js'; } from '@/models/_.js';
import { CONTEXT } from './misc/contexts.js'; import {bindThis} from '@/decorators.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 {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() @Injectable()
export class ApRendererService { export class ApRendererService {
@ -793,6 +826,12 @@ export class ApRendererService {
@bindThis @bindThis
public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise<IActivity> { public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise<IActivity> {
// 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 keypair = await this.userKeypairService.getUserKeypair(user.id);
const jsonLd = this.jsonLdService.use(); const jsonLd = this.jsonLdService.use();