diff --git a/docs/config/01-configuration-file.md b/docs/config/01-configuration-file.md index ca24a1cdf..73bb149d7 100644 --- a/docs/config/01-configuration-file.md +++ b/docs/config/01-configuration-file.md @@ -176,6 +176,13 @@ See [config/files] for more information. }, ``` +## proxyValidateSSL +**Type:** Boolean + +**Default:** `true` + +**Description:** Should https proxies error on invalid SSL cert. + ## reportSlowerThan **Type:** Number diff --git a/lib/config.js b/lib/config.js index bd96c56ab..6b89d7e15 100644 --- a/lib/config.js +++ b/lib/config.js @@ -244,6 +244,7 @@ var parseConfig = function(configFilePath, cliOptions) { browsers: [], captureTimeout: 60000, proxies: {}, + proxyValidateSSL: true, preprocessors: {'**/*.coffee': 'coffee'}, urlRoot: '/', reportSlowerThan: 0, diff --git a/lib/proxy.js b/lib/proxy.js index ede075645..16b2f3f5d 100644 --- a/lib/proxy.js +++ b/lib/proxy.js @@ -36,9 +36,14 @@ var parseProxyConfig = function(proxies) { proxyConfig[proxyPath] = { host: proxyDetails.hostname, - port: proxyDetails.port || '80', - baseProxyUrl: pathname + port: proxyDetails.port, + baseProxyUrl: pathname, + https: proxyDetails.protocol === 'https:' }; + + if (!proxyConfig[proxyPath].port) { + proxyConfig[proxyPath].port = proxyConfig[proxyPath].https ? '443' : '80'; + } }); return proxyConfig; @@ -51,7 +56,7 @@ var parseProxyConfig = function(proxies) { * @param proxies a map of routes to proxy url * @return {Function} handler function */ -var createProxyHandler = function(proxy, proxyConfig) { +var createProxyHandler = function(proxy, proxyConfig, proxyValidateSSL) { var proxies = parseProxyConfig(proxyConfig); var proxiesList = Object.keys(proxies).sort().reverse(); @@ -75,7 +80,11 @@ var createProxyHandler = function(proxy, proxyConfig) { log.debug('proxying request - %s to %s:%s', request.url, proxiedUrl.host, proxiedUrl.port); request.url = request.url.replace(proxiesList[i], proxiedUrl.baseProxyUrl); - proxy.proxyRequest(request, response, {host: proxiedUrl.host, port: proxiedUrl.port}); + proxy.proxyRequest(request, response, { + host: proxiedUrl.host, + port: proxiedUrl.port, + target: { https: proxiedUrl.https, rejectUnauthorized: proxyValidateSSL } + }); return; } } diff --git a/lib/web-server.js b/lib/web-server.js index 3a3a41887..aacfe7373 100644 --- a/lib/web-server.js +++ b/lib/web-server.js @@ -190,10 +190,10 @@ var createSourceFileHandler = function(promiseContainer, adapterFolder, baseFold var createHandler = function(promiseContainer, staticFolder, adapterFolder, baseFolder, proxyFn, - proxies, urlRoot, customFileHandlers, customScriptTypes) { + proxies, urlRoot, customFileHandlers, customScriptTypes, proxyValidateSSL) { var karmaSrcHandler = createKarmaSourceHandler(promiseContainer, staticFolder, adapterFolder, baseFolder, urlRoot, customFileHandlers, customScriptTypes); - var proxiedPathsHandler = proxy.createProxyHandler(proxyFn, proxies); + var proxiedPathsHandler = proxy.createProxyHandler(proxyFn, proxies, proxyValidateSSL); var sourceFileHandler = createSourceFileHandler(promiseContainer, adapterFolder, baseFolder); return function(request, response) { @@ -210,7 +210,7 @@ var createHandler = function(promiseContainer, staticFolder, adapterFolder, base exports.createWebServer = function (baseFolder, proxies, urlRoot, - customFileHandlers, customScriptTypes) { + customFileHandlers, customScriptTypes, proxyValidateSSL) { var staticFolder = path.normalize(__dirname + '/../static'); var adapterFolder = path.normalize(__dirname + '/../adapter'); @@ -221,7 +221,7 @@ exports.createWebServer = function (baseFolder, proxies, urlRoot, var server = http.createServer(createHandler(promiseContainer, helper.normalizeWinPath(staticFolder), helper.normalizeWinPath(adapterFolder), baseFolder, new httpProxy.RoutingProxy({changeOrigin: true}), proxies, urlRoot, customFileHandlers, - customScriptTypes)); + customScriptTypes, proxyValidateSSL)); server.updateFilesPromise = function(promise) { promiseContainer.promise = promise; @@ -231,4 +231,4 @@ exports.createWebServer = function (baseFolder, proxies, urlRoot, }; exports.createWebServer.$inject = ['config.basePath', 'config.proxies', 'config.urlRoot', - 'customFileHandlers', 'customScriptTypes']; + 'customFileHandlers', 'customScriptTypes', 'config.proxyValidateSSL']; diff --git a/test/unit/proxy.spec.coffee b/test/unit/proxy.spec.coffee index 6bde8f190..c7c48d5f4 100644 --- a/test/unit/proxy.spec.coffee +++ b/test/unit/proxy.spec.coffee @@ -26,37 +26,73 @@ describe 'proxy', -> it 'should proxy requests', (done) -> - proxy = m.createProxyHandler mockProxy, {'/proxy': 'http://localhost:9000'} + proxy = m.createProxyHandler mockProxy, {'/proxy': 'http://localhost:9000'}, true proxy new httpMock.ServerRequest('/proxy/test.html'), response, nextSpy expect(nextSpy).not.to.have.been.called expect(requestedUrl).to.equal '/test.html' - expect(actualOptions).to.deep.equal {host: 'localhost', port: '9000'} + expect(actualOptions).to.deep.equal { + host: 'localhost', + port: '9000', + target:{https:false, rejectUnauthorized:true} + } done() + it 'should enable https', (done) -> + proxy = m.createProxyHandler mockProxy, {'/proxy': 'https://localhost:9000'}, true + proxy new httpMock.ServerRequest('/proxy/test.html'), response, nextSpy + + expect(nextSpy).not.to.have.been.called + expect(requestedUrl).to.equal '/test.html' + expect(actualOptions).to.deep.equal { + host: 'localhost', + port: '9000', + target:{https:true, rejectUnauthorized:true} + } + done() + + it 'disable ssl validation', (done) -> + proxy = m.createProxyHandler mockProxy, {'/proxy': 'https://localhost:9000'}, false + proxy new httpMock.ServerRequest('/proxy/test.html'), response, nextSpy + + expect(nextSpy).not.to.have.been.called + expect(requestedUrl).to.equal '/test.html' + expect(actualOptions).to.deep.equal { + host: 'localhost', + port: '9000', + target:{https:true, rejectUnauthorized:false} + } + done() it 'should support multiple proxies', -> proxy = m.createProxyHandler mockProxy, { '/proxy': 'http://localhost:9000' '/static': 'http://gstatic.com' - } + }, true proxy new httpMock.ServerRequest('/static/test.html'), response, nextSpy expect(nextSpy).not.to.have.been.called expect(requestedUrl).to.equal '/test.html' - expect(actualOptions).to.deep.equal {host: 'gstatic.com', port: '80'} - + expect(actualOptions).to.deep.equal { + host: 'gstatic.com', + port: '80', + target:{https:false, rejectUnauthorized:true} + } it 'should handle nested proxies', -> proxy = m.createProxyHandler mockProxy, { '/sub': 'http://localhost:9000' '/sub/some': 'http://gstatic.com/something' - } + }, true proxy new httpMock.ServerRequest('/sub/some/Test.html'), response, nextSpy expect(nextSpy).not.to.have.been.called expect(requestedUrl).to.equal '/something/Test.html' - expect(actualOptions).to.deep.equal {host: 'gstatic.com', port: '80'} + expect(actualOptions).to.deep.equal { + host: 'gstatic.com', + port: '80', + target:{https:false, rejectUnauthorized:true} + } it 'should call next handler if the path is not proxied', -> @@ -77,7 +113,21 @@ describe 'proxy', -> proxy = {'/base/': 'http://localhost:8000/'} parsedProxyConfig = m.parseProxyConfig proxy expect(parsedProxyConfig).to.deep.equal { - '/base/': {host: 'localhost', port: '8000', baseProxyUrl: '/'} + '/base/': {host: 'localhost', port: '8000', baseProxyUrl: '/', https:false} + } + + it 'should set defualt http port', -> + proxy = {'/base/': 'http://localhost/'} + parsedProxyConfig = m.parseProxyConfig proxy + expect(parsedProxyConfig).to.deep.equal { + '/base/': {host: 'localhost', port: '80', baseProxyUrl: '/', https:false} + } + + it 'should set defualt https port', -> + proxy = {'/base/': 'https://localhost/'} + parsedProxyConfig = m.parseProxyConfig proxy + expect(parsedProxyConfig).to.deep.equal { + '/base/': {host: 'localhost', port: '443', baseProxyUrl: '/', https:true} } @@ -85,9 +135,15 @@ describe 'proxy', -> proxy = {'/base': 'http://localhost:8000/proxy'} parsedProxyConfig = m.parseProxyConfig proxy expect(parsedProxyConfig).to.deep.equal { - '/base': {host: 'localhost', port: '8000', baseProxyUrl: '/proxy'} + '/base': {host: 'localhost', port: '8000', baseProxyUrl: '/proxy', https:false} } + it 'should determine protocol', -> + proxy = {'/base':'https://localhost:8000'} + parsedProxyConfig = m.parseProxyConfig proxy + expect(parsedProxyConfig).to.deep.equal { + '/base': {host: 'localhost', port: '8000', baseProxyUrl: '', https: true} + } it 'should handle empty proxy config', -> expect(m.parseProxyConfig {}).to.deep.equal({}) diff --git a/test/unit/web-server.spec.coffee b/test/unit/web-server.spec.coffee index 32444781e..2c941b3b1 100644 --- a/test/unit/web-server.spec.coffee +++ b/test/unit/web-server.spec.coffee @@ -72,7 +72,7 @@ describe 'web-server', -> servedFiles defaultFiles handler = m.createHandler promiseContainer, staticFolderPath, adapterFolderPath, baseFolder, mockProxy, {'/_karma_/': 'http://localhost:9000', '/base/': 'http://localhost:1000'}, - '/_karma_/', [], [] + '/_karma_/', [], [], true actualOptions = {} response = new responseMock() nextSpy = sinon.spy() @@ -109,7 +109,11 @@ describe 'web-server', -> it 'should delegate to proxy after checking for karma files', (done) -> response.once 'end', -> - expect(actualOptions).to.deep.equal {host: 'localhost', port: '9000'} + expect(actualOptions).to.deep.equal { + host: 'localhost', + port: '9000', + target:{https:false, rejectUnauthorized:true} + } done() handler new httpMock.ServerRequest('/_karma_/not_client.html'), response @@ -117,7 +121,11 @@ describe 'web-server', -> it 'should delegate to proxy after checking for source files', (done) -> response.once 'end', -> - expect(actualOptions).to.deep.equal {host: 'localhost', port: '1000'} + expect(actualOptions).to.deep.equal { + host: 'localhost', + port: '1000', + target:{https:false, rejectUnauthorized:true} + } done() handler new httpMock.ServerRequest('/base/not_client.html'), response