2 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
4 from ipsilon.login.common import LoginMgrsInstall
5 from ipsilon.info.common import InfoProviderInstall
6 from ipsilon.providers.common import ProvidersInstall
7 from ipsilon.helpers.common import EnvHelpersInstall
8 from ipsilon.util.data import UserStore
9 from ipsilon.tools import files
24 TEMPLATES = '/usr/share/ipsilon/templates/install'
25 CONFDIR = '/etc/ipsilon'
26 DATADIR = '/var/lib/ipsilon'
27 HTTPDCONFD = '/etc/httpd/conf.d'
28 BINDIR = '/usr/libexec'
29 STATICDIR = '/usr/share/ipsilon'
30 WSGI_SOCKET_PREFIX = None
33 class ConfigurationError(Exception):
35 def __init__(self, message):
36 super(ConfigurationError, self).__init__(message)
37 self.message = message
40 return repr(self.message)
43 #Silence cherrypy logging to screen
44 cherrypy.log.screen = False
47 LOGFILE = '/var/log/ipsilon-install.log'
48 logger = logging.getLogger()
52 global logger # pylint: disable=W0603
53 if os.path.isfile(LOGFILE):
55 created = '%s' % time.ctime(os.path.getctime(LOGFILE))
56 shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created))
59 logger = logging.getLogger()
61 lh = logging.FileHandler(LOGFILE)
63 print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e))
64 lh = logging.StreamHandler(sys.stderr)
65 formatter = logging.Formatter('[%(asctime)s] %(message)s')
66 lh.setFormatter(formatter)
67 lh.setLevel(logging.DEBUG)
69 logger.propagate = False
70 ch = logging.StreamHandler(sys.stdout)
71 formatter = logging.Formatter('%(message)s')
72 ch.setFormatter(formatter)
73 ch.setLevel(logging.INFO)
75 cherrypy.log.error_log.setLevel(logging.DEBUG)
78 def install(plugins, args):
79 logger.info('Installation initiated')
80 now = time.strftime("%Y%m%d%H%M%S", time.gmtime())
81 instance_conf = os.path.join(CONFDIR, args['instance'])
83 logger.info('Installing default config files')
84 ipsilon_conf = os.path.join(instance_conf, 'ipsilon.conf')
85 idp_conf = os.path.join(instance_conf, 'idp.conf')
86 args['httpd_conf'] = os.path.join(HTTPDCONFD,
87 'ipsilon-%s.conf' % args['instance'])
88 args['data_dir'] = os.path.join(DATADIR, args['instance'])
89 args['public_data_dir'] = os.path.join(args['data_dir'], 'public')
90 args['wellknown_dir'] = os.path.join(args['public_data_dir'],
92 if os.path.exists(ipsilon_conf):
93 shutil.move(ipsilon_conf, '%s.bakcup.%s' % (ipsilon_conf, now))
94 if os.path.exists(idp_conf):
95 shutil.move(idp_conf, '%s.backup.%s' % (idp_conf, now))
96 if not os.path.exists(instance_conf):
97 os.makedirs(instance_conf, 0700)
98 confopts = {'instance': args['instance'],
99 'datadir': args['data_dir'],
100 'publicdatadir': args['public_data_dir'],
101 'wellknowndir': args['wellknown_dir'],
102 'sysuser': args['system_user'],
103 'ipsilondir': BINDIR,
104 'staticdir': STATICDIR,
105 'admindb': args['admin_dburi'] or args['database_url'] % {
106 'datadir': args['data_dir'], 'dbname': 'adminconfig'},
107 'usersdb': args['users_dburi'] or args['database_url'] % {
108 'datadir': args['data_dir'], 'dbname': 'userprefs'},
109 'transdb': args['transaction_dburi'] or args['database_url'] %
110 {'datadir': args['data_dir'], 'dbname': 'transactions'},
111 'secure': "False" if args['secure'] == "no" else "True",
112 'debugging': "True" if args['server_debugging'] else "False"}
113 # Testing database sessions
114 if 'session_type' in args:
115 confopts['sesstype'] = args['session_type']
117 confopts['sesstype'] = 'file'
118 if 'session_dburi' in args:
119 confopts['sessopt'] = 'dburi'
120 confopts['sessval'] = args['session_dburi']
122 confopts['sessopt'] = 'path'
123 confopts['sessval'] = os.path.join(args['data_dir'], 'sessions')
124 # Whether to disable security (for testing)
125 if args['secure'] == 'no':
126 confopts['secure'] = "False"
127 confopts['sslrequiressl'] = ""
129 confopts['secure'] = "True"
130 confopts['sslrequiressl'] = " SSLRequireSSL"
131 if WSGI_SOCKET_PREFIX:
132 confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
134 confopts['wsgi_socket'] = ''
135 files.write_from_template(ipsilon_conf,
136 os.path.join(TEMPLATES, 'ipsilon.conf'),
138 files.write_from_template(idp_conf,
139 os.path.join(TEMPLATES, 'idp.conf'),
141 if not os.path.exists(args['httpd_conf']):
142 os.symlink(idp_conf, args['httpd_conf'])
143 if not os.path.exists(args['public_data_dir']):
144 os.makedirs(args['public_data_dir'], 0755)
145 if not os.path.exists(args['wellknown_dir']):
146 os.makedirs(args['wellknown_dir'], 0755)
147 sessdir = os.path.join(args['data_dir'], 'sessions')
148 if not os.path.exists(sessdir):
149 os.makedirs(sessdir, 0700)
150 data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
151 if not os.path.exists(data_conf):
152 os.symlink(ipsilon_conf, data_conf)
153 # Load the cherrypy config from the newly installed file so
154 # that db paths and all is properly set before configuring
156 cherrypy.config.update(ipsilon_conf)
158 # Prepare to allow plugins to save things changed during install
159 changes = {'env_helper': {},
164 # Move pre-existing admin db away
165 admin_db = cherrypy.config['admin.config.db']
166 if os.path.exists(admin_db):
167 shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
170 users_db = cherrypy.config['user.prefs.db']
171 if os.path.exists(users_db):
172 shutil.move(users_db, '%s.backup.%s' % (users_db, now))
174 db.save_user_preferences(args['admin_user'], {'is_admin': 1})
176 logger.info('Configuring environment helpers')
177 for plugin_name in plugins['Environment Helpers']:
178 plugin = plugins['Environment Helpers'][plugin_name]
180 if plugin.configure_server(args, plugin_changes) == False:
181 logger.info('Configuration of environment helper %s failed' % plugin_name)
182 changes['env_helper'][plugin_name] = plugin_changes
184 logger.info('Configuring login managers')
185 for plugin_name in args['lm_order']:
187 plugin = plugins['Login Managers'][plugin_name]
189 sys.exit('Login provider %s not installed' % plugin_name)
191 if plugin.configure(args, plugin_changes) == False:
192 logger.info('Configuration of login manager %s failed' % plugin_name)
193 changes['login_manager'][plugin_name] = plugin_changes
195 logger.info('Configuring Info provider')
196 for plugin_name in plugins['Info Provider']:
197 plugin = plugins['Info Provider'][plugin_name]
199 if plugin.configure(args, plugin_changes) == False:
200 logger.info('Configuration of info provider %s failed' % plugin_name)
201 changes['info_provider'][plugin_name] = plugin_changes
203 logger.info('Configuring Authentication Providers')
204 for plugin_name in plugins['Auth Providers']:
205 plugin = plugins['Auth Providers'][plugin_name]
207 if plugin.configure(args, plugin_changes) == False:
208 logger.info('Configuration of auth provider %s failed' % plugin_name)
209 changes['auth_provider'][plugin_name] = plugin_changes
211 # Save any changes that were made
212 install_changes = os.path.join(instance_conf, 'install_changes')
213 changes = json.dumps(changes)
214 with open(install_changes, 'w+') as f:
217 # Fixup permissions so only the ipsilon user can read these files
218 files.fix_user_dirs(instance_conf, opts['system_user'])
219 files.fix_user_dirs(args['data_dir'], opts['system_user'])
221 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
222 except Exception: # pylint: disable=broad-except
226 def uninstall(plugins, args):
227 logger.info('Uninstallation initiated')
228 instance_conf = os.path.join(CONFDIR, args['instance'])
230 httpd_conf = os.path.join(HTTPDCONFD,
231 'ipsilon-%s.conf' % args['instance'])
232 data_dir = os.path.join(DATADIR, args['instance'])
234 if not os.path.exists(instance_conf):
235 raise Exception('Could not find instance %s configuration'
237 if not os.path.exists(httpd_conf):
238 raise Exception('Could not find instance %s httpd configuration'
241 sure = raw_input(('Are you certain you want to erase instance %s ' +
245 raise Exception('Aborting')
247 # Get the details of what we changed during installation
248 install_changes = os.path.join(instance_conf, 'install_changes')
249 with open(install_changes, 'r') as f:
250 changes = json.loads(f.read())
252 logger.info('Removing environment helpers')
253 for plugin_name in plugins['Environment Helpers']:
254 plugin = plugins['Environment Helpers'][plugin_name]
255 plugin_changes = changes['env_helper'].get(plugin_name, {})
256 if plugin.unconfigure(args, plugin_changes) == False:
257 logger.info('Removal of environment helper %s failed' % plugin_name)
259 logger.info('Removing login managers')
260 for plugin_name in plugins['Login Managers']:
261 plugin = plugins['Login Managers'][plugin_name]
262 plugin_changes = changes['login_manager'].get(plugin_name, {})
263 if plugin.unconfigure(args, plugin_changes) == False:
264 logger.info('Removal of login manager %s failed' % plugin_name)
266 logger.info('Removing Info providers')
267 for plugin_name in plugins['Info Provider']:
268 plugin = plugins['Info Provider'][plugin_name]
269 plugin_changes = changes['info_provider'].get(plugin_name, {})
270 if plugin.unconfigure(args, plugin_changes) == False:
271 logger.info('Removal of info provider %s failed' % plugin_name)
273 logger.info('Removing Authentication Providers')
274 for plugin_name in plugins['Auth Providers']:
275 plugin = plugins['Auth Providers'][plugin_name]
276 plugin_changes = changes['auth_provider'].get(plugin_name, {})
277 if plugin.unconfigure(args, plugin_changes) == False:
278 logger.info('Removal of auth provider %s failed' % plugin_name)
280 logger.info('Removing httpd configuration')
281 os.remove(httpd_conf)
282 logger.info('Erasing instance configuration')
283 shutil.rmtree(instance_conf)
284 logger.info('Erasing instance data')
285 shutil.rmtree(data_dir)
286 logger.info('Uninstalled instance %s' % args['instance'])
291 'Environment Helpers': EnvHelpersInstall().plugins,
292 'Login Managers': LoginMgrsInstall().plugins,
293 'Info Provider': InfoProviderInstall().plugins,
294 'Auth Providers': ProvidersInstall().plugins
299 def parse_config_profile(args):
300 config = ConfigParser.RawConfigParser()
301 files = config.read(args['config_profile'])
303 raise ConfigurationError('Config Profile file %s not found!' %
304 args['config_profile'])
306 if 'globals' in config.sections():
307 G = config.options('globals')
309 val = config.get('globals', g)
313 for k in globals().keys():
314 if k.lower() == g.lower():
318 if 'arguments' in config.sections():
319 A = config.options('arguments')
321 args[a] = config.get('arguments', a)
326 def parse_args(plugins):
327 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
328 parser.add_argument('--version',
329 action='version', version='%(prog)s 0.1')
330 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
331 help='Comma separated list of login managers')
332 parser.add_argument('--hostname',
333 help="Machine's fully qualified host name")
334 parser.add_argument('--instance', default='idp',
335 help="IdP instance name, each is a separate idp")
336 parser.add_argument('--system-user', default='ipsilon',
337 help="User account used to run the server")
338 parser.add_argument('--admin-user', default='admin',
339 help="User account that is assigned admin privileges")
340 parser.add_argument('--database-url',
341 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
342 help="The (templatized) database URL to use")
343 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
344 help="Turn on all security checks")
345 parser.add_argument('--config-profile', default=None,
346 help=argparse.SUPPRESS)
347 parser.add_argument('--server-debugging', action='store_true',
348 help="Enable debugging")
349 parser.add_argument('--uninstall', action='store_true',
350 help="Uninstall the server and all data")
351 parser.add_argument('--yes', action='store_true',
352 help="Always answer yes")
353 parser.add_argument('--admin-dburi',
354 help='Configuration database URI (override template)')
355 parser.add_argument('--users-dburi',
356 help='User configuration database URI (override '
358 parser.add_argument('--transaction-dburi',
359 help='Transaction database URI (override template)')
363 for plugin_group in plugins:
364 group = parser.add_argument_group(plugin_group)
365 for plugin_name in plugins[plugin_group]:
366 plugin = plugins[plugin_group][plugin_name]
367 if plugin.ptype == 'login':
368 lms.append(plugin.name)
369 plugin.install_args(group)
371 args = vars(parser.parse_args())
373 if args['config_profile']:
374 args = parse_config_profile(args)
376 if not args['hostname']:
377 args['hostname'] = socket.getfqdn()
379 if args['uninstall']:
382 if len(args['hostname'].split('.')) < 2:
383 raise ConfigurationError('Hostname: %s is not a FQDN')
385 for plugin_group in plugins:
386 for plugin_name in plugins[plugin_group]:
387 plugin = plugins[plugin_group][plugin_name]
388 plugin.validate_args(args)
391 pwd.getpwnam(args['system_user'])
393 raise ConfigurationError('User: %s not found on the system')
395 if args['lm_order'] is None:
396 args['lm_order'] = []
398 if args[name] == 'yes':
399 args['lm_order'].append(name)
401 args['lm_order'] = args['lm_order'].split(',')
403 if len(args['lm_order']) == 0:
404 sys.exit('No login plugins are enabled.')
406 #FIXME: check instance is only alphanums
410 if __name__ == '__main__':
415 fplugins = find_plugins()
416 opts = parse_args(fplugins)
418 logger.setLevel(logging.DEBUG)
420 logger.debug('Installation arguments:')
421 for k in sorted(opts.iterkeys()):
422 logger.debug('%s: %s', k, opts[k])
424 if 'uninstall' in opts and opts['uninstall'] is True:
425 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
426 logger.info('Instance %s could not be found' % opts['instance'])
428 uninstall(fplugins, opts)
430 install(fplugins, opts)
431 except Exception, e: # pylint: disable=broad-except
433 if 'uninstall' in opts and opts['uninstall'] is True:
434 logger.info('Uninstallation aborted.')
436 logger.info('Installation aborted.')
437 logger.info('See log file %s for details' % LOGFILE)
444 if 'uninstall' in opts and opts['uninstall'] is True:
445 logger.info('Uninstallation complete.')
447 logger.info('Installation complete.')
448 logger.info('Please restart HTTPD to enable the IdP instance.')