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, dbupgrade
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 CACHEDIR = '/var/cache/httpd/ipsilon'
31 WSGI_SOCKET_PREFIX = None
34 class ConfigurationError(StandardError):
36 def __init__(self, message):
37 StandardError.__init__(self, message)
40 #Silence cherrypy logging to screen
41 cherrypy.log.screen = False
44 LOGFILE = '/var/log/ipsilon-install.log'
45 logger = logging.getLogger()
49 global logger # pylint: disable=W0603
50 if os.path.isfile(LOGFILE):
52 created = '%s' % time.ctime(os.path.getctime(LOGFILE))
53 shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created))
56 logger = logging.getLogger()
58 lh = logging.FileHandler(LOGFILE)
60 print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e))
61 lh = logging.StreamHandler(sys.stderr)
62 formatter = logging.Formatter('[%(asctime)s] %(message)s')
63 lh.setFormatter(formatter)
64 lh.setLevel(logging.DEBUG)
66 logger.propagate = False
67 ch = logging.StreamHandler(sys.stdout)
68 formatter = logging.Formatter('%(message)s')
69 ch.setFormatter(formatter)
70 ch.setLevel(logging.INFO)
72 cherrypy.log.error_log.setLevel(logging.DEBUG)
75 def install(plugins, args):
76 logger.info('Installation initiated')
77 now = time.strftime("%Y%m%d%H%M%S", time.gmtime())
78 instance_conf = os.path.join(CONFDIR, args['instance'])
80 logger.info('Installing default config files')
81 ipsilon_conf = os.path.join(instance_conf, 'ipsilon.conf')
82 idp_conf = os.path.join(instance_conf, 'idp.conf')
83 args['httpd_conf'] = os.path.join(HTTPDCONFD,
84 'ipsilon-%s.conf' % args['instance'])
85 args['data_dir'] = os.path.join(DATADIR, args['instance'])
86 args['public_data_dir'] = os.path.join(args['data_dir'], 'public')
87 args['wellknown_dir'] = os.path.join(args['public_data_dir'],
89 if os.path.exists(ipsilon_conf):
90 shutil.move(ipsilon_conf, '%s.bakcup.%s' % (ipsilon_conf, now))
91 if os.path.exists(idp_conf):
92 shutil.move(idp_conf, '%s.backup.%s' % (idp_conf, now))
93 if not os.path.exists(instance_conf):
94 os.makedirs(instance_conf, 0700)
95 confopts = {'instance': args['instance'],
96 'datadir': args['data_dir'],
97 'publicdatadir': args['public_data_dir'],
98 'wellknowndir': args['wellknown_dir'],
99 'sysuser': args['system_user'],
100 'ipsilondir': BINDIR,
101 'staticdir': STATICDIR,
102 'cachedir': CACHEDIR,
103 'admindb': args['admin_dburi'] or args['database_url'] % {
104 'datadir': args['data_dir'], 'dbname': 'adminconfig'},
105 'usersdb': args['users_dburi'] or args['database_url'] % {
106 'datadir': args['data_dir'], 'dbname': 'userprefs'},
107 'transdb': args['transaction_dburi'] or args['database_url'] %
108 {'datadir': args['data_dir'], 'dbname': 'transactions'},
109 'samlsessionsdb': args['samlsessions_dburi'] or args[
110 'database_url'] % {'datadir': args['data_dir'],
111 'dbname': 'saml2sessions'},
112 'secure': "False" if args['secure'] == "no" else "True",
113 'debugging': "True" if args['server_debugging'] else "False"}
114 # Testing database sessions
115 if 'session_type' in args:
116 confopts['sesstype'] = args['session_type']
118 confopts['sesstype'] = 'file'
119 if 'session_dburi' in args:
120 confopts['sessopt'] = 'dburi'
121 confopts['sessval'] = args['session_dburi']
123 confopts['sessopt'] = 'path'
124 confopts['sessval'] = os.path.join(args['data_dir'], 'sessions')
125 # Whether to disable security (for testing)
126 if args['secure'] == 'no':
127 confopts['secure'] = "False"
128 confopts['sslrequiressl'] = ""
130 confopts['secure'] = "True"
131 confopts['sslrequiressl'] = " SSLRequireSSL"
132 if WSGI_SOCKET_PREFIX:
133 confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
135 confopts['wsgi_socket'] = ''
136 files.write_from_template(ipsilon_conf,
137 os.path.join(TEMPLATES, 'ipsilon.conf'),
139 files.write_from_template(idp_conf,
140 os.path.join(TEMPLATES, 'idp.conf'),
142 if not os.path.exists(args['httpd_conf']):
143 os.symlink(idp_conf, args['httpd_conf'])
144 if not os.path.exists(args['public_data_dir']):
145 os.makedirs(args['public_data_dir'], 0755)
146 if not os.path.exists(args['wellknown_dir']):
147 os.makedirs(args['wellknown_dir'], 0755)
148 sessdir = os.path.join(args['data_dir'], 'sessions')
149 if not os.path.exists(sessdir):
150 os.makedirs(sessdir, 0700)
151 data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
152 if not os.path.exists(data_conf):
153 os.symlink(ipsilon_conf, data_conf)
154 # Load the cherrypy config from the newly installed file so
155 # that db paths and all is properly set before configuring
157 cherrypy.config.update(ipsilon_conf)
159 # Prepare to allow plugins to save things changed during install
160 changes = {'env_helper': {},
165 # Move pre-existing dbs away
166 admin_db = cherrypy.config['admin.config.db']
167 if os.path.exists(admin_db):
168 shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
169 users_db = cherrypy.config['user.prefs.db']
170 if os.path.exists(users_db):
171 shutil.move(users_db, '%s.backup.%s' % (users_db, now))
173 # Initialize initial database schemas
174 dbupgrade.execute_upgrade(ipsilon_conf)
176 # Store primary admin
178 db.save_user_preferences(args['admin_user'], {'is_admin': 1})
180 logger.info('Configuring environment helpers')
181 for plugin_name in plugins['Environment Helpers']:
182 plugin = plugins['Environment Helpers'][plugin_name]
184 if plugin.configure_server(args, plugin_changes) == False:
185 msg = 'Configuration of environment helper %s failed' % plugin_name
186 raise ConfigurationError(msg)
187 changes['env_helper'][plugin_name] = plugin_changes
189 logger.info('Configuring login managers')
190 for plugin_name in args['lm_order']:
192 plugin = plugins['Login Managers'][plugin_name]
194 sys.exit('Login provider %s not installed' % plugin_name)
196 if plugin.configure(args, plugin_changes) == False:
197 msg = 'Configuration of login manager %s failed' % plugin_name
198 raise ConfigurationError(msg)
199 changes['login_manager'][plugin_name] = plugin_changes
201 logger.info('Configuring Info provider')
202 for plugin_name in plugins['Info Provider']:
203 plugin = plugins['Info Provider'][plugin_name]
205 if plugin.configure(args, plugin_changes) == False:
206 msg = 'Configuration of info provider %s failed' % plugin_name
207 raise ConfigurationError(msg)
208 changes['info_provider'][plugin_name] = plugin_changes
210 logger.info('Configuring Authentication Providers')
211 for plugin_name in plugins['Auth Providers']:
212 plugin = plugins['Auth Providers'][plugin_name]
214 if plugin.configure(args, plugin_changes) == False:
215 msg = 'Configuration of auth provider %s failed' % plugin_name
216 raise ConfigurationError(msg)
217 changes['auth_provider'][plugin_name] = plugin_changes
219 # Save any changes that were made
220 install_changes = os.path.join(instance_conf, 'install_changes')
221 changes = json.dumps(changes)
222 with open(install_changes, 'w+') as f:
225 # Initialize extra database schemas
226 dbupgrade.execute_upgrade(ipsilon_conf)
228 # Fixup permissions so only the ipsilon user can read these files
229 files.fix_user_dirs(instance_conf, opts['system_user'])
230 files.fix_user_dirs(args['data_dir'], opts['system_user'])
232 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
233 except Exception: # pylint: disable=broad-except
237 def uninstall(plugins, args):
238 logger.info('Uninstallation initiated')
239 instance_conf = os.path.join(CONFDIR, args['instance'])
241 httpd_conf = os.path.join(HTTPDCONFD,
242 'ipsilon-%s.conf' % args['instance'])
243 data_dir = os.path.join(DATADIR, args['instance'])
245 if not os.path.exists(instance_conf):
246 raise Exception('Could not find instance %s configuration'
248 if not os.path.exists(httpd_conf):
249 raise Exception('Could not find instance %s httpd configuration'
252 sure = raw_input(('Are you certain you want to erase instance %s ' +
256 raise Exception('Aborting')
258 # Get the details of what we changed during installation
259 install_changes = os.path.join(instance_conf, 'install_changes')
260 with open(install_changes, 'r') as f:
261 changes = json.loads(f.read())
263 logger.info('Removing environment helpers')
264 for plugin_name in plugins['Environment Helpers']:
265 plugin = plugins['Environment Helpers'][plugin_name]
266 plugin_changes = changes['env_helper'].get(plugin_name, {})
267 if plugin.unconfigure(args, plugin_changes) == False:
268 logger.info('Removal of environment helper %s failed' % plugin_name)
270 logger.info('Removing login managers')
271 for plugin_name in plugins['Login Managers']:
272 plugin = plugins['Login Managers'][plugin_name]
273 plugin_changes = changes['login_manager'].get(plugin_name, {})
274 if plugin.unconfigure(args, plugin_changes) == False:
275 logger.info('Removal of login manager %s failed' % plugin_name)
277 logger.info('Removing Info providers')
278 for plugin_name in plugins['Info Provider']:
279 plugin = plugins['Info Provider'][plugin_name]
280 plugin_changes = changes['info_provider'].get(plugin_name, {})
281 if plugin.unconfigure(args, plugin_changes) == False:
282 logger.info('Removal of info provider %s failed' % plugin_name)
284 logger.info('Removing Authentication Providers')
285 for plugin_name in plugins['Auth Providers']:
286 plugin = plugins['Auth Providers'][plugin_name]
287 plugin_changes = changes['auth_provider'].get(plugin_name, {})
288 if plugin.unconfigure(args, plugin_changes) == False:
289 logger.info('Removal of auth provider %s failed' % plugin_name)
291 logger.info('Removing httpd configuration')
292 os.remove(httpd_conf)
293 logger.info('Erasing instance configuration')
294 shutil.rmtree(instance_conf)
295 logger.info('Erasing instance data')
296 shutil.rmtree(data_dir)
297 logger.info('Uninstalled instance %s' % args['instance'])
302 'Environment Helpers': EnvHelpersInstall().plugins,
303 'Login Managers': LoginMgrsInstall().plugins,
304 'Info Provider': InfoProviderInstall().plugins,
305 'Auth Providers': ProvidersInstall().plugins
310 def parse_config_profile(args):
311 config = ConfigParser.RawConfigParser()
312 files = config.read(args['config_profile'])
314 raise ConfigurationError('Config Profile file %s not found!' %
315 args['config_profile'])
317 if 'globals' in config.sections():
318 G = config.options('globals')
320 val = config.get('globals', g)
325 if k.lower() == g.lower():
329 if 'arguments' in config.sections():
330 A = config.options('arguments')
332 args[a] = config.get('arguments', a)
337 def parse_args(plugins):
338 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
339 parser.add_argument('--version',
340 action='version', version='%(prog)s 0.1')
341 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
342 help='Comma separated list of login managers')
343 parser.add_argument('--hostname',
344 help="Machine's fully qualified host name")
345 parser.add_argument('--instance', default='idp',
346 help="IdP instance name, each is a separate idp")
347 parser.add_argument('--system-user', default='ipsilon',
348 help="User account used to run the server")
349 parser.add_argument('--admin-user', default='admin',
350 help="User account that is assigned admin privileges")
351 parser.add_argument('--database-url',
352 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
353 help="The (templatized) database URL to use")
354 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
355 help="Turn on all security checks")
356 parser.add_argument('--config-profile', default=None,
357 help=argparse.SUPPRESS)
358 parser.add_argument('--server-debugging', action='store_true',
359 help="Enable debugging")
360 parser.add_argument('--uninstall', action='store_true',
361 help="Uninstall the server and all data")
362 parser.add_argument('--yes', action='store_true',
363 help="Always answer yes")
364 parser.add_argument('--admin-dburi',
365 help='Configuration database URI (override template)')
366 parser.add_argument('--users-dburi',
367 help='User configuration database URI (override '
369 parser.add_argument('--transaction-dburi',
370 help='Transaction database URI (override template)')
371 parser.add_argument('--samlsessions-dburi',
372 help='SAML 2 sessions database URI (override template)')
376 for plugin_group in plugins:
377 group = parser.add_argument_group(plugin_group)
378 for plugin_name in plugins[plugin_group]:
379 plugin = plugins[plugin_group][plugin_name]
380 if plugin.ptype == 'login':
381 lms.append(plugin.name)
382 plugin.install_args(group)
384 args = vars(parser.parse_args())
386 if args['config_profile']:
387 args = parse_config_profile(args)
389 if not args['hostname']:
390 args['hostname'] = socket.getfqdn()
392 if args['uninstall']:
395 if len(args['hostname'].split('.')) < 2:
396 raise ConfigurationError('Hostname: %s is not a FQDN')
398 for plugin_group in plugins:
399 for plugin_name in plugins[plugin_group]:
400 plugin = plugins[plugin_group][plugin_name]
401 plugin.validate_args(args)
404 pwd.getpwnam(args['system_user'])
406 raise ConfigurationError('User: %s not found on the system')
408 if args['lm_order'] is None:
409 args['lm_order'] = []
411 if args[name] == 'yes':
412 args['lm_order'].append(name)
414 args['lm_order'] = args['lm_order'].split(',')
416 if len(args['lm_order']) == 0 and args['ipa'] != 'yes':
417 sys.exit('No login plugins are enabled.')
419 #FIXME: check instance is only alphanums
423 if __name__ == '__main__':
428 fplugins = find_plugins()
429 opts = parse_args(fplugins)
431 logger.setLevel(logging.DEBUG)
433 logger.debug('Installation arguments:')
434 for k in sorted(opts.iterkeys()):
435 logger.debug('%s: %s', k, opts[k])
437 if 'uninstall' in opts and opts['uninstall'] is True:
438 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
439 logger.info('Instance %s could not be found' % opts['instance'])
441 uninstall(fplugins, opts)
443 install(fplugins, opts)
444 except Exception, e: # pylint: disable=broad-except
445 logger.debug(e, exc_info=1)
447 if 'uninstall' in opts and opts['uninstall'] is True:
448 logger.info('Uninstallation aborted.')
450 logger.info('Installation aborted.')
451 logger.info('See log file %s for details' % LOGFILE)
458 if 'uninstall' in opts and opts['uninstall'] is True:
459 logger.info('Uninstallation complete.')
461 logger.info('Installation complete.')
462 logger.info('Please restart HTTPD to enable the IdP instance.')