feat(xen-api): add HTTP proxy support (#5958)
See #5436 Using an IP address as HTTPS proxy show this warning: `DeprecationWarning: Setting the TLS ServerName to an IP address is not permitted by RFC 6066` The corresponding issue is there : TooTallNate/node-https-proxy-agent#127
This commit is contained in:
parent
0c87dee31c
commit
2412f8b1e2
@ -11,6 +11,7 @@
|
|||||||
- [Jobs] Ability to copy a job ID (PR [#5951](https://github.com/vatesfr/xen-orchestra/pull/5951))
|
- [Jobs] Ability to copy a job ID (PR [#5951](https://github.com/vatesfr/xen-orchestra/pull/5951))
|
||||||
- [Host/advanced] Add button to enable/disable the host (PR [#5952](https://github.com/vatesfr/xen-orchestra/pull/5952))
|
- [Host/advanced] Add button to enable/disable the host (PR [#5952](https://github.com/vatesfr/xen-orchestra/pull/5952))
|
||||||
- [VM/export] Ability to copy the export URL (PR [#5948](https://github.com/vatesfr/xen-orchestra/pull/5948))
|
- [VM/export] Ability to copy the export URL (PR [#5948](https://github.com/vatesfr/xen-orchestra/pull/5948))
|
||||||
|
- [Servers] Ability to use an HTTP proxy between XO and a server
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
@ -42,8 +43,10 @@
|
|||||||
|
|
||||||
- xo-server-netbox patch
|
- xo-server-netbox patch
|
||||||
- vhd-lib minor
|
- vhd-lib minor
|
||||||
|
- xen-api minor
|
||||||
- @xen-orchestra/backup minor
|
- @xen-orchestra/backup minor
|
||||||
- @xen-orchestra/proxy minor
|
- @xen-orchestra/proxy minor
|
||||||
- vhd-cli minor
|
- vhd-cli minor
|
||||||
|
- xapi-explore-sr minor
|
||||||
- xo-server patch
|
- xo-server patch
|
||||||
- xo-web minor
|
- xo-web minor
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import archy from 'archy'
|
import archy from 'archy'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import execPromise from 'exec-promise'
|
import execPromise from 'exec-promise'
|
||||||
|
import firstDefined from '@xen-orchestra/defined'
|
||||||
import humanFormat from 'human-format'
|
import humanFormat from 'human-format'
|
||||||
import pw from 'pw'
|
import pw from 'pw'
|
||||||
import { createClient } from 'xen-api'
|
import { createClient } from 'xen-api'
|
||||||
@ -69,11 +70,13 @@ execPromise(async args => {
|
|||||||
url = required('Host URL'),
|
url = required('Host URL'),
|
||||||
user = required('Host user'),
|
user = required('Host user'),
|
||||||
password = await askPassword('Host password'),
|
password = await askPassword('Host password'),
|
||||||
|
httpProxy = firstDefined(process.env.http_proxy, process.env.HTTP_PROXY),
|
||||||
] = args
|
] = args
|
||||||
|
|
||||||
const xapi = createClient({
|
const xapi = createClient({
|
||||||
allowUnauthorized: true,
|
allowUnauthorized: true,
|
||||||
auth: { user, password },
|
auth: { user, password },
|
||||||
|
httpProxy,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
url,
|
url,
|
||||||
watchEvents: false,
|
watchEvents: false,
|
||||||
|
@ -52,6 +52,7 @@ Options:
|
|||||||
- `auth`: credentials used to sign in (can also be specified in the URL)
|
- `auth`: credentials used to sign in (can also be specified in the URL)
|
||||||
- `readOnly = false`: if true, no methods with side-effects can be called
|
- `readOnly = false`: if true, no methods with side-effects can be called
|
||||||
- `callTimeout`: number of milliseconds after which a call is considered failed (can also be a map of timeouts by methods)
|
- `callTimeout`: number of milliseconds after which a call is considered failed (can also be a map of timeouts by methods)
|
||||||
|
- `httpProxy`: URL of the HTTP/HTTPS proxy used to reach the host, can include credentials
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Force connection.
|
// Force connection.
|
||||||
|
@ -115,6 +115,7 @@ export class Xapi extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._allowUnauthorized = opts.allowUnauthorized
|
this._allowUnauthorized = opts.allowUnauthorized
|
||||||
|
this._httpProxy = opts.httpProxy
|
||||||
this._setUrl(url)
|
this._setUrl(url)
|
||||||
|
|
||||||
this._connected = new Promise(resolve => {
|
this._connected = new Promise(resolve => {
|
||||||
@ -851,6 +852,7 @@ export class Xapi extends EventEmitter {
|
|||||||
rejectUnauthorized: !this._allowUnauthorized,
|
rejectUnauthorized: !this._allowUnauthorized,
|
||||||
},
|
},
|
||||||
url,
|
url,
|
||||||
|
httpProxy: this._httpProxy,
|
||||||
})
|
})
|
||||||
this._url = url
|
this._url = url
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import httpRequestPlus from 'http-request-plus'
|
import httpRequestPlus from 'http-request-plus'
|
||||||
|
import ProxyAgent from 'proxy-agent'
|
||||||
import { format, parse } from 'json-rpc-protocol'
|
import { format, parse } from 'json-rpc-protocol'
|
||||||
|
|
||||||
import XapiError from '../_XapiError'
|
import XapiError from '../_XapiError'
|
||||||
@ -6,7 +7,11 @@ import XapiError from '../_XapiError'
|
|||||||
import UnsupportedTransport from './_UnsupportedTransport'
|
import UnsupportedTransport from './_UnsupportedTransport'
|
||||||
|
|
||||||
// https://github.com/xenserver/xenadmin/blob/0df39a9d83cd82713f32d24704852a0fd57b8a64/XenModel/XenAPI/Session.cs#L403-L433
|
// https://github.com/xenserver/xenadmin/blob/0df39a9d83cd82713f32d24704852a0fd57b8a64/XenModel/XenAPI/Session.cs#L403-L433
|
||||||
export default ({ secureOptions, url }) => {
|
export default ({ secureOptions, url, httpProxy }) => {
|
||||||
|
let agent
|
||||||
|
if (httpProxy !== undefined) {
|
||||||
|
agent = new ProxyAgent(httpProxy)
|
||||||
|
}
|
||||||
return (method, args) =>
|
return (method, args) =>
|
||||||
httpRequestPlus
|
httpRequestPlus
|
||||||
.post(url, {
|
.post(url, {
|
||||||
@ -17,6 +22,7 @@ export default ({ secureOptions, url }) => {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
path: '/jsonrpc',
|
path: '/jsonrpc',
|
||||||
|
agent,
|
||||||
})
|
})
|
||||||
.readAll('utf8')
|
.readAll('utf8')
|
||||||
.then(
|
.then(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createClient, createSecureClient } from 'xmlrpc'
|
import { createClient, createSecureClient } from 'xmlrpc'
|
||||||
import { promisify } from 'promise-toolbox'
|
import { promisify } from 'promise-toolbox'
|
||||||
|
import ProxyAgent from 'proxy-agent'
|
||||||
|
|
||||||
import XapiError from '../_XapiError'
|
import XapiError from '../_XapiError'
|
||||||
|
|
||||||
@ -70,10 +71,15 @@ const parseResult = result => {
|
|||||||
throw new UnsupportedTransport()
|
throw new UnsupportedTransport()
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ secureOptions, url: { hostname, port, protocol } }) => {
|
export default ({ secureOptions, url: { hostname, port, protocol }, httpProxy }) => {
|
||||||
const secure = protocol === 'https:'
|
const secure = protocol === 'https:'
|
||||||
|
let agent
|
||||||
|
if (httpProxy !== undefined) {
|
||||||
|
agent = new ProxyAgent(httpProxy)
|
||||||
|
}
|
||||||
const client = (secure ? createSecureClient : createClient)({
|
const client = (secure ? createSecureClient : createClient)({
|
||||||
...(secure ? secureOptions : undefined),
|
...(secure ? secureOptions : undefined),
|
||||||
|
agent,
|
||||||
host: hostname,
|
host: hostname,
|
||||||
path: '/json',
|
path: '/json',
|
||||||
port,
|
port,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createClient, createSecureClient } from 'xmlrpc'
|
import { createClient, createSecureClient } from 'xmlrpc'
|
||||||
import { promisify } from 'promise-toolbox'
|
import { promisify } from 'promise-toolbox'
|
||||||
|
import ProxyAgent from 'proxy-agent'
|
||||||
|
|
||||||
import XapiError from '../_XapiError'
|
import XapiError from '../_XapiError'
|
||||||
|
|
||||||
@ -30,10 +31,15 @@ const parseResult = result => {
|
|||||||
return result.Value
|
return result.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ secureOptions, url: { hostname, port, protocol } }) => {
|
export default ({ secureOptions, url: { hostname, port, protocol, httpProxy } }) => {
|
||||||
const secure = protocol === 'https:'
|
const secure = protocol === 'https:'
|
||||||
|
let agent
|
||||||
|
if (httpProxy !== undefined) {
|
||||||
|
agent = new ProxyAgent(httpProxy)
|
||||||
|
}
|
||||||
const client = (secure ? createSecureClient : createClient)({
|
const client = (secure ? createSecureClient : createClient)({
|
||||||
...(secure ? secureOptions : undefined),
|
...(secure ? secureOptions : undefined),
|
||||||
|
agent,
|
||||||
host: hostname,
|
host: hostname,
|
||||||
port,
|
port,
|
||||||
})
|
})
|
||||||
|
@ -36,6 +36,10 @@ add.params = {
|
|||||||
optional: true,
|
optional: true,
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
|
httpProxy: {
|
||||||
|
optional: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@ -104,6 +108,10 @@ set.params = {
|
|||||||
optional: true,
|
optional: true,
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
},
|
},
|
||||||
|
httpProxy: {
|
||||||
|
optional: true,
|
||||||
|
type: ['string', 'null'],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
@ -94,7 +94,7 @@ export default class {
|
|||||||
// TODO: disconnect servers on stop.
|
// TODO: disconnect servers on stop.
|
||||||
}
|
}
|
||||||
|
|
||||||
async registerXenServer({ allowUnauthorized = false, host, label, password, readOnly = false, username }) {
|
async registerXenServer({ allowUnauthorized = false, host, label, password, readOnly = false, username, httpProxy }) {
|
||||||
// FIXME: We are storing passwords which is bad!
|
// FIXME: We are storing passwords which is bad!
|
||||||
// Could we use tokens instead?
|
// Could we use tokens instead?
|
||||||
// TODO: use plain objects
|
// TODO: use plain objects
|
||||||
@ -102,6 +102,7 @@ export default class {
|
|||||||
allowUnauthorized,
|
allowUnauthorized,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
host,
|
host,
|
||||||
|
httpProxy,
|
||||||
label: label || undefined,
|
label: label || undefined,
|
||||||
password,
|
password,
|
||||||
readOnly,
|
readOnly,
|
||||||
@ -119,11 +120,18 @@ export default class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateXenServer(id, { allowUnauthorized, enabled, error, host, label, password, readOnly, username }) {
|
async updateXenServer(
|
||||||
|
id,
|
||||||
|
{ allowUnauthorized, enabled, error, host, label, password, readOnly, username, httpProxy }
|
||||||
|
) {
|
||||||
const server = await this._getXenServer(id)
|
const server = await this._getXenServer(id)
|
||||||
const xapi = this._xapis[id]
|
const xapi = this._xapis[id]
|
||||||
const requireDisconnected =
|
const requireDisconnected =
|
||||||
allowUnauthorized !== undefined || host !== undefined || password !== undefined || username !== undefined
|
allowUnauthorized !== undefined ||
|
||||||
|
host !== undefined ||
|
||||||
|
password !== undefined ||
|
||||||
|
username !== undefined ||
|
||||||
|
httpProxy !== undefined
|
||||||
|
|
||||||
if (requireDisconnected && xapi !== undefined && xapi.status !== 'disconnected') {
|
if (requireDisconnected && xapi !== undefined && xapi.status !== 'disconnected') {
|
||||||
throw new Error('this entry require disconnecting the server to update it')
|
throw new Error('this entry require disconnecting the server to update it')
|
||||||
@ -153,6 +161,10 @@ export default class {
|
|||||||
server.set('allowUnauthorized', allowUnauthorized)
|
server.set('allowUnauthorized', allowUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpProxy !== undefined) {
|
||||||
|
// if value is null, pass undefined to the model , so it will delete this optionnal property from the Server object
|
||||||
|
server.set('httpProxy', httpProxy === null ? undefined : httpProxy)
|
||||||
|
}
|
||||||
await this._servers.update(server)
|
await this._servers.update(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +300,7 @@ export default class {
|
|||||||
readOnly: server.readOnly,
|
readOnly: server.readOnly,
|
||||||
|
|
||||||
...config.get('xapiOptions'),
|
...config.get('xapiOptions'),
|
||||||
|
httpProxy: server.httpProxy,
|
||||||
guessVhdSizeOnImport: config.get('guessVhdSizeOnImport'),
|
guessVhdSizeOnImport: config.get('guessVhdSizeOnImport'),
|
||||||
|
|
||||||
auth: {
|
auth: {
|
||||||
|
@ -1826,6 +1826,8 @@ const messages = {
|
|||||||
serverEnabled: 'Enabled',
|
serverEnabled: 'Enabled',
|
||||||
serverDisabled: 'Disabled',
|
serverDisabled: 'Disabled',
|
||||||
serverDisable: 'Disable server',
|
serverDisable: 'Disable server',
|
||||||
|
serverHttpProxy: ' HTTP proxy URL',
|
||||||
|
serverHttpProxyPlaceHolder: ' HTTP proxy URL',
|
||||||
|
|
||||||
// ----- Copy VM -----
|
// ----- Copy VM -----
|
||||||
copyVm: 'Copy VM',
|
copyVm: 'Copy VM',
|
||||||
|
@ -560,10 +560,11 @@ export const exportConfig = () =>
|
|||||||
|
|
||||||
// Server ------------------------------------------------------------
|
// Server ------------------------------------------------------------
|
||||||
|
|
||||||
export const addServer = (host, username, password, label, allowUnauthorized) =>
|
export const addServer = (host, username, password, label, allowUnauthorized, httpProxy) =>
|
||||||
_call('server.add', {
|
_call('server.add', {
|
||||||
allowUnauthorized,
|
allowUnauthorized,
|
||||||
host,
|
host,
|
||||||
|
httpProxy,
|
||||||
label,
|
label,
|
||||||
password,
|
password,
|
||||||
username,
|
username,
|
||||||
|
@ -132,6 +132,18 @@ const COLUMNS = [
|
|||||||
itemRenderer: ({ poolId }) => poolId !== undefined && <Pool id={poolId} link />,
|
itemRenderer: ({ poolId }) => poolId !== undefined && <Pool id={poolId} link />,
|
||||||
name: _('pool'),
|
name: _('pool'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
itemRenderer: (server, formatMessage) => (
|
||||||
|
<Text
|
||||||
|
value={server.httpProxy || ''}
|
||||||
|
// force a null value for falsish value to ensure the value is removed from object if set to ''
|
||||||
|
onChange={httpProxy => editServer(server, { httpProxy: httpProxy || null })}
|
||||||
|
placeholder={formatMessage(messages.serverHttpProxyPlaceHolder)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
name: _('serverHttpProxy'),
|
||||||
|
sortCriteria: _ => _.httpProxy,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
const INDIVIDUAL_ACTIONS = [
|
const INDIVIDUAL_ACTIONS = [
|
||||||
{
|
{
|
||||||
@ -152,13 +164,13 @@ export default class Servers extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_addServer = async () => {
|
_addServer = async () => {
|
||||||
const { label, host, password, username, allowUnauthorized } = this.state
|
const { label, host, password, username, allowUnauthorized, httpProxy } = this.state
|
||||||
|
await addServer(host, username, password, label, allowUnauthorized, httpProxy)
|
||||||
await addServer(host, username, password, label, allowUnauthorized)
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
allowUnauthorized: false,
|
allowUnauthorized: false,
|
||||||
host: '',
|
host: '',
|
||||||
|
httpProxy: '',
|
||||||
label: '',
|
label: '',
|
||||||
password: '',
|
password: '',
|
||||||
username: '',
|
username: '',
|
||||||
@ -227,6 +239,15 @@ export default class Servers extends Component {
|
|||||||
<Toggle onChange={this.linkState('allowUnauthorized')} value={state.allowUnauthorized} />
|
<Toggle onChange={this.linkState('allowUnauthorized')} value={state.allowUnauthorized} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>{' '}
|
</div>{' '}
|
||||||
|
<div className='form-group'>
|
||||||
|
<input
|
||||||
|
className='form-control'
|
||||||
|
onChange={this.linkState('httpProxy')}
|
||||||
|
placeholder={formatMessage(messages.serverHttpProxy)}
|
||||||
|
type='text'
|
||||||
|
value={state.httpProxy || ''}
|
||||||
|
/>
|
||||||
|
</div>{' '}
|
||||||
<ActionButton btnStyle='primary' form='form-add-server' handler={this._addServer} icon='save'>
|
<ActionButton btnStyle='primary' form='form-add-server' handler={this._addServer} icon='save'>
|
||||||
{_('serverConnect')}
|
{_('serverConnect')}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
Loading…
Reference in New Issue
Block a user