Skip to content

Commit

Permalink
feat(xen-api): add HTTP proxy support (#5958)
Browse files Browse the repository at this point in the history
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/proxy-agents#127
  • Loading branch information
fbeauchamp authored Oct 27, 2021
1 parent 0c87dee commit 2412f8b
Show file tree
Hide file tree
Showing 12 changed files with 82 additions and 10 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 changes: 3 additions & 0 deletions packages/xapi-explore-sr/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/xen-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions packages/xen-api/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -851,6 +852,7 @@ export class Xapi extends EventEmitter {
rejectUnauthorized: !this._allowUnauthorized,
},
url,
httpProxy: this._httpProxy,
})
this._url = url
}
Expand Down
8 changes: 7 additions & 1 deletion packages/xen-api/src/transports/json-rpc.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import httpRequestPlus from 'http-request-plus'
import ProxyAgent from 'proxy-agent'
import { format, parse } from 'json-rpc-protocol'

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, {
Expand All @@ -17,6 +22,7 @@ export default ({ secureOptions, url }) => {
'Content-Type': 'application/json',
},
path: '/jsonrpc',
agent,
})
.readAll('utf8')
.then(
Expand Down
8 changes: 7 additions & 1 deletion packages/xen-api/src/transports/xml-rpc-json.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createClient, createSecureClient } from 'xmlrpc'
import { promisify } from 'promise-toolbox'
import ProxyAgent from 'proxy-agent'

import XapiError from '../_XapiError'

Expand Down Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion packages/xen-api/src/transports/xml-rpc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createClient, createSecureClient } from 'xmlrpc'
import { promisify } from 'promise-toolbox'
import ProxyAgent from 'proxy-agent'

import XapiError from '../_XapiError'

Expand Down Expand Up @@ -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,
})
Expand Down
8 changes: 8 additions & 0 deletions packages/xo-server/src/api/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ add.params = {
optional: true,
type: 'boolean',
},
httpProxy: {
optional: true,
type: 'string',
},
}

// -------------------------------------------------------------------
Expand Down Expand Up @@ -104,6 +108,10 @@ set.params = {
optional: true,
type: 'boolean',
},
httpProxy: {
optional: true,
type: ['string', 'null'],
},
}

// -------------------------------------------------------------------
Expand Down
19 changes: 16 additions & 3 deletions packages/xo-server/src/xo-mixins/xen-servers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,15 @@ 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
const server = await this._servers.create({
allowUnauthorized,
enabled: true,
host,
httpProxy,
label: label || undefined,
password,
readOnly,
Expand All @@ -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')
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -288,6 +300,7 @@ export default class {
readOnly: server.readOnly,

...config.get('xapiOptions'),
httpProxy: server.httpProxy,
guessVhdSizeOnImport: config.get('guessVhdSizeOnImport'),

auth: {
Expand Down
2 changes: 2 additions & 0 deletions packages/xo-web/src/common/intl/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
3 changes: 2 additions & 1 deletion packages/xo-web/src/common/xo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
27 changes: 24 additions & 3 deletions packages/xo-web/src/xo-app/settings/servers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
Expand All @@ -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: '',
Expand Down Expand Up @@ -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>
Expand Down

0 comments on commit 2412f8b

Please sign in to comment.