From: Daniel Henrique Barboza <danielhb(a)linux.vnet.ibm.com>
The kimchid script was revamped to launch 3 processes:
- a cherrypy process that will act as frontend, running as
non-root, providing the html templates;
- another cherrypy process running as backend, with root privileges;
- a reverse proxy (nginx) that will forward all requests made to
the kimchi port to the frontend/backend.
The same options that the previous kimchid scripts provided are
available in this new version.
Signed-off-by: Daniel Henrique Barboza <danielhb(a)linux.vnet.ibm.com>
---
src/kimchid.in | 200 +++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 174 insertions(+), 26 deletions(-)
diff --git a/src/kimchid.in b/src/kimchid.in
index 8b63b57..51d4026 100644
--- a/src/kimchid.in
+++ b/src/kimchid.in
@@ -2,7 +2,7 @@
#
# Project Kimchi
#
-# Copyright IBM, Corp. 2013
+# Copyright IBM, Corp. 2014
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -16,18 +16,27 @@
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
import logging
import os
+import pwd
+import re
+import signal
+import subprocess
import sys
+import time
sys.path.insert(1, '@pythondir@')
+from string import Template
+
import kimchi.server
import kimchi.config
from kimchi.config import config, paths
-from optparse import OptionParser
+from kimchi.utils import parse_command_line_options
+
if not paths.installed:
sys.path.append(paths.prefix)
@@ -35,30 +44,169 @@ if not paths.installed:
ACCESS_LOG = "kimchi-access.log"
ERROR_LOG = "kimchi-error.log"
+
+def create_proxy_config(p_port, f_port, b_port,
+ p_ssl_port, f_ssl_port, b_ssl_port):
+ """Create nginx configuration file based on current ports config
+
+ To allow flexibility in which port kimchi runs, we need the same
+ flexibility with the nginx proxy. This method creates the config
+ file dynamically by using 'nginx.conf.in' as a template, creating
+ the file 'nginx_kimchi.config' which will be used to launch the
+ proxy.
+
+ Arguments:
+ p_port - proxy port
+ f_port - frontend port
+ b_port - backend port
+ p_ssl_port - proxy SSL port
+ f_ssl_port - frontend SSL port
+ b_ssl_port - backend SSL port
+ """
+
+ # get SSL paths to be used by the proxy
+ config_dir = paths.conf_dir
+ cert = '%s/kimchi-cert.pem' % config_dir
+ key = '%s/kimchi-key.pem' % config_dir
+
+ with open(os.path.join(paths.conf_dir, "nginx.conf.in")) as myfile:
+ data = myfile.read()
+
+ data = Template(data)
+ data = data.safe_substitute(proxy_port=p_port,
+ frontend_port=f_port,
+ backend_port=b_port,
+ proxy_ssl_port=p_ssl_port,
+ frontend_ssl_port=f_ssl_port,
+ backend_ssl_port=b_ssl_port,
+ cert_pem=cert, cert_key=key)
+
+ config_file = open(os.getcwd() + "/nginx_kimchi.conf", "w")
+ config_file.write(data)
+ config_file.close()
+
+
+def start_proxy():
+ """Start nginx reverse proxy."""
+ config_file = os.getcwd() + "/nginx_kimchi.conf"
+ cmd = ['nginx', '-c', config_file]
+ subprocess.call(cmd)
+
+
+def terminate_proxy():
+ """Stop nginx process."""
+ term_proxy_cmd = ['nginx', '-s', 'stop']
+ subprocess.call(term_proxy_cmd)
+
+
def main(options):
- host = config.get("server", "host")
- port = config.get("server", "port")
- ssl_port = config.get("server", "ssl_port")
- runningEnv = config.get('server', 'environment')
- logDir = config.get("logging", "log_dir")
- logLevel = config.get("logging", "log_level")
-
- parser = OptionParser()
- parser.add_option('--host', type="string", default=host,
help="Hostname to listen on")
- parser.add_option('--port', type="int", default=port,
help="Port to listen on")
- parser.add_option('--ssl-port', type="int", default=ssl_port,
help="Enable a SSL server on the given port")
- parser.add_option('--log-level', default=logLevel, help="Logging
level")
- parser.add_option('--access-log', default=os.path.join(logDir,ACCESS_LOG),
help="Access log file")
- parser.add_option('--error-log', default=os.path.join(logDir,ERROR_LOG),
help="Error log file")
- parser.add_option('--environment', default=runningEnv, help="Running
environment of kimchi server")
- parser.add_option('--test', action='store_true', help="Run
server in mock model")
- (options, args) = parser.parse_args()
-
- # Add non-option arguments
- setattr(options, 'ssl_cert', config.get('server',
'ssl_cert'))
- setattr(options, 'ssl_key', config.get('server', 'ssl_key'))
-
- kimchi.server.main(options)
+ # Script must run as root or with sudo.
+ if not os.geteuid() == 0:
+ sys.exit("\nMust be root to run this script. Exiting ...\n")
+
+ # The following method is used when the process receives a
+ # SIGINT signal (CTRL+C).
+ def terminate_kimchi(signal, frame):
+ terminate_proxy()
+ sys.exit(0)
+ signal.signal(signal.SIGINT, terminate_kimchi)
+
+ # User that will execute the frontend will own this
+ # process temporarily. First we need to detect if the script
+ # was run with SUDO or from the root directly. If
+ # run with SUDO, the user that fired the execution will
+ # take over (this user is registered at the 'SUDO_USER' env
+ # variable). Otherwise the user kimchi will take over.
+ #
+ # The frontend will **NOT** be run as root under any
+ # circunstance using this script.
+ if os.getenv("SUDO_USER") is not None:
+ temp_user = os.getenv("SUDO_USER")
+ else:
+ temp_user = 'kimchi'
+
+ # Drop execution privileges temporarily
+ os.seteuid(pwd.getpwnam(temp_user).pw_uid)
+
+ # Validate command line options before forwarding them
+ # to the cherrypy processes.
+ parse_command_line_options()
+
+ # Copy the options (sys.argv[:1]) into 2 other lists. That
+ # way we can edit the copies with modified parameters to
+ # use in the cherrypy processes.
+ argv_frontend = list(options)
+ argv_backend = list(options)
+
+ ssl_port_match = re.compile("^--ssl-port.*")
+ port_match = re.compile("^--port.*")
+
+ port_index = None
+ ssl_port_index = None
+
+ # Default ports
+ proxy_port = 8000
+ proxy_ssl_port = 8001
+
+ # Search for the '--port=' and '--ssl-port=' arguments to verify
+ # if the user wants to run in a port other than the default.
+ for i, value in enumerate(options):
+ if re.search(port_match, value):
+ proxy_port = int(value.partition("=")[2])
+ port_index = i
+ elif re.search(ssl_port_match, value):
+ proxy_ssl_port = int(value.partition("=")[2])
+ ssl_port_index = i
+
+ # The max value between port and proxy_port
+ # will be used to calculate the frontend and
+ # backend ports.
+ next_port = max(proxy_port, proxy_ssl_port) + 1
+ frontend_port = next_port
+ next_port += 1
+ frontend_ssl_port = next_port
+ next_port += 1
+ backend_port = next_port
+ next_port += 1
+ backend_ssl_port = next_port
+
+ if port_index is not None:
+ # edit args to be used by the front/backend
+ argv_frontend[port_index] = "--port="+str(frontend_port)
+ argv_backend[port_index] = "--port="+str(backend_port)
+ else:
+ # add port info to the front/backend options
+ argv_frontend += ["--port="+str(frontend_port)]
+ argv_backend += ["--port="+str(backend_port)]
+
+ if ssl_port_index is not None:
+ # edit args to be used by the front/backend
+ argv_frontend[ssl_port_index] = "--ssl-port="+str(frontend_ssl_port)
+ argv_backend[ssl_port_index] = "--ssl-port="+str(backend_ssl_port)
+ else:
+ # add ssl-port info to the front/backend options
+ argv_frontend += ["--ssl-port="+str(frontend_ssl_port)]
+ argv_backend += ["--ssl-port="+str(backend_ssl_port)]
+
+ # launch frontend with edited options
+ command = os.getcwd() + '/src/kimchid_server'
+ cmd = [command] + argv_frontend
+ frontend = subprocess.Popen(cmd)
+
+ # Retrieve root access before launching the backend.
+ os.seteuid(0)
+
+ # Launch the cherrypy backend process.
+ cmd = [command] + argv_backend + ['--backend'] + ['--log_screen']
+ backend = subprocess.Popen(cmd, stderr=subprocess.STDOUT)
+
+ # Launch reverse proxy: create config file and start.
+ create_proxy_config(proxy_port, frontend_port, backend_port,
+ proxy_ssl_port, frontend_ssl_port, backend_ssl_port)
+ start_proxy()
+
+ # suspend execution until interruption (SIGINT, SIGKILL)
+ signal.pause()
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
--
1.8.3.1