feat: implement attachLdSignatureForRelays to control signing of Relayed activities
				
					
				
			This commit is contained in:
		
							parent
							
								
									c344705d67
								
							
						
					
					
						commit
						32872181dd
					
				
					 6 changed files with 89 additions and 39 deletions
				
			
		| 
						 | 
				
			
			@ -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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<RedisOptions> & {
 | 
			
		||||
	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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<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 jsonLd = this.jsonLdService.use();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue