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))
|
||||
- [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))
|
||||
- [Servers] Ability to use an HTTP proxy between XO and a server
|
||||
|
||||
### Bug fixes
|
||||
|
||||
@ -42,8 +43,10 @@
|
||||
|
||||
- xo-server-netbox patch
|
||||
- vhd-lib minor
|
||||
- xen-api minor
|
||||
- @xen-orchestra/backup minor
|
||||
- @xen-orchestra/proxy minor
|
||||
- vhd-cli minor
|
||||
- xapi-explore-sr minor
|
||||
- xo-server patch
|
||||
- xo-web minor
|
||||
|
@ -3,6 +3,7 @@
|
||||
import archy from 'archy'
|
||||
import chalk from 'chalk'
|
||||
import execPromise from 'exec-promise'
|
||||
import firstDefined from '@xen-orchestra/defined'
|
||||
import humanFormat from 'human-format'
|
||||
import pw from 'pw'
|
||||
import { createClient } from 'xen-api'
|
||||
@ -69,11 +70,13 @@ execPromise(async args => {
|
||||
url = required('Host URL'),
|
||||
user = required('Host user'),
|
||||
password = await askPassword('Host password'),
|
||||
httpProxy = firstDefined(process.env.http_proxy, process.env.HTTP_PROXY),
|
||||
] = args
|
||||
|
||||
const xapi = createClient({
|
||||
allowUnauthorized: true,
|
||||
auth: { user, password },
|
||||
httpProxy,
|
||||
readOnly: true,
|
||||
url,
|
||||
watchEvents: false,
|
||||
|
@ -52,6 +52,7 @@ Options:
|
||||
- `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
|
||||
- `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
|
||||
// Force connection.
|
||||
|
@ -115,6 +115,7 @@ export class Xapi extends EventEmitter {
|
||||
}
|
||||
|
||||
this._allowUnauthorized = opts.allowUnauthorized
|
||||
this._httpProxy = opts.httpProxy
|
||||
this._setUrl(url)
|
||||
|
||||
this._connected = new Promise(resolve => {
|
||||
@ -851,6 +852,7 @@ export class Xapi extends EventEmitter {
|
||||
rejectUnauthorized: !this._allowUnauthorized,
|
||||
},
|
||||
url,
|
||||
httpProxy: this._httpProxy,
|
||||
})
|
||||
this._url = url
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import httpRequestPlus from 'http-request-plus'
|
||||
import ProxyAgent from 'proxy-agent'
|
||||
import { format, parse } from 'json-rpc-protocol'
|
||||
|
||||
import XapiError from '../_XapiError'
|
||||
@ -6,7 +7,11 @@ import XapiError from '../_XapiError'
|
||||
import UnsupportedTransport from './_UnsupportedTransport'
|
||||
|
||||
// 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) =>
|
||||
httpRequestPlus
|
||||
.post(url, {
|
||||
@ -17,6 +22,7 @@ export default ({ secureOptions, url }) => {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
path: '/jsonrpc',
|
||||
agent,
|
||||
})
|
||||
.readAll('utf8')
|
||||
.then(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { createClient, createSecureClient } from 'xmlrpc'
|
||||
import { promisify } from 'promise-toolbox'
|
||||
import ProxyAgent from 'proxy-agent'
|
||||
|
||||
import XapiError from '../_XapiError'
|
||||
|
||||
@ -70,10 +71,15 @@ const parseResult = result => {
|
||||
throw new UnsupportedTransport()
|
||||
}
|
||||
|
||||
export default ({ secureOptions, url: { hostname, port, protocol } }) => {
|
||||
export default ({ secureOptions, url: { hostname, port, protocol }, httpProxy }) => {
|
||||
const secure = protocol === 'https:'
|
||||
let agent
|
||||
if (httpProxy !== undefined) {
|
||||
agent = new ProxyAgent(httpProxy)
|
||||
}
|
||||
const client = (secure ? createSecureClient : createClient)({
|
||||
...(secure ? secureOptions : undefined),
|
||||
agent,
|
||||
host: hostname,
|
||||
path: '/json',
|
||||
port,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { createClient, createSecureClient } from 'xmlrpc'
|
||||
import { promisify } from 'promise-toolbox'
|
||||
import ProxyAgent from 'proxy-agent'
|
||||
|
||||
import XapiError from '../_XapiError'
|
||||
|
||||
@ -30,10 +31,15 @@ const parseResult = result => {
|
||||
return result.Value
|
||||
}
|
||||
|
||||
export default ({ secureOptions, url: { hostname, port, protocol } }) => {
|
||||
export default ({ secureOptions, url: { hostname, port, protocol, httpProxy } }) => {
|
||||
const secure = protocol === 'https:'
|
||||
let agent
|
||||
if (httpProxy !== undefined) {
|
||||
agent = new ProxyAgent(httpProxy)
|
||||
}
|
||||
const client = (secure ? createSecureClient : createClient)({
|
||||
...(secure ? secureOptions : undefined),
|
||||
agent,
|
||||
host: hostname,
|
||||
port,
|
||||
})
|
||||
|
@ -36,6 +36,10 @@ add.params = {
|
||||
optional: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
httpProxy: {
|
||||
optional: true,
|
||||
type: 'string',
|
||||
},
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@ -104,6 +108,10 @@ set.params = {
|
||||
optional: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
httpProxy: {
|
||||
optional: true,
|
||||
type: ['string', 'null'],
|
||||
},
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
@ -94,7 +94,7 @@ export default class {
|
||||
// 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!
|
||||
// Could we use tokens instead?
|
||||
// TODO: use plain objects
|
||||
@ -102,6 +102,7 @@ export default class {
|
||||
allowUnauthorized,
|
||||
enabled: true,
|
||||
host,
|
||||
httpProxy,
|
||||
label: label || undefined,
|
||||
password,
|
||||
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 xapi = this._xapis[id]
|
||||
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') {
|
||||
throw new Error('this entry require disconnecting the server to update it')
|
||||
@ -153,6 +161,10 @@ export default class {
|
||||
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)
|
||||
}
|
||||
|
||||
@ -288,6 +300,7 @@ export default class {
|
||||
readOnly: server.readOnly,
|
||||
|
||||
...config.get('xapiOptions'),
|
||||
httpProxy: server.httpProxy,
|
||||
guessVhdSizeOnImport: config.get('guessVhdSizeOnImport'),
|
||||
|
||||
auth: {
|
||||
|
@ -1826,6 +1826,8 @@ const messages = {
|
||||
serverEnabled: 'Enabled',
|
||||
serverDisabled: 'Disabled',
|
||||
serverDisable: 'Disable server',
|
||||
serverHttpProxy: ' HTTP proxy URL',
|
||||
serverHttpProxyPlaceHolder: ' HTTP proxy URL',
|
||||
|
||||
// ----- Copy VM -----
|
||||
copyVm: 'Copy VM',
|
||||
|
@ -560,10 +560,11 @@ export const exportConfig = () =>
|
||||
|
||||
// Server ------------------------------------------------------------
|
||||
|
||||
export const addServer = (host, username, password, label, allowUnauthorized) =>
|
||||
export const addServer = (host, username, password, label, allowUnauthorized, httpProxy) =>
|
||||
_call('server.add', {
|
||||
allowUnauthorized,
|
||||
host,
|
||||
httpProxy,
|
||||
label,
|
||||
password,
|
||||
username,
|
||||
|
@ -132,6 +132,18 @@ const COLUMNS = [
|
||||
itemRenderer: ({ poolId }) => poolId !== undefined && <Pool id={poolId} link />,
|
||||
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 = [
|
||||
{
|
||||
@ -152,13 +164,13 @@ export default class Servers extends Component {
|
||||
}
|
||||
|
||||
_addServer = async () => {
|
||||
const { label, host, password, username, allowUnauthorized } = this.state
|
||||
|
||||
await addServer(host, username, password, label, allowUnauthorized)
|
||||
const { label, host, password, username, allowUnauthorized, httpProxy } = this.state
|
||||
await addServer(host, username, password, label, allowUnauthorized, httpProxy)
|
||||
|
||||
this.setState({
|
||||
allowUnauthorized: false,
|
||||
host: '',
|
||||
httpProxy: '',
|
||||
label: '',
|
||||
password: '',
|
||||
username: '',
|
||||
@ -227,6 +239,15 @@ export default class Servers extends Component {
|
||||
<Toggle onChange={this.linkState('allowUnauthorized')} value={state.allowUnauthorized} />
|
||||
</Tooltip>
|
||||
</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'>
|
||||
{_('serverConnect')}
|
||||
</ActionButton>
|
||||
|
Loading…
Reference in New Issue
Block a user