Fix TLS crash with HTTP proxy
This commit is contained in:
parent
4592f062c4
commit
3fafcb15a1
5 changed files with 161 additions and 14 deletions
|
@ -142,7 +142,6 @@
|
|||
"got": "^13.0.0",
|
||||
"got-ssrf": "^3.0.0",
|
||||
"helmet": "^7.0.0",
|
||||
"hpagent": "^1.0.0",
|
||||
"http-problem-details": "^0.1.5",
|
||||
"ioredis": "^5.2.3",
|
||||
"ip-anonymize": "^0.1.0",
|
||||
|
|
158
server/core/helpers/hpagent.ts
Normal file
158
server/core/helpers/hpagent.ts
Normal file
|
@ -0,0 +1,158 @@
|
|||
// Copy of un-maintained hpagent package with https://github.com/delvedor/hpagent/pull/114 fix
|
||||
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
import { Socket, TcpNetConnectOpts } from 'net'
|
||||
import { URL } from 'url'
|
||||
|
||||
type Options = {
|
||||
keepAlive: boolean
|
||||
keepAliveMsecs: number
|
||||
maxSockets: number
|
||||
maxFreeSockets: number
|
||||
scheduling: 'lifo'
|
||||
proxy: string
|
||||
}
|
||||
|
||||
export class HttpProxyAgent extends http.Agent {
|
||||
private readonly proxy: URL
|
||||
private readonly keepAlive: boolean
|
||||
|
||||
constructor (options: Options) {
|
||||
const { proxy, ...opts } = options
|
||||
|
||||
super(opts)
|
||||
|
||||
this.keepAlive = options.keepAlive
|
||||
|
||||
this.proxy = typeof proxy === 'string'
|
||||
? new URL(proxy)
|
||||
: proxy
|
||||
}
|
||||
|
||||
createConnection (options: TcpNetConnectOpts, callback?: (err: Error, socket: Socket) => void) {
|
||||
const requestOptions = {
|
||||
method: 'CONNECT',
|
||||
host: this.proxy.hostname,
|
||||
port: this.proxy.port,
|
||||
path: `${options.host}:${options.port}`,
|
||||
setHost: false,
|
||||
headers: { connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },
|
||||
agent: false,
|
||||
timeout: options.timeout || 0,
|
||||
servername: undefined as string
|
||||
}
|
||||
|
||||
if (this.proxy.username || this.proxy.password) {
|
||||
const base64 = Buffer.from(
|
||||
`${decodeURIComponent(this.proxy.username || '')}:${decodeURIComponent(this.proxy.password || '')}`
|
||||
).toString('base64')
|
||||
|
||||
requestOptions.headers['proxy-authorization'] = `Basic ${base64}`
|
||||
}
|
||||
|
||||
if (this.proxy.protocol === 'https:') {
|
||||
requestOptions.servername = this.proxy.hostname
|
||||
}
|
||||
|
||||
const request = (this.proxy.protocol === 'http:' ? http : https).request(requestOptions)
|
||||
request.once('connect', (response, socket, head) => {
|
||||
request.removeAllListeners()
|
||||
socket.removeAllListeners()
|
||||
if (response.statusCode === 200) {
|
||||
callback(null, socket)
|
||||
} else {
|
||||
socket.destroy()
|
||||
callback(new Error(`Bad response: ${response.statusCode}`), null)
|
||||
}
|
||||
})
|
||||
|
||||
request.once('timeout', () => {
|
||||
request.destroy(new Error('Proxy timeout'))
|
||||
})
|
||||
|
||||
request.once('error', err => {
|
||||
request.removeAllListeners()
|
||||
callback(err, null)
|
||||
})
|
||||
|
||||
request.end()
|
||||
}
|
||||
}
|
||||
|
||||
export class HttpsProxyAgent extends https.Agent {
|
||||
private readonly proxy: URL
|
||||
private readonly keepAlive: boolean
|
||||
|
||||
constructor (options: Options) {
|
||||
const { proxy, ...opts } = options
|
||||
|
||||
super(opts)
|
||||
|
||||
this.keepAlive = options.keepAlive
|
||||
|
||||
this.proxy = typeof proxy === 'string'
|
||||
? new URL(proxy)
|
||||
: proxy
|
||||
}
|
||||
|
||||
createConnection (options: TcpNetConnectOpts, callback?: (err: Error, socket: Socket) => void) {
|
||||
const requestOptions = {
|
||||
method: 'CONNECT',
|
||||
host: this.proxy.hostname,
|
||||
port: this.proxy.port,
|
||||
path: `${options.host}:${options.port}`,
|
||||
setHost: false,
|
||||
headers: { connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },
|
||||
agent: false,
|
||||
timeout: options.timeout || 0,
|
||||
servername: undefined as string
|
||||
}
|
||||
|
||||
if (this.proxy.username || this.proxy.password) {
|
||||
const base64 = Buffer.from(
|
||||
`${decodeURIComponent(this.proxy.username || '')}:${decodeURIComponent(this.proxy.password || '')}
|
||||
`).toString('base64')
|
||||
|
||||
requestOptions.headers['proxy-authorization'] = `Basic ${base64}`
|
||||
}
|
||||
|
||||
// Necessary for the TLS check with the proxy to succeed.
|
||||
if (this.proxy.protocol === 'https:') {
|
||||
requestOptions.servername = this.proxy.hostname
|
||||
}
|
||||
|
||||
const request = (this.proxy.protocol === 'http:' ? http : https).request(requestOptions)
|
||||
request.once('connect', (response, socket, head) => {
|
||||
request.removeAllListeners()
|
||||
socket.removeAllListeners()
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
try {
|
||||
// FIXME: typings doesn't include createConnection type in HTTP agent
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
const secureSocket = super.createConnection({ ...options, socket })
|
||||
callback(null, secureSocket)
|
||||
} catch (err) {
|
||||
socket.destroy()
|
||||
callback(err, null)
|
||||
}
|
||||
} else {
|
||||
socket.destroy()
|
||||
callback(new Error(`Bad response: ${response.statusCode}`), null)
|
||||
}
|
||||
})
|
||||
|
||||
request.once('timeout', () => {
|
||||
request.destroy(new Error('Proxy timeout'))
|
||||
})
|
||||
|
||||
request.once('error', err => {
|
||||
request.removeAllListeners()
|
||||
callback(err, null)
|
||||
})
|
||||
|
||||
request.end()
|
||||
}
|
||||
}
|
|
@ -1,14 +1,9 @@
|
|||
function getProxy () {
|
||||
export function getProxy () {
|
||||
return process.env.HTTPS_PROXY ||
|
||||
process.env.HTTP_PROXY ||
|
||||
undefined
|
||||
}
|
||||
|
||||
function isProxyEnabled () {
|
||||
export function isProxyEnabled () {
|
||||
return !!getProxy()
|
||||
}
|
||||
|
||||
export {
|
||||
getProxy,
|
||||
isProxyEnabled
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { createWriteStream } from 'fs'
|
|||
import { remove } from 'fs-extra/esm'
|
||||
import got, { CancelableRequest, OptionsInit, OptionsOfTextResponseBody, OptionsOfUnknownResponseBody, RequestError, Response } from 'got'
|
||||
import { gotSsrf } from 'got-ssrf'
|
||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'
|
||||
import { HttpProxyAgent, HttpsProxyAgent } from '../helpers/hpagent.js'
|
||||
import { ACTIVITY_PUB, BINARY_CONTENT_TYPES, PEERTUBE_VERSION, REQUEST_TIMEOUTS, WEBSERVER } from '../initializers/constants.js'
|
||||
import { pipelinePromise } from './core-utils.js'
|
||||
import { logger, loggerTagsFactory } from './logger.js'
|
||||
|
|
|
@ -6386,11 +6386,6 @@ homedir-polyfill@^1.0.1:
|
|||
dependencies:
|
||||
parse-passwd "^1.0.0"
|
||||
|
||||
hpagent@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-1.2.0.tgz#0ae417895430eb3770c03443456b8d90ca464903"
|
||||
integrity sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==
|
||||
|
||||
html-to-text@9.0.5, html-to-text@^9.0.5:
|
||||
version "9.0.5"
|
||||
resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-9.0.5.tgz#6149a0f618ae7a0db8085dca9bbf96d32bb8368d"
|
||||
|
|
Loading…
Add table
Reference in a new issue