mirror of
https://github.com/vatesfr/xen-orchestra.git
synced 2026-11-13 18:51:03 -06:00
Compare commits
1 Commits
proxy-api-
...
centralize
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f102a0d43 |
@@ -89,21 +89,7 @@ export default class Api {
|
||||
|
||||
let result
|
||||
try {
|
||||
result = await zone.run(() => {
|
||||
const { method, params } = body
|
||||
|
||||
debug(`call: ${method}()`, { method, params })
|
||||
const fn = this._methods[method]
|
||||
if (fn === undefined) {
|
||||
throw new MethodNotFound(method)
|
||||
}
|
||||
|
||||
if (fn.unref) {
|
||||
ctx.req.socket.unref()
|
||||
}
|
||||
|
||||
return fn(params)
|
||||
})
|
||||
result = await zone.run(() => this._call(body.method, body.params))
|
||||
} catch (error) {
|
||||
ctx.set('Content-Type', 'application/json')
|
||||
ctx.body = formatError(body.id, error)
|
||||
@@ -250,7 +236,7 @@ export default class Api {
|
||||
})
|
||||
}
|
||||
|
||||
addMethod(name, method, { description, params = {}, result: resultSchema, unref = false } = {}) {
|
||||
addMethod(name, method, { description, params = {}, result: resultSchema } = {}) {
|
||||
const methods = this._methods
|
||||
|
||||
if (name in methods) {
|
||||
@@ -290,7 +276,6 @@ export default class Api {
|
||||
m.description = description
|
||||
m.params = params
|
||||
m.result = resultSchema
|
||||
m.unref = unref
|
||||
|
||||
methods[name] = m
|
||||
|
||||
@@ -333,6 +318,15 @@ export default class Api {
|
||||
return once => forOwn(removes, remove => remove())
|
||||
}
|
||||
|
||||
_call(method, params = {}) {
|
||||
debug(`call: ${method}()`, { method, params })
|
||||
const fn = this._methods[method]
|
||||
if (fn === undefined) {
|
||||
throw new MethodNotFound(method)
|
||||
}
|
||||
return fn(params)
|
||||
}
|
||||
|
||||
#compileSchema(schema) {
|
||||
if (schema === undefined) {
|
||||
return stubTrue
|
||||
|
||||
@@ -154,7 +154,7 @@ export default class Appliance {
|
||||
params: { channel: { type: 'string' } },
|
||||
},
|
||||
],
|
||||
upgrade: [() => callUpdate({ upgrade: true }), { unref: true }],
|
||||
upgrade: () => callUpdate({ upgrade: true }),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<!--packages-start-->
|
||||
|
||||
- @xen-orchestra/log minor
|
||||
- @xen-orchestra/proxy patch
|
||||
- @xen-orchestra/mixin minor
|
||||
- @xen-orchestra/xapi patch
|
||||
- xo-cli minor
|
||||
|
||||
@@ -30,11 +30,11 @@ import { URL } from 'url'
|
||||
import { verifyTotp } from '@vates/otp'
|
||||
|
||||
import { compile as compilePug } from 'pug'
|
||||
import { fromCallback, fromEvent } from 'promise-toolbox'
|
||||
import { fromCallback, fromEvent, pCatch } from 'promise-toolbox'
|
||||
import { ifDef } from '@xen-orchestra/defined'
|
||||
|
||||
import fse from 'fs-extra'
|
||||
import { invalidCredentials } from 'xo-common/api-errors.js'
|
||||
import { invalidCredentials, noSuchObject } from 'xo-common/api-errors.js'
|
||||
import { Peer as JsonRpcPeer } from 'json-rpc-peer'
|
||||
|
||||
import ensureArray from './_ensureArray.mjs'
|
||||
@@ -314,8 +314,7 @@ async function setUpPassport(express, xo, { authentication: authCfg, http: { coo
|
||||
})(req, res, next)
|
||||
}
|
||||
|
||||
const { token } = req.cookies
|
||||
if (token !== undefined && (await xo.isValidAuthenticationToken(token))) {
|
||||
if (req.user !== undefined) {
|
||||
next()
|
||||
} else {
|
||||
req.flash('return-url', url)
|
||||
@@ -572,7 +571,16 @@ const setUpProxies = (express, opts, xo) => {
|
||||
|
||||
for (const prefix in opts) {
|
||||
if (url.startsWith(prefix)) {
|
||||
const target = opts[prefix]
|
||||
let target = opts[prefix]
|
||||
|
||||
if (typeof target !== 'string') {
|
||||
const { permission } = target
|
||||
target = target.target
|
||||
|
||||
if (permission !== undefined && !xo.hasPermission(req.user?.permission, permission)) {
|
||||
return res.sendStatus(401)
|
||||
}
|
||||
}
|
||||
|
||||
proxy.web(req, res, {
|
||||
agent: new URL(target).hostname === 'localhost' ? undefined : xo.httpAgent,
|
||||
@@ -597,7 +605,16 @@ const setUpProxies = (express, opts, xo) => {
|
||||
|
||||
for (const prefix in opts) {
|
||||
if (url.startsWith(prefix)) {
|
||||
const target = opts[prefix]
|
||||
let target = opts[prefix]
|
||||
|
||||
if (typeof target !== 'string') {
|
||||
const { permission } = target
|
||||
target = target.target
|
||||
|
||||
if (permission !== undefined && !xo.hasPermission(req.user?.permission, permission)) {
|
||||
return socket.end('HTTP/1.1 401 Unauthorized\r\n\r\n')
|
||||
}
|
||||
}
|
||||
|
||||
proxy.ws(req, socket, head, {
|
||||
agent: new URL(target).hostname === 'localhost' ? undefined : xo.httpAgent,
|
||||
@@ -693,13 +710,13 @@ const setUpApi = (webServer, xo, config, useForwardedHeaders) => {
|
||||
|
||||
const CONSOLE_PROXY_PATH_RE = /^\/api\/consoles\/(.*)$/
|
||||
|
||||
const setUpConsoleProxy = (webServer, xo, useForwardedHeaders) => {
|
||||
const setUpConsoleProxy = (express, xo, useForwardedHeaders) => {
|
||||
const webSocketServer = new WebSocketServer({
|
||||
noServer: true,
|
||||
})
|
||||
xo.hooks.on('stop', () => fromCallback.call(webSocketServer, 'close'))
|
||||
|
||||
webServer.on('upgrade', async (req, socket, head) => {
|
||||
express.on('upgrade', async (req, socket, head) => {
|
||||
const matches = CONSOLE_PROXY_PATH_RE.exec(req.url)
|
||||
if (!matches) {
|
||||
return
|
||||
@@ -711,17 +728,11 @@ const setUpConsoleProxy = (webServer, xo, useForwardedHeaders) => {
|
||||
|
||||
// TODO: factorize permissions checking in an Express middleware.
|
||||
{
|
||||
const { token } = parseCookies(req.headers.cookie)
|
||||
|
||||
const remoteAddress = proxyAddr(req, useForwardedHeaders)
|
||||
|
||||
const { user } = await xo.authenticateUser({ token }, { ip: remoteAddress })
|
||||
const { user } = req
|
||||
if (!(await xo.hasPermissions(user.id, [[id, 'operate']]))) {
|
||||
throw invalidCredentials()
|
||||
}
|
||||
|
||||
log.info(`+ Console proxy (${user.name} - ${remoteAddress})`)
|
||||
|
||||
const data = {
|
||||
timestamp: Date.now(),
|
||||
userId: user.id,
|
||||
@@ -849,21 +860,6 @@ export default async function main(args) {
|
||||
}
|
||||
}
|
||||
|
||||
// Attaches express to the web server.
|
||||
webServer.on('request', (req, res) => {
|
||||
// don't redirect let's encrypt challenge to https
|
||||
if (req.url.startsWith('/.well')) {
|
||||
return
|
||||
}
|
||||
// don't handle proxy requests
|
||||
if (req.url.startsWith('/')) {
|
||||
return express(req, res)
|
||||
}
|
||||
})
|
||||
webServer.on('upgrade', (req, socket, head) => {
|
||||
express.emit('upgrade', req, socket, head)
|
||||
})
|
||||
|
||||
const safeMode = includes(args, '--safe-mode')
|
||||
|
||||
// Creates main object.
|
||||
@@ -877,6 +873,41 @@ export default async function main(args) {
|
||||
safeMode,
|
||||
})
|
||||
|
||||
let useForwardedHeaders = false
|
||||
|
||||
async function authenticateRequest(req) {
|
||||
const cookies = parseCookies(req.headers.cookie)
|
||||
|
||||
const { token = cookies.authenticationToken } = cookies
|
||||
if (token !== undefined) {
|
||||
try {
|
||||
const { user } = await xo.authenticateUser({ token }, { ip: proxyAddr(req, useForwardedHeaders) })
|
||||
req.user = user
|
||||
} catch (error) {
|
||||
if (!invalidCredentials.is(error)) {
|
||||
log.warn({ error })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attaches express to the web server.
|
||||
webServer.on('request', async (req, res) => {
|
||||
// don't redirect let's encrypt challenge to https
|
||||
if (req.url.startsWith('/.well')) {
|
||||
return
|
||||
}
|
||||
// don't handle proxy requests
|
||||
if (req.url.startsWith('/')) {
|
||||
await authenticateRequest(req)
|
||||
return express(req, res)
|
||||
}
|
||||
})
|
||||
webServer.on('upgrade', async (req, socket, head) => {
|
||||
await authenticateRequest(req)
|
||||
express.emit('upgrade', req, socket, head)
|
||||
})
|
||||
|
||||
// Register web server close on XO stop.
|
||||
xo.hooks.on('stop', () => fromCallback.call(webServer, 'stop'))
|
||||
|
||||
@@ -886,7 +917,7 @@ export default async function main(args) {
|
||||
// Trigger a clean job.
|
||||
await xo.hooks.clean()
|
||||
|
||||
const useForwardedHeaders = (() => {
|
||||
useForwardedHeaders = (() => {
|
||||
// recompile the fonction when the setting change
|
||||
let useForwardedHeaders
|
||||
xo.config.watch('http.useForwardedHeaders', val => {
|
||||
@@ -911,7 +942,7 @@ export default async function main(args) {
|
||||
await setUpPassport(express, xo, config)
|
||||
|
||||
// Must be set up before the static files.
|
||||
setUpApi(webServer, xo, config, useForwardedHeaders)
|
||||
setUpApi(express, xo, config, useForwardedHeaders)
|
||||
|
||||
setUpProxies(express, config.http.proxies, xo)
|
||||
|
||||
|
||||
@@ -402,7 +402,10 @@ export default class Api {
|
||||
let result = await (name in NO_LOG_METHODS
|
||||
? method.call(app, resolvedParams)
|
||||
: app.tasks
|
||||
.create({ name: 'API call: ' + name, method: name, params: data.params, type: 'api.call' }, { clearLogOnSuccess: true })
|
||||
.create(
|
||||
{ name: 'API call: ' + name, method: name, params: data.params, type: 'api.call' },
|
||||
{ clearLogOnSuccess: true }
|
||||
)
|
||||
.run(() => method.call(app, resolvedParams)))
|
||||
|
||||
// If nothing was returned, consider this operation a success
|
||||
@@ -496,6 +499,10 @@ export default class Api {
|
||||
return connection
|
||||
}
|
||||
|
||||
hasPermission(actual = 'none', expected) {
|
||||
return PERMISSIONS[actual] >= PERMISSIONS[expected]
|
||||
}
|
||||
|
||||
registerApiHttpRequest(method, connection, fn, data, { exposeAllErrors = false, ...opts } = {}) {
|
||||
const app = this._app
|
||||
const logger = this._logger
|
||||
|
||||
@@ -312,6 +312,11 @@ export default class {
|
||||
return token !== undefined && (await this._app.doesUserExist(token.user_id))
|
||||
}
|
||||
|
||||
async getAuthenticationTokenUser(id) {
|
||||
const token = await this.getAuthenticationToken(id)
|
||||
return this._app.getUser(token.user_id)
|
||||
}
|
||||
|
||||
async updateAuthenticationToken(properties, { description }) {
|
||||
const token = await this.getAuthenticationToken(properties)
|
||||
patch(token, { description })
|
||||
|
||||
Reference in New Issue
Block a user