Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy support for plugin installer #12753

Merged
merged 15 commits into from
Jul 26, 2017
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
125 changes: 125 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,130 @@ 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 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