mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-01 04:20:23 -05:00
fix: route all https traffic through proxy (#8827)
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
const _ = require('lodash')
|
||||
const { agent, allowDestroy, connect } = require('@packages/network')
|
||||
const { allowDestroy, connect } = require('@packages/network')
|
||||
const debug = require('debug')('cypress:https-proxy')
|
||||
const {
|
||||
getProxyForUrl,
|
||||
} = require('proxy-from-env')
|
||||
const https = require('https')
|
||||
const net = require('net')
|
||||
const parse = require('./util/parse')
|
||||
@@ -76,29 +73,16 @@ class Server {
|
||||
})
|
||||
}
|
||||
|
||||
_onFirstHeadBytes (req, browserSocket, head, options) {
|
||||
let odc
|
||||
|
||||
_onFirstHeadBytes (req, browserSocket, head) {
|
||||
debug('Got first head bytes %o', { url: req.url, head: _.chain(head).invoke('toString').slice(0, 64).join('').value() })
|
||||
|
||||
browserSocket.pause()
|
||||
|
||||
odc = options.onDirectConnection
|
||||
|
||||
if (odc) {
|
||||
// if onDirectConnection return true
|
||||
// then dont proxy, just pass this through
|
||||
if (odc.call(this, req, browserSocket, head) === true) {
|
||||
return this._makeDirectConnection(req, browserSocket, head)
|
||||
}
|
||||
|
||||
debug('Not making direct connection %o', { url: req.url })
|
||||
}
|
||||
|
||||
return this._onServerConnectData(req, browserSocket, head)
|
||||
}
|
||||
|
||||
_onUpgrade (fn, req, browserSocket, head) {
|
||||
debug('upgrade', req.url)
|
||||
if (fn) {
|
||||
return fn.call(this, req, browserSocket, head)
|
||||
}
|
||||
@@ -118,31 +102,7 @@ class Server {
|
||||
}
|
||||
}
|
||||
|
||||
_getProxyForUrl (urlStr) {
|
||||
const port = Number(_.get(url.parse(urlStr), 'port'))
|
||||
|
||||
debug('getting proxy URL %o', { port, serverPort: this._port, sniPort: this._sniPort, url: urlStr })
|
||||
|
||||
if ([this._sniPort, this._port].includes(port)) {
|
||||
// https://github.com/cypress-io/cypress/issues/4257
|
||||
// this is a tunnel to the SNI server or to the main server,
|
||||
// it should never go through a proxy
|
||||
return undefined
|
||||
}
|
||||
|
||||
return getProxyForUrl(urlStr)
|
||||
}
|
||||
|
||||
_makeDirectConnection (req, browserSocket, head) {
|
||||
const { port, hostname } = url.parse(`https://${req.url}`)
|
||||
|
||||
debug(`Making connection to ${hostname}:${port}`)
|
||||
|
||||
return this._makeConnection(browserSocket, head, port, hostname)
|
||||
}
|
||||
|
||||
_makeConnection (browserSocket, head, port, hostname) {
|
||||
let upstreamProxy
|
||||
const onSocket = (err, upstreamSocket) => {
|
||||
debug('received upstreamSocket callback for request %o', { port, hostname, err })
|
||||
|
||||
@@ -174,26 +134,6 @@ class Server {
|
||||
port = '443'
|
||||
}
|
||||
|
||||
upstreamProxy = this._getProxyForUrl(`https://${hostname}:${port}`)
|
||||
|
||||
if (upstreamProxy) {
|
||||
// todo: as soon as all requests are intercepted, this can go away since this is just for pass-through
|
||||
debug('making proxied connection %o', {
|
||||
host: `${hostname}:${port}`,
|
||||
proxy: upstreamProxy,
|
||||
})
|
||||
|
||||
return agent.httpsAgent.createUpstreamProxyConnection({
|
||||
proxy: upstreamProxy,
|
||||
href: `https://${hostname}:${port}`,
|
||||
uri: {
|
||||
port,
|
||||
hostname,
|
||||
},
|
||||
shouldRetry: true,
|
||||
}, onSocket)
|
||||
}
|
||||
|
||||
return connect.createRetryingSocket({ port, host: hostname }, onSocket)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"fs-extra": "8.1.0",
|
||||
"lodash": "4.17.19",
|
||||
"node-forge": "0.10.0",
|
||||
"proxy-from-env": "1.0.0",
|
||||
"semaphore": "1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -89,34 +89,6 @@ describe('Proxy', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// this will fail due to dynamic cert
|
||||
// generation when strict ssl is true
|
||||
it('can pass directly through', () => {
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:8444/replace',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
.then((html) => {
|
||||
expect(html).to.include('https server')
|
||||
})
|
||||
})
|
||||
|
||||
it('retries 5 times', function () {
|
||||
this.sandbox.spy(net, 'connect')
|
||||
|
||||
return request({
|
||||
strictSSL: false,
|
||||
url: 'https://localhost:12344',
|
||||
proxy: 'http://localhost:3333',
|
||||
})
|
||||
.then(() => {
|
||||
throw new Error('should not reach')
|
||||
}).catch(() => {
|
||||
expect(net.connect).to.have.callCount(5)
|
||||
})
|
||||
})
|
||||
|
||||
it('closes outgoing connections when client disconnects', function () {
|
||||
this.sandbox.spy(net, 'connect')
|
||||
|
||||
@@ -130,12 +102,8 @@ describe('Proxy', () => {
|
||||
// ensure client has disconnected
|
||||
expect(res.socket.destroyed).to.be.true
|
||||
// ensure the outgoing socket created for this connection was destroyed
|
||||
const socket = net.connect.getCalls()
|
||||
.find((call) => {
|
||||
return (call.args[0].port === '8444') && (call.args[0].host === 'localhost')
|
||||
}).returnValue
|
||||
|
||||
expect(socket.destroyed).to.be.true
|
||||
expect(net.connect).calledOnce
|
||||
expect(net.connect.getCalls()[0].returnValue.destroyed).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
@@ -227,6 +195,7 @@ describe('Proxy', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// TODO
|
||||
context('with an upstream proxy', () => {
|
||||
beforeEach(function () {
|
||||
// PROXY vars should override npm_config vars, so set them to cause failures if they are used
|
||||
@@ -300,10 +269,8 @@ describe('Proxy', () => {
|
||||
expect(res.socket.destroyed).to.be.true
|
||||
|
||||
// ensure the outgoing socket created for this connection was destroyed
|
||||
const socket = net.connect.getCalls()
|
||||
.find((call) => {
|
||||
return (call.args[0].port === 9001) && (call.args[0].host === 'localhost')
|
||||
}).returnValue
|
||||
expect(net.connect).calledOnce
|
||||
const socket = net.connect.getCalls()[0].returnValue
|
||||
|
||||
return new Promise((resolve) => {
|
||||
return socket.on('close', () => {
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('lib/server', () => {
|
||||
|
||||
this.setup({ onError })
|
||||
.then((srv) => {
|
||||
srv._makeDirectConnection({ url: 'localhost:8444' }, socket, head)
|
||||
srv._makeConnection(socket, head, '8444', 'localhost')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('lib/server', () => {
|
||||
const head = {}
|
||||
|
||||
const onError = function (err, socket2, head2, port) {
|
||||
expect(err.message).to.eq('connect ECONNREFUSED 127.0.0.1:443')
|
||||
expect(err.message).to.eq('getaddrinfo ENOTFOUND %7Balgolia_application_id%7D-dsn.algolia.net')
|
||||
|
||||
expect(socket).to.eq(socket2)
|
||||
expect(head).to.eq(head2)
|
||||
@@ -89,34 +89,7 @@ describe('lib/server', () => {
|
||||
|
||||
this.setup({ onError })
|
||||
.then((srv) => {
|
||||
srv._makeDirectConnection({ url: '%7Balgolia_application_id%7D-dsn.algolia.net:443' }, socket, head)
|
||||
})
|
||||
})
|
||||
|
||||
it('with proxied connection calls options.onError with err and port and destroys the client socket', function (done) {
|
||||
const socket = new EE()
|
||||
|
||||
socket.destroy = this.sandbox.stub()
|
||||
const head = {}
|
||||
|
||||
const onError = function (err, socket2, head2, port) {
|
||||
expect(err.message).to.eq('A connection to the upstream proxy could not be established: connect ECONNREFUSED 127.0.0.1:8444')
|
||||
|
||||
expect(socket).to.eq(socket2)
|
||||
expect(head).to.eq(head2)
|
||||
expect(port).to.eq('11111')
|
||||
|
||||
expect(socket.destroy).to.be.calledOnce
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
process.env.HTTPS_PROXY = 'http://localhost:8444'
|
||||
process.env.NO_PROXY = ''
|
||||
|
||||
this.setup({ onError })
|
||||
.then((srv) => {
|
||||
srv._makeDirectConnection({ url: 'should-not-reach.invalid:11111' }, socket, head)
|
||||
srv._makeConnection(socket, head, '443', '%7Balgolia_application_id%7D-dsn.algolia.net')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user