Skip to content

Commit

Permalink
Plugin installer proxy support (#12753) (#13115)
Browse files Browse the repository at this point in the history
Proxy support for plugin installer
  • Loading branch information
timroes authored Jul 26, 2017
1 parent 43b66ce commit 26dff17
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 3 deletions.
14 changes: 14 additions & 0 deletions docs/plugins.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ If plugins were installed as a different user and the server is not starting, th
[source,shell]
$ chown -R kibana:kibana /path/to/kibana/optimize

[float]
=== Proxy support for plugin installation

Kibana supports plugin installation via a proxy. It uses the `http_proxy` and `https_proxy`
environment variables to detect a proxy for HTTP and HTTPS URLs.

It also respects the `no_proxy` environment variable to exclude specific URLs from proxying.

You can specify the environment variable directly when installing plugins:

[source,shell]
$ http_proxy="http://proxy.local:4242" bin/kibana-plugin install <package name or URL>


== Updating & Removing Plugins

To update a plugin, remove the current version and reinstall the plugin.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"h2o2": "5.1.1",
"handlebars": "4.0.5",
"hapi": "14.2.0",
"http-proxy-agent": "1.0.0",
"imports-loader": "0.6.4",
"inert": "4.0.2",
"jade": "1.11.0",
Expand All @@ -157,6 +158,7 @@
"node-fetch": "1.3.2",
"pegjs": "0.9.0",
"postcss-loader": "1.2.1",
"proxy-from-env": "1.0.0",
"prop-types": "15.5.8",
"pui-react-overlay-trigger": "7.5.4",
"pui-react-tooltip": "7.5.4",
Expand Down
145 changes: 145 additions & 0 deletions src/cli_plugin/install/__tests__/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Logger from '../../lib/logger';
import { UnsupportedProtocolError } from '../../lib/errors';
import { download, _downloadSingle, _getFilePath, _checkFilePathDeprecation } from '../download';
import { join } from 'path';
import http from 'http';

describe('kibana cli', function () {

Expand Down Expand Up @@ -251,6 +252,150 @@ describe('kibana cli', function () {
});
});

after(function () {
nock.cleanAll();
});

});

describe('proxy support', function () {

const proxyPort = 2626;
const proxyUrl = `http://localhost:${proxyPort}`;

let proxyHit = false;

const proxy = http.createServer(function (req, res) {
proxyHit = true;
// Our test proxy simply returns an empty 200 response, since we only
// care about the download promise being resolved.
res.writeHead(200);
res.end();
});

function expectProxyHit() {
expect(proxyHit).to.be(true);
}

function expectNoProxyHit() {
expect(proxyHit).to.be(false);
}

function nockPluginForUrl(url) {
nock(url)
.get('/plugin.zip')
.replyWithFile(200, join(__dirname, 'replies/test_plugin.zip'));
}

before(function (done) {
proxy.listen(proxyPort, done);
});

beforeEach(function () {
proxyHit = false;
});

afterEach(function () {
delete process.env.http_proxy;
delete process.env.https_proxy;
delete process.env.no_proxy;
});

it('should use http_proxy env variable', function () {
process.env.http_proxy = proxyUrl;
settings.urls = ['http://example.com/plugin.zip'];

return download(settings, logger)
.then(expectProxyHit);
});

it('should use https_proxy for secure URLs', function () {
process.env.https_proxy = proxyUrl;
settings.urls = ['https://example.com/plugin.zip'];

return download(settings, logger)
.then(expectProxyHit);
});

it('should not use http_proxy for HTTPS urls', function () {
process.env.http_proxy = proxyUrl;
settings.urls = ['https://example.com/plugin.zip'];

nockPluginForUrl('https://example.com');

return download(settings, logger)
.then(expectNoProxyHit);
});

it('should not use https_proxy for HTTP urls', function () {
process.env.https_proxy = proxyUrl;
settings.urls = ['http://example.com/plugin.zip'];

nockPluginForUrl('http://example.com');

return download(settings, logger)
.then(expectNoProxyHit);
});

it('should support domains in no_proxy', function () {
process.env.http_proxy = proxyUrl;
process.env.no_proxy = 'foo.bar, example.com';
settings.urls = ['http://example.com/plugin.zip'];

nockPluginForUrl('http://example.com');

return download(settings, logger)
.then(expectNoProxyHit);
});

it('should support subdomains in no_proxy', function () {
process.env.http_proxy = proxyUrl;
process.env.no_proxy = 'foo.bar,plugins.example.com';
settings.urls = ['http://plugins.example.com/plugin.zip'];

nockPluginForUrl('http://plugins.example.com');

return download(settings, logger)
.then(expectNoProxyHit);
});

it('should accept wildcard subdomains in no_proxy', function () {
process.env.http_proxy = proxyUrl;
process.env.no_proxy = 'foo.bar, .example.com';
settings.urls = ['http://plugins.example.com/plugin.zip'];

nockPluginForUrl('http://plugins.example.com');

return download(settings, logger)
.then(expectNoProxyHit);
});

it('should support asterisk wildcard no_proxy syntax', function () {
process.env.http_proxy = proxyUrl;
process.env.no_proxy = '*.example.com';
settings.urls = ['http://plugins.example.com/plugin.zip'];

nockPluginForUrl('http://plugins.example.com');

return download(settings, logger)
.then(expectNoProxyHit);
});

it('should support implicit ports in no_proxy', function () {
process.env.https_proxy = proxyUrl;
process.env.no_proxy = 'example.com:443';
settings.urls = ['https://example.com/plugin.zip'];

nockPluginForUrl('https://example.com');

return download(settings, logger)
.then(expectNoProxyHit);
});

after(function (done) {
proxy.close(done);
});

});

});
Expand Down
26 changes: 23 additions & 3 deletions src/cli_plugin/install/downloaders/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,31 @@ import Wreck from 'wreck';
import Progress from '../progress';
import { fromNode as fn } from 'bluebird';
import { createWriteStream } from 'fs';
import HttpProxyAgent from 'http-proxy-agent';
import { getProxyForUrl } from 'proxy-from-env';

function sendRequest({ sourceUrl, timeout }) {
function getProxyAgent(sourceUrl, logger) {
const proxy = getProxyForUrl(sourceUrl);

if (!proxy) {
return null;
}

logger.log(`Picked up proxy ${proxy} from environment variable.`);
return new HttpProxyAgent(proxy);
}

function sendRequest({ sourceUrl, timeout }, logger) {
const maxRedirects = 11; //Because this one goes to 11.
return fn(cb => {
const req = Wreck.request('GET', sourceUrl, { timeout, redirects: maxRedirects }, (err, resp) => {
const reqOptions = { timeout, redirects: maxRedirects };
const proxyAgent = getProxyAgent(sourceUrl, logger);

if (proxyAgent) {
reqOptions.agent = proxyAgent;
}

const req = Wreck.request('GET', sourceUrl, reqOptions, (err, resp) => {
if (err) {
if (err.code === 'ECONNREFUSED') {
err = new Error('ENOTFOUND');
Expand Down Expand Up @@ -50,7 +70,7 @@ Responsible for managing http transfers
*/
export default async function downloadUrl(logger, sourceUrl, targetPath, timeout) {
try {
const { req, resp } = await sendRequest({ sourceUrl, timeout });
const { req, resp } = await sendRequest({ sourceUrl, timeout }, logger);

try {
const totalSize = parseFloat(resp.headers['content-length']) || 0;
Expand Down

0 comments on commit 26dff17

Please sign in to comment.