Add SSRF protection
This commit is contained in:
parent
af9f20d60c
commit
d24d221550
|
@ -436,6 +436,10 @@ federation:
|
||||||
# Enable ActivityPub endpoints (inbox/outbox)
|
# Enable ActivityPub endpoints (inbox/outbox)
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
# Prevent SSRF requests (requests to your internal network for example) by checking the request IP address
|
||||||
|
# More information about SSRF: https://portswigger.net/web-security/ssrf
|
||||||
|
prevent_ssrf: true
|
||||||
|
|
||||||
# Some federated software such as Mastodon may require an HTTP signature to access content
|
# Some federated software such as Mastodon may require an HTTP signature to access content
|
||||||
sign_federated_fetches: true
|
sign_federated_fetches: true
|
||||||
|
|
||||||
|
|
|
@ -434,6 +434,10 @@ federation:
|
||||||
# Enable ActivityPub endpoints (inbox/outbox)
|
# Enable ActivityPub endpoints (inbox/outbox)
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
# Prevent SSRF requests (requests to your internal network for example) by checking the request IP address
|
||||||
|
# More information about SSRF: https://portswigger.net/web-security/ssrf
|
||||||
|
prevent_ssrf: true
|
||||||
|
|
||||||
# Some federated software such as Mastodon may require an HTTP signature to access content
|
# Some federated software such as Mastodon may require an HTTP signature to access content
|
||||||
sign_federated_fetches: true
|
sign_federated_fetches: true
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,9 @@ plugins:
|
||||||
check_latest_versions_interval: '10 minutes'
|
check_latest_versions_interval: '10 minutes'
|
||||||
|
|
||||||
federation:
|
federation:
|
||||||
|
# We use internal IP address in PeerTube tests
|
||||||
|
prevent_ssrf: false
|
||||||
|
|
||||||
videos:
|
videos:
|
||||||
federate_unlisted: true
|
federate_unlisted: true
|
||||||
cleanup_remote_interactions: false
|
cleanup_remote_interactions: false
|
||||||
|
|
|
@ -136,6 +136,7 @@
|
||||||
"fluent-ffmpeg": "^2.1.0",
|
"fluent-ffmpeg": "^2.1.0",
|
||||||
"fs-extra": "^11.1.0",
|
"fs-extra": "^11.1.0",
|
||||||
"got": "^13.0.0",
|
"got": "^13.0.0",
|
||||||
|
"got-ssrf": "^3.0.0",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
"hpagent": "^1.0.0",
|
"hpagent": "^1.0.0",
|
||||||
"http-problem-details": "^0.1.5",
|
"http-problem-details": "^0.1.5",
|
||||||
|
|
|
@ -14,6 +14,7 @@ import './logs.js'
|
||||||
import './reverse-proxy.js'
|
import './reverse-proxy.js'
|
||||||
import './services.js'
|
import './services.js'
|
||||||
import './slow-follows.js'
|
import './slow-follows.js'
|
||||||
|
import './ssrf.js'
|
||||||
import './stats.js'
|
import './stats.js'
|
||||||
import './tracker.js'
|
import './tracker.js'
|
||||||
import './no-client.js'
|
import './no-client.js'
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
|
import {
|
||||||
|
cleanupTests,
|
||||||
|
createMultipleServers,
|
||||||
|
doubleFollow,
|
||||||
|
PeerTubeServer,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
waitJobs
|
||||||
|
} from '@peertube/peertube-server-commands'
|
||||||
|
import { MockHTTP } from '@tests/shared/mock-servers/mock-http.js'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { HttpStatusCode } from '../../../../models/src/http/http-status-codes.js'
|
||||||
|
|
||||||
|
describe('Test SSRF requests', function () {
|
||||||
|
let servers: PeerTubeServer[] = []
|
||||||
|
|
||||||
|
let baseServerConfig: any
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(240000)
|
||||||
|
|
||||||
|
const mockHTTP = new MockHTTP()
|
||||||
|
const port = await mockHTTP.initialize()
|
||||||
|
|
||||||
|
baseServerConfig = {
|
||||||
|
plugins: {
|
||||||
|
index: {
|
||||||
|
url: `http://127.0.0.1:${port}/redirect/https://packages.joinpeertube.org`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servers = await createMultipleServers(2, baseServerConfig)
|
||||||
|
await setAccessTokensToServers(servers)
|
||||||
|
|
||||||
|
await doubleFollow(servers[0], servers[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Disabled SSRF protection', function () {
|
||||||
|
|
||||||
|
it('Should not forbid non-unicast federation', async function () {
|
||||||
|
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await servers[1].videos.get({ id: uuid })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fetch plugin index', async function () {
|
||||||
|
const { total } = await servers[0].plugins.listAvailable({ count: 10 })
|
||||||
|
|
||||||
|
expect(total).to.be.at.least(15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Enabled SSRF protection', function () {
|
||||||
|
before(async function () {
|
||||||
|
await servers[0].kill()
|
||||||
|
|
||||||
|
await servers[0].run({
|
||||||
|
...baseServerConfig,
|
||||||
|
|
||||||
|
federation: { prevent_ssrf: true }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should forbid non-unicast federation', async function () {
|
||||||
|
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await servers[1].videos.get({ id: uuid, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should still allow plugin index search on internal network', async function () {
|
||||||
|
const { total } = await servers[0].plugins.listAvailable({ count: 10 })
|
||||||
|
|
||||||
|
expect(total).to.be.at.least(15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await cleanupTests(servers)
|
||||||
|
})
|
||||||
|
})
|
|
@ -8,6 +8,11 @@ export class MockHTTP {
|
||||||
async initialize () {
|
async initialize () {
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
|
app.get('/redirect/*', (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
|
||||||
|
return res.redirect(req.params[0])
|
||||||
|
})
|
||||||
|
|
||||||
app.get('/*', (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
app.get('/*', (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
return res.sendStatus(200)
|
return res.sendStatus(200)
|
||||||
})
|
})
|
||||||
|
|
|
@ -78,8 +78,8 @@ async function searchVideoChannelsIndex (query: VideoChannelsSearchQueryAfterSan
|
||||||
try {
|
try {
|
||||||
logger.debug('Doing video channels search index request on %s.', url, { body })
|
logger.debug('Doing video channels search index request on %s.', url, { body })
|
||||||
|
|
||||||
const { body: searchIndexResult } = await doJSONRequest<ResultList<VideoChannel>>(url, { method: 'POST', json: body })
|
const searchIndexResult = await doJSONRequest<ResultList<VideoChannel>>(url, { method: 'POST', json: body, preventSSRF: false })
|
||||||
const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.video-channels.index.list.result')
|
const jsonResult = await Hooks.wrapObject(searchIndexResult.body, 'filter:api.search.video-channels.index.list.result')
|
||||||
|
|
||||||
return res.json(jsonResult)
|
return res.json(jsonResult)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -69,8 +69,8 @@ async function searchVideoPlaylistsIndex (query: VideoPlaylistsSearchQueryAfterS
|
||||||
try {
|
try {
|
||||||
logger.debug('Doing video playlists search index request on %s.', url, { body })
|
logger.debug('Doing video playlists search index request on %s.', url, { body })
|
||||||
|
|
||||||
const { body: searchIndexResult } = await doJSONRequest<ResultList<VideoPlaylist>>(url, { method: 'POST', json: body })
|
const searchIndexResult = await doJSONRequest<ResultList<VideoPlaylist>>(url, { method: 'POST', json: body, preventSSRF: false })
|
||||||
const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.video-playlists.index.list.result')
|
const jsonResult = await Hooks.wrapObject(searchIndexResult.body, 'filter:api.search.video-playlists.index.list.result')
|
||||||
|
|
||||||
return res.json(jsonResult)
|
return res.json(jsonResult)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -87,8 +87,8 @@ async function searchVideosIndex (query: VideosSearchQueryAfterSanitize, res: ex
|
||||||
try {
|
try {
|
||||||
logger.debug('Doing videos search index request on %s.', url, { body })
|
logger.debug('Doing videos search index request on %s.', url, { body })
|
||||||
|
|
||||||
const { body: searchIndexResult } = await doJSONRequest<ResultList<Video>>(url, { method: 'POST', json: body })
|
const searchIndexResult = await doJSONRequest<ResultList<Video>>(url, { method: 'POST', json: body, preventSSRF: false })
|
||||||
const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.videos.index.list.result')
|
const jsonResult = await Hooks.wrapObject(searchIndexResult.body, 'filter:api.search.videos.index.list.result')
|
||||||
|
|
||||||
return res.json(jsonResult)
|
return res.json(jsonResult)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import maxmind, { CityResponse, CountryResponse, Reader } from 'maxmind'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { isArray } from './custom-validators/misc.js'
|
import { isArray } from './custom-validators/misc.js'
|
||||||
import { logger, loggerTagsFactory } from './logger.js'
|
import { logger, loggerTagsFactory } from './logger.js'
|
||||||
import { isBinaryResponse, peertubeGot } from './requests.js'
|
import { isBinaryResponse, unsafeSSRFGot } from './requests.js'
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('geo-ip')
|
const lTags = loggerTagsFactory('geo-ip')
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ export class GeoIP {
|
||||||
const gotOptions = { context: { bodyKBLimit: 800_000 }, responseType: 'buffer' as 'buffer' }
|
const gotOptions = { context: { bodyKBLimit: 800_000 }, responseType: 'buffer' as 'buffer' }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const gotResult = await peertubeGot(url, gotOptions)
|
const gotResult = await unsafeSSRFGot(url, gotOptions)
|
||||||
|
|
||||||
if (!isBinaryResponse(gotResult)) {
|
if (!isBinaryResponse(gotResult)) {
|
||||||
throw new Error('Not a binary response')
|
throw new Error('Not a binary response')
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import httpSignature from '@peertube/http-signature'
|
import httpSignature from '@peertube/http-signature'
|
||||||
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
import { createWriteStream } from 'fs'
|
import { createWriteStream } from 'fs'
|
||||||
import { remove } from 'fs-extra/esm'
|
import { remove } from 'fs-extra/esm'
|
||||||
import got, { CancelableRequest, OptionsInit, OptionsOfTextResponseBody, OptionsOfUnknownResponseBody, RequestError, Response } from 'got'
|
import got, { CancelableRequest, OptionsInit, OptionsOfTextResponseBody, OptionsOfUnknownResponseBody, RequestError, Response } from 'got'
|
||||||
|
import { gotSsrf } from 'got-ssrf'
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'
|
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'
|
||||||
import { ACTIVITY_PUB, BINARY_CONTENT_TYPES, PEERTUBE_VERSION, REQUEST_TIMEOUTS, WEBSERVER } from '../initializers/constants.js'
|
import { ACTIVITY_PUB, BINARY_CONTENT_TYPES, PEERTUBE_VERSION, REQUEST_TIMEOUTS, WEBSERVER } from '../initializers/constants.js'
|
||||||
import { pipelinePromise } from './core-utils.js'
|
import { pipelinePromise } from './core-utils.js'
|
||||||
|
@ -35,8 +37,8 @@ export type PeerTubeRequestOptions = {
|
||||||
followRedirect?: boolean
|
followRedirect?: boolean
|
||||||
} & Pick<OptionsInit, 'headers' | 'json' | 'method' | 'searchParams'>
|
} & Pick<OptionsInit, 'headers' | 'json' | 'method' | 'searchParams'>
|
||||||
|
|
||||||
export const peertubeGot = got.extend({
|
export const unsafeSSRFGot = got.extend({
|
||||||
...getAgent(),
|
...getProxyAgent(),
|
||||||
|
|
||||||
headers: {
|
headers: {
|
||||||
'user-agent': getUserAgent()
|
'user-agent': getUserAgent()
|
||||||
|
@ -116,6 +118,12 @@ export const peertubeGot = got.extend({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const peertubeGot = CONFIG.FEDERATION.PREVENT_SSRF
|
||||||
|
? got.extend(gotSsrf, unsafeSSRFGot)
|
||||||
|
: unsafeSSRFGot
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export function doRequest (url: string, options: PeerTubeRequestOptions = {}) {
|
export function doRequest (url: string, options: PeerTubeRequestOptions = {}) {
|
||||||
const gotOptions = buildGotOptions(options) as OptionsOfTextResponseBody
|
const gotOptions = buildGotOptions(options) as OptionsOfTextResponseBody
|
||||||
|
|
||||||
|
@ -123,10 +131,14 @@ export function doRequest (url: string, options: PeerTubeRequestOptions = {}) {
|
||||||
.catch(err => { throw buildRequestError(err) })
|
.catch(err => { throw buildRequestError(err) })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doJSONRequest <T> (url: string, options: PeerTubeRequestOptions = {}) {
|
export function doJSONRequest <T> (url: string, options: PeerTubeRequestOptions & { preventSSRF?: false } = {}) {
|
||||||
const gotOptions = buildGotOptions(options)
|
const gotOptions = buildGotOptions(options)
|
||||||
|
|
||||||
return peertubeGot<T>(url, { ...gotOptions, responseType: 'json' })
|
const gotInstance = options.preventSSRF === false
|
||||||
|
? unsafeSSRFGot
|
||||||
|
: peertubeGot
|
||||||
|
|
||||||
|
return gotInstance<T>(url, { ...gotOptions, responseType: 'json' })
|
||||||
.catch(err => { throw buildRequestError(err) })
|
.catch(err => { throw buildRequestError(err) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +166,7 @@ export function generateRequestStream (url: string, options: PeerTubeRequestOpti
|
||||||
return peertubeGot.stream(url, { ...gotOptions, isStream: true })
|
return peertubeGot.stream(url, { ...gotOptions, isStream: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAgent () {
|
export function getProxyAgent () {
|
||||||
if (!isProxyEnabled()) return {}
|
if (!isProxyEnabled()) return {}
|
||||||
|
|
||||||
const proxy = getProxy()
|
const proxy = getProxy()
|
||||||
|
@ -178,10 +190,6 @@ export function getAgent () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserAgent () {
|
|
||||||
return `PeerTube/${PEERTUBE_VERSION} (+${WEBSERVER.URL})`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isBinaryResponse (result: Response<any>) {
|
export function isBinaryResponse (result: Response<any>) {
|
||||||
return BINARY_CONTENT_TYPES.has(result.headers['content-type'])
|
return BINARY_CONTENT_TYPES.has(result.headers['content-type'])
|
||||||
}
|
}
|
||||||
|
@ -190,6 +198,10 @@ export function isBinaryResponse (result: Response<any>) {
|
||||||
// Private
|
// Private
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function getUserAgent () {
|
||||||
|
return `PeerTube/${PEERTUBE_VERSION} (+${WEBSERVER.URL})`
|
||||||
|
}
|
||||||
|
|
||||||
function buildGotOptions (options: PeerTubeRequestOptions): OptionsOfUnknownResponseBody {
|
function buildGotOptions (options: PeerTubeRequestOptions): OptionsOfUnknownResponseBody {
|
||||||
const { activityPub, bodyKBLimit = 3000 } = options
|
const { activityPub, bodyKBLimit = 3000 } = options
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { OptionsOfBufferResponseBody } from 'got'
|
||||||
import { dirname, join } from 'path'
|
import { dirname, join } from 'path'
|
||||||
import { logger, loggerTagsFactory } from '../logger.js'
|
import { logger, loggerTagsFactory } from '../logger.js'
|
||||||
import { getProxy, isProxyEnabled } from '../proxy.js'
|
import { getProxy, isProxyEnabled } from '../proxy.js'
|
||||||
import { isBinaryResponse, peertubeGot } from '../requests.js'
|
import { isBinaryResponse, unsafeSSRFGot } from '../requests.js'
|
||||||
|
|
||||||
type ProcessOptions = Pick<ExecaNodeOptions, 'cwd' | 'maxBuffer'>
|
type ProcessOptions = Pick<ExecaNodeOptions, 'cwd' | 'maxBuffer'>
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ export class YoutubeDLCLI {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let gotResult = await peertubeGot(url, gotOptions)
|
let gotResult = await unsafeSSRFGot(url, gotOptions)
|
||||||
|
|
||||||
if (!isBinaryResponse(gotResult)) {
|
if (!isBinaryResponse(gotResult)) {
|
||||||
const json = JSON.parse(gotResult.body.toString())
|
const json = JSON.parse(gotResult.body.toString())
|
||||||
|
@ -56,7 +56,7 @@ export class YoutubeDLCLI {
|
||||||
const releaseAsset = latest.assets.find(a => a.name === releaseName)
|
const releaseAsset = latest.assets.find(a => a.name === releaseName)
|
||||||
if (!releaseAsset) throw new Error(`Cannot find appropriate release with name ${releaseName} in release assets`)
|
if (!releaseAsset) throw new Error(`Cannot find appropriate release with name ${releaseName} in release assets`)
|
||||||
|
|
||||||
gotResult = await peertubeGot(releaseAsset.browser_download_url, gotOptions)
|
gotResult = await unsafeSSRFGot(releaseAsset.browser_download_url, gotOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isBinaryResponse(gotResult)) {
|
if (!isBinaryResponse(gotResult)) {
|
||||||
|
|
|
@ -74,7 +74,7 @@ function checkMissedConfig () {
|
||||||
'feeds.videos.count', 'feeds.comments.count',
|
'feeds.videos.count', 'feeds.comments.count',
|
||||||
'geo_ip.enabled', 'geo_ip.country.database_url', 'geo_ip.city.database_url',
|
'geo_ip.enabled', 'geo_ip.country.database_url', 'geo_ip.city.database_url',
|
||||||
'remote_redundancy.videos.accept_from',
|
'remote_redundancy.videos.accept_from',
|
||||||
'federation.enabled', 'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions',
|
'federation.enabled', 'federation.prevent_ssrf', 'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions',
|
||||||
'peertube.check_latest_version.enabled', 'peertube.check_latest_version.url',
|
'peertube.check_latest_version.enabled', 'peertube.check_latest_version.url',
|
||||||
'search.remote_uri.users', 'search.remote_uri.anonymous', 'search.search_index.enabled', 'search.search_index.url',
|
'search.remote_uri.users', 'search.remote_uri.anonymous', 'search.search_index.enabled', 'search.search_index.url',
|
||||||
'search.search_index.disable_local_search', 'search.search_index.is_default_search',
|
'search.search_index.disable_local_search', 'search.search_index.is_default_search',
|
||||||
|
|
|
@ -342,6 +342,7 @@ const CONFIG = {
|
||||||
},
|
},
|
||||||
FEDERATION: {
|
FEDERATION: {
|
||||||
ENABLED: config.get<boolean>('federation.enabled'),
|
ENABLED: config.get<boolean>('federation.enabled'),
|
||||||
|
PREVENT_SSRF: config.get<boolean>('federation.prevent_ssrf'),
|
||||||
VIDEOS: {
|
VIDEOS: {
|
||||||
FEDERATE_UNLISTED: config.get<boolean>('federation.videos.federate_unlisted'),
|
FEDERATE_UNLISTED: config.get<boolean>('federation.videos.federate_unlisted'),
|
||||||
CLEANUP_REMOTE_INTERACTIONS: config.get<boolean>('federation.videos.cleanup_remote_interactions')
|
CLEANUP_REMOTE_INTERACTIONS: config.get<boolean>('federation.videos.cleanup_remote_interactions')
|
||||||
|
|
|
@ -40,7 +40,7 @@ async function processActivityPubFollow (job: Job) {
|
||||||
|
|
||||||
targetActor = await getOrCreateAPActor(actorUrl, 'all')
|
targetActor = await getOrCreateAPActor(actorUrl, 'all')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(`Do not follow ${handle} because we could not find the actor URL (in database or using webfinger)`)
|
logger.warn(`Do not follow ${handle} because we could not find the actor URL (in database or using webfinger)`, { err })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import type { S3Client } from '@aws-sdk/client-s3'
|
import type { S3Client } from '@aws-sdk/client-s3'
|
||||||
import { logger } from '@server/helpers/logger.js'
|
import { logger } from '@server/helpers/logger.js'
|
||||||
import { isProxyEnabled } from '@server/helpers/proxy.js'
|
import { isProxyEnabled } from '@server/helpers/proxy.js'
|
||||||
import { getAgent } from '@server/helpers/requests.js'
|
import { getProxyAgent } from '@server/helpers/requests.js'
|
||||||
import { CONFIG } from '@server/initializers/config.js'
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
import { lTags } from './logger.js'
|
import { lTags } from './logger.js'
|
||||||
|
|
||||||
async function getProxyRequestHandler () {
|
async function getProxyRequestHandler () {
|
||||||
if (!isProxyEnabled()) return null
|
if (!isProxyEnabled()) return null
|
||||||
|
|
||||||
const { agent } = getAgent()
|
const { agent } = getProxyAgent()
|
||||||
|
|
||||||
const { NodeHttpHandler } = await import('@smithy/node-http-handler')
|
const { NodeHttpHandler } = await import('@smithy/node-http-handler')
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
import { sanitizeUrl } from '@server/helpers/core-utils.js'
|
|
||||||
import { logger } from '@server/helpers/logger.js'
|
|
||||||
import { doJSONRequest } from '@server/helpers/requests.js'
|
|
||||||
import { CONFIG } from '@server/initializers/config.js'
|
|
||||||
import { PEERTUBE_VERSION } from '@server/initializers/constants.js'
|
|
||||||
import { PluginModel } from '@server/models/server/plugin.js'
|
|
||||||
import {
|
import {
|
||||||
PeerTubePluginIndex,
|
PeerTubePluginIndex,
|
||||||
PeertubePluginIndexList,
|
PeertubePluginIndexList,
|
||||||
|
@ -11,6 +5,12 @@ import {
|
||||||
PeertubePluginLatestVersionResponse,
|
PeertubePluginLatestVersionResponse,
|
||||||
ResultList
|
ResultList
|
||||||
} from '@peertube/peertube-models'
|
} from '@peertube/peertube-models'
|
||||||
|
import { sanitizeUrl } from '@server/helpers/core-utils.js'
|
||||||
|
import { logger } from '@server/helpers/logger.js'
|
||||||
|
import { doJSONRequest } from '@server/helpers/requests.js'
|
||||||
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
|
import { PEERTUBE_VERSION } from '@server/initializers/constants.js'
|
||||||
|
import { PluginModel } from '@server/models/server/plugin.js'
|
||||||
import { PluginManager } from './plugin-manager.js'
|
import { PluginManager } from './plugin-manager.js'
|
||||||
|
|
||||||
async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) {
|
async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) {
|
||||||
|
@ -28,7 +28,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList)
|
||||||
const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins'
|
const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { body } = await doJSONRequest<any>(uri, { searchParams })
|
const { body } = await doJSONRequest<any>(uri, { searchParams, preventSSRF: false })
|
||||||
|
|
||||||
logger.debug('Got result from PeerTube index.', { body })
|
logger.debug('Got result from PeerTube index.', { body })
|
||||||
|
|
||||||
|
@ -57,12 +57,7 @@ async function getLatestPluginsVersion (npmNames: string[]): Promise<PeertubePlu
|
||||||
}
|
}
|
||||||
|
|
||||||
const uri = sanitizeUrl(CONFIG.PLUGINS.INDEX.URL) + '/api/v1/plugins/latest-version'
|
const uri = sanitizeUrl(CONFIG.PLUGINS.INDEX.URL) + '/api/v1/plugins/latest-version'
|
||||||
|
const { body } = await doJSONRequest<PeertubePluginLatestVersionResponse>(uri, { json: bodyRequest, method: 'POST', preventSSRF: false })
|
||||||
const options = {
|
|
||||||
json: bodyRequest,
|
|
||||||
method: 'POST' as 'POST'
|
|
||||||
}
|
|
||||||
const { body } = await doJSONRequest<PeertubePluginLatestVersionResponse>(uri, options)
|
|
||||||
|
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
@ -79,7 +74,6 @@ async function getLatestPluginVersion (npmName: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
listAvailablePluginsFromIndex,
|
getLatestPluginsVersion, getLatestPluginVersion, listAvailablePluginsFromIndex
|
||||||
getLatestPluginVersion,
|
|
||||||
getLatestPluginsVersion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ export class AutoFollowIndexInstances extends AbstractScheduler {
|
||||||
|
|
||||||
this.lastCheck = new Date()
|
this.lastCheck = new Date()
|
||||||
|
|
||||||
const { body } = await doJSONRequest<any>(indexUrl, { searchParams })
|
const { body } = await doJSONRequest<any>(indexUrl, { searchParams, preventSSRF: false })
|
||||||
if (!body.data || Array.isArray(body.data) === false) {
|
if (!body.data || Array.isArray(body.data) === false) {
|
||||||
logger.error('Cannot auto follow instances of index %s. Please check the auto follow URL.', indexUrl, { body })
|
logger.error('Cannot auto follow instances of index %s. Please check the auto follow URL.', indexUrl, { body })
|
||||||
return
|
return
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class PeerTubeVersionCheckScheduler extends AbstractScheduler {
|
||||||
|
|
||||||
logger.info('Checking latest PeerTube version.')
|
logger.info('Checking latest PeerTube version.')
|
||||||
|
|
||||||
const { body } = await doJSONRequest<JoinPeerTubeVersions>(CONFIG.PEERTUBE.CHECK_LATEST_VERSION.URL)
|
const { body } = await doJSONRequest<JoinPeerTubeVersions>(CONFIG.PEERTUBE.CHECK_LATEST_VERSION.URL, { preventSSRF: false })
|
||||||
|
|
||||||
if (!body?.peertube?.latestVersion) {
|
if (!body?.peertube?.latestVersion) {
|
||||||
logger.warn('Cannot check latest PeerTube version: body is invalid.', { body })
|
logger.warn('Cannot check latest PeerTube version: body is invalid.', { body })
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -6151,6 +6151,14 @@ gopd@^1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
get-intrinsic "^1.1.3"
|
get-intrinsic "^1.1.3"
|
||||||
|
|
||||||
|
got-ssrf@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/got-ssrf/-/got-ssrf-3.0.0.tgz#400962f6157ed7ca86024adc274f7c49814d6f3f"
|
||||||
|
integrity sha512-Cg9lmGkaNzT8cUSDbnb1GIgetnsrwCSA8S6ZW6NiY37cEbJ/HoDs/LIradAzLcrL62XPcK6eP1442OFvrNzoIg==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.3.2"
|
||||||
|
ipaddr.js "^2.0.1"
|
||||||
|
|
||||||
got@^13.0.0:
|
got@^13.0.0:
|
||||||
version "13.0.0"
|
version "13.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/got/-/got-13.0.0.tgz#a2402862cef27a5d0d1b07c0fb25d12b58175422"
|
resolved "https://registry.yarnpkg.com/got/-/got-13.0.0.tgz#a2402862cef27a5d0d1b07c0fb25d12b58175422"
|
||||||
|
@ -6599,7 +6607,7 @@ ipaddr.js@1.9.1:
|
||||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||||
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||||
|
|
||||||
ipaddr.js@2.2.0, "ipaddr.js@>= 0.1.5", ipaddr.js@^2.0.0:
|
ipaddr.js@2.2.0, "ipaddr.js@>= 0.1.5", ipaddr.js@^2.0.0, ipaddr.js@^2.0.1:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8"
|
||||||
integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
|
integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==
|
||||||
|
|
Loading…
Reference in New Issue