Plugin installer proxy support (#12753)

Proxy support for plugin installer
This commit is contained in:
Tim Roes 2017-07-26 09:35:00 +02:00 committed by GitHub
parent 460157d4e5
commit 008cf03d51
4 changed files with 184 additions and 3 deletions

View file

@ -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.

View file

@ -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",
@ -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",

View file

@ -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 () {
@ -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);
});
});
});

View file

@ -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');
@ -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;