From 774b4051bc32159d6220f850512f083535170aeb Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Fri, 27 Dec 2013 12:04:54 +0100 Subject: [PATCH] XO-Server can now drop priviledges. --- config/local.yaml.dist | 32 +++++++++++++++++++++++++++++ src/fibers-utils.coffee | 45 ++++++++++++++++++++++++++++++++--------- src/main.coffee | 20 +++++++++++++----- src/web-server.coffee | 9 ++++++++- src/xo.coffee | 5 ++--- 5 files changed, 92 insertions(+), 19 deletions(-) diff --git a/config/local.yaml.dist b/config/local.yaml.dist index 3294b30e6..86fbc5063 100644 --- a/config/local.yaml.dist +++ b/config/local.yaml.dist @@ -1,5 +1,35 @@ # Note: Relative paths will be resolved from XO-Server's directory. +#===================================================================== + +# It may be necessary to run XO-Server as a priviledged user (e.g. +# `root`) for instance to allow the HTTP server to listen on a +# [priviledged ports](http://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html). +# +# To avoid security issues, XO-Server can drop its priviledges by +# changing the user and the group is running with. +# +# Note: XO-Server will change them just after reading the +# configuration. + +# User to run XO-Server as. +# +# Note: The user can be specified using either its name or its numeric +# identifier. +# +# Default: undefined +#user: 'nobody' + +# Group to run XO-Server as. +# +# Note: The group can be specified using either its name or its +# numeric identifier. +# +# Default: undefined +#group: 'nogroup' + +#===================================================================== + # Configuration of the embedded HTTP server. http: @@ -52,6 +82,8 @@ http: mounts: #'/': '/path/to/xo-web/dist/' +#===================================================================== + # Connection to the Redis server. redis: # Syntax: tcp://[db[:password]@]hostname[:port] diff --git a/src/fibers-utils.coffee b/src/fibers-utils.coffee index b96418eb6..3142ddfaf 100644 --- a/src/fibers-utils.coffee +++ b/src/fibers-utils.coffee @@ -21,7 +21,8 @@ $sleep = (ms) -> setTimeout (-> fiber.run()), ms $fiber.yield() -# Makes an asynchrouneous function synchrouneous (in a fiber). +# Makes an Node like asynchrouneous function synchrouneous (in a +# fiber). $synchronize = (fn, ctx) -> fn = ctx[fn] if $_.isString fn @@ -33,22 +34,46 @@ $synchronize = (fn, ctx) -> fiber.throwInto error else fiber.run result - result = fn.apply ctx, args - - # A promise can only be detected once the function has been - # called. - if $isPromise result - result.then( - (result) -> fiber.run result - (error) -> fiber.throwInto error - ) + fn.apply ctx, args $fiber.yield() +# Waits for an event. +# +# Note: if the *error* event is emitted, this function will throw. +$waitEvent = (emitter, event) -> + fiber = $fiber.current + + errorHandler = null + handler = (args...) -> + emitter.removeListener 'error', errorHandler + fiber.run args + errorHandler = (error) -> + emitter.removeListener event, handler + fiber.throwInto error + + emitter.once event, handler + emitter.once 'error', errorHandler + + $fiber.yield() + +# Waits for a promise to be fulfilled or broken. +$waitPromise = (promise) -> + fiber = $fiber.current + + promise.then( + (result) -> fiber.run result + (error) -> fiber.throwInto error + ) + + $fiber.yield() + #===================================================================== module.exports = { $fiberize $sleep $synchronize + $waitEvent + $waitPromise } diff --git a/src/main.coffee b/src/main.coffee index c39c91eac..cfd7bc093 100644 --- a/src/main.coffee +++ b/src/main.coffee @@ -25,7 +25,7 @@ $Session = require './session' $XO = require './xo' # Helpers for dealing with fibers. -{$fiberize, $synchronize} = require './fibers-utils' +{$fiberize, $synchronize, $waitEvent} = require './fibers-utils' # HTTP/HTTPS server which can listen on multiple ports. $WebServer = require './web-server' @@ -112,6 +112,20 @@ do $fiberize -> if $nconf.get entry console.warn "[Warn] `#{entry}` configuration is deprecated." + # Creates the web server according to the configuration. + webServer = new $WebServer() + webServer.listen options for options in $nconf.get 'http:listen' + + # Waits for the web server to start listening to drop priviledges. + $waitEvent webServer, 'listening' + try + if (group = $nconf.get 'group')? + process.setgid group + if (user = $nconf.get 'user')? + process.setuid user + catch error + console.warn "[WARN] Failed to change the user or group: #{error.message}" + # Creates the main object which will connects to Xen servers and # manages all the models. xo = new $XO() @@ -123,10 +137,6 @@ do $fiberize -> } } - # Creates the web server according to the configuration. - webServer = new $WebServer() - webServer.listen options for options in $nconf.get 'http:listen' - # Static file serving (e.g. for XO-Web). connect = $connect() for urlPath, filePaths of $nconf.get 'http:mounts' diff --git a/src/web-server.coffee b/src/web-server.coffee index 39418f728..a881853e5 100644 --- a/src/web-server.coffee +++ b/src/web-server.coffee @@ -42,6 +42,7 @@ class $WebServer extends $EventEmitter constructor: -> @_servers = [] + @_notYetListening = 0 close: -> server.close() for server in @_servers @@ -65,8 +66,10 @@ class $WebServer extends $EventEmitter else server.listen port, host + ++@_notYetListening + # Helpful message. - server.once 'listening', -> + server.once 'listening', => address = server.address() if $_.isObject address {address, port} = address @@ -74,6 +77,10 @@ class $WebServer extends $EventEmitter console.log "WebServer listening on #{address}" + # If the web server is listening on all addresses, fire the + # `listening` event. + @emit 'listening' unless --@_notYetListening + # Forwards events to this object. $_.each $events, (event) => server.on event, (args...) => @emit event, args... diff --git a/src/xo.coffee b/src/xo.coffee index f4f460280..aff90c0f5 100644 --- a/src/xo.coffee +++ b/src/xo.coffee @@ -28,7 +28,7 @@ $Model = require './model' $XAPI = require './xapi' # Helpers for dealing with fibers. -{$fiberize, $synchronize} = require './fibers-utils' +{$fiberize, $synchronize, $waitPromise} = require './fibers-utils' #===================================================================== @@ -266,8 +266,7 @@ class $XO throw error unless error[0] is 'SESSION_NOT_REGISTERED' # Connects to existing servers. - getServers = $synchronize 'get', @servers - connect server for server in getServers() + connect server for server in $waitPromise @servers.get() # Automatically connects to new servers. @servers.on 'add', (servers) ->