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 WSGI_SOCKET_PREFIX = None
33 class ConfigurationError(StandardError):
35 def __init__(self, message):
36 StandardError.__init__(self, message)
39 #Silence cherrypy logging to screen
40 cherrypy.log.screen = False
43 LOGFILE = '/var/log/ipsilon-install.log'
44 logger = logging.getLogger()
48 global logger # pylint: disable=W0603
49 if os.path.isfile(LOGFILE):
51 created = '%s' % time.ctime(os.path.getctime(LOGFILE))
52 shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created))
55 logger = logging.getLogger()
57 lh = logging.FileHandler(LOGFILE)
59 print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e))
60 lh = logging.StreamHandler(sys.stderr)
61 formatter = logging.Formatter('[%(asctime)s] %(message)s')
62 lh.setFormatter(formatter)
63 lh.setLevel(logging.DEBUG)
65 logger.propagate = False
66 ch = logging.StreamHandler(sys.stdout)
67 formatter = logging.Formatter('%(message)s')
68 ch.setFormatter(formatter)
69 ch.setLevel(logging.INFO)
71 cherrypy.log.error_log.setLevel(logging.DEBUG)
74 def install(plugins, args):
75 logger.info('Installation initiated')
76 now = time.strftime("%Y%m%d%H%M%S", time.gmtime())
77 instance_conf = os.path.join(CONFDIR, args['instance'])
79 logger.info('Installing default config files')
80 ipsilon_conf = os.path.join(instance_conf, 'ipsilon.conf')
81 idp_conf = os.path.join(instance_conf, 'idp.conf')
82 args['httpd_conf'] = os.path.join(HTTPDCONFD,
83 'ipsilon-%s.conf' % args['instance'])
84 args['data_dir'] = os.path.join(DATADIR, args['instance'])
85 args['public_data_dir'] = os.path.join(args['data_dir'], 'public')
86 args['wellknown_dir'] = os.path.join(args['public_data_dir'],
88 if os.path.exists(ipsilon_conf):
89 shutil.move(ipsilon_conf, '%s.bakcup.%s' % (ipsilon_conf, now))
90 if os.path.exists(idp_conf):
91 shutil.move(idp_conf, '%s.backup.%s' % (idp_conf, now))
92 if not os.path.exists(instance_conf):
93 os.makedirs(instance_conf, 0700)
94 confopts = {'instance': args['instance'],
95 'datadir': args['data_dir'],
96 'publicdatadir': args['public_data_dir'],
97 'wellknowndir': args['wellknown_dir'],
98 'sysuser': args['system_user'],
100 'staticdir': STATICDIR,
101 'admindb': args['admin_dburi'] or args['database_url'] % {
102 'datadir': args['data_dir'], 'dbname': 'adminconfig'},
103 'usersdb': args['users_dburi'] or args['database_url'] % {
104 'datadir': args['data_dir'], 'dbname': 'userprefs'},
105 'transdb': args['transaction_dburi'] or args['database_url'] %
106 {'datadir': args['data_dir'], 'dbname': 'transactions'},
107 'samlsessionsdb': args['samlsessions_dburi'] or args[
108 'database_url'] % {'datadir': args['data_dir'],
109 'dbname': 'saml2sessions'},
110 'secure': "False" if args['secure'] == "no" else "True",
111 'debugging': "True" if args['server_debugging'] else "False"}
112 # Testing database sessions
113 if 'session_type' in args:
114 confopts['sesstype'] = args['session_type']
116 confopts['sesstype'] = 'file'
117 if 'session_dburi' in args:
118 confopts['sessopt'] = 'dburi'
119 confopts['sessval'] = args['session_dburi']
121 confopts['sessopt'] = 'path'
122 confopts['sessval'] = os.path.join(args['data_dir'], 'sessions')
123 # Whether to disable security (for testing)
124 if args['secure'] == 'no':
125 confopts['secure'] = "False"
126 confopts['sslrequiressl'] = ""
128 confopts['secure'] = "True"
129 confopts['sslrequiressl'] = " SSLRequireSSL"
130 if WSGI_SOCKET_PREFIX:
131 confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
133 confopts['wsgi_socket'] = ''
134 files.write_from_template(ipsilon_conf,
135 os.path.join(TEMPLATES, 'ipsilon.conf'),
137 files.write_from_template(idp_conf,
138 os.path.join(TEMPLATES, 'idp.conf'),
140 if not os.path.exists(args['httpd_conf']):
141 os.symlink(idp_conf, args['httpd_conf'])
142 if not os.path.exists(args['public_data_dir']):
143 os.makedirs(args['public_data_dir'], 0755)
144 if not os.path.exists(args['wellknown_dir']):
145 os.makedirs(args['wellknown_dir'], 0755)
146 sessdir = os.path.join(args['data_dir'], 'sessions')
147 if not os.path.exists(sessdir):
148 os.makedirs(sessdir, 0700)
149 data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
150 if not os.path.exists(data_conf):
151 os.symlink(ipsilon_conf, data_conf)
152 # Load the cherrypy config from the newly installed file so
153 # that db paths and all is properly set before configuring
155 cherrypy.config.update(ipsilon_conf)
157 # Prepare to allow plugins to save things changed during install
158 changes = {'env_helper': {},
163 # Move pre-existing dbs away
164 admin_db = cherrypy.config['admin.config.db']
165 if os.path.exists(admin_db):
166 shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
167 users_db = cherrypy.config['user.prefs.db']
168 if os.path.exists(users_db):
169 shutil.move(users_db, '%s.backup.%s' % (users_db, now))
171 # Initialize initial database schemas
172 dbupgrade.execute_upgrade(ipsilon_conf)
174 # Store primary admin
176 db.save_user_preferences(args['admin_user'], {'is_admin': 1})
178 logger.info('Configuring environment helpers')
179 for plugin_name in plugins['Environment Helpers']:
180 plugin = plugins['Environment Helpers'][plugin_name]
182 if plugin.configure_server(args, plugin_changes) == False:
183 msg = 'Configuration of environment helper %s failed' % plugin_name
184 raise ConfigurationError(msg)
185 changes['env_helper'][plugin_name] = plugin_changes
187 logger.info('Configuring login managers')
188 for plugin_name in args['lm_order']:
190 plugin = plugins['Login Managers'][plugin_name]
192 sys.exit('Login provider %s not installed' % plugin_name)
194 if plugin.configure(args, plugin_changes) == False:
195 msg = 'Configuration of login manager %s failed' % plugin_name
196 raise ConfigurationError(msg)
197 changes['login_manager'][plugin_name] = plugin_changes
199 logger.info('Configuring Info provider')
200 for plugin_name in plugins['Info Provider']:
201 plugin = plugins['Info Provider'][plugin_name]
203 if plugin.configure(args, plugin_changes) == False:
204 msg = 'Configuration of info provider %s failed' % plugin_name
205 raise ConfigurationError(msg)
206 changes['info_provider'][plugin_name] = plugin_changes
208 logger.info('Configuring Authentication Providers')
209 for plugin_name in plugins['Auth Providers']:
210 plugin = plugins['Auth Providers'][plugin_name]
212 if plugin.configure(args, plugin_changes) == False:
213 msg = 'Configuration of auth provider %s failed' % plugin_name
214 raise ConfigurationError(msg)
215 changes['auth_provider'][plugin_name] = plugin_changes
217 # Save any changes that were made
218 install_changes = os.path.join(instance_conf, 'install_changes')
219 changes = json.dumps(changes)
220 with open(install_changes, 'w+') as f:
223 # Initialize extra database schemas
224 dbupgrade.execute_upgrade(ipsilon_conf)
226 # Fixup permissions so only the ipsilon user can read these files
227 files.fix_user_dirs(instance_conf, opts['system_user'])
228 files.fix_user_dirs(args['data_dir'], opts['system_user'])
230 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
231 except Exception: # pylint: disable=broad-except
235 def uninstall(plugins, args):
236 logger.info('Uninstallation initiated')
237 instance_conf = os.path.join(CONFDIR, args['instance'])
239 httpd_conf = os.path.join(HTTPDCONFD,
240 'ipsilon-%s.conf' % args['instance'])
241 data_dir = os.path.join(DATADIR, args['instance'])
243 if not os.path.exists(instance_conf):
244 raise Exception('Could not find instance %s configuration'
246 if not os.path.exists(httpd_conf):
247 raise Exception('Could not find instance %s httpd configuration'
250 sure = raw_input(('Are you certain you want to erase instance %s ' +
254 raise Exception('Aborting')
256 # Get the details of what we changed during installation
257 install_changes = os.path.join(instance_conf, 'install_changes')
258 with open(install_changes, 'r') as f:
259 changes = json.loads(f.read())
261 logger.info('Removing environment helpers')
262 for plugin_name in plugins['Environment Helpers']:
263 plugin = plugins['Environment Helpers'][plugin_name]
264 plugin_changes = changes['env_helper'].get(plugin_name, {})
265 if plugin.unconfigure(args, plugin_changes) == False:
266 logger.info('Removal of environment helper %s failed' % plugin_name)
268 logger.info('Removing login managers')
269 for plugin_name in plugins['Login Managers']:
270 plugin = plugins['Login Managers'][plugin_name]
271 plugin_changes = changes['login_manager'].get(plugin_name, {})
272 if plugin.unconfigure(args, plugin_changes) == False:
273 logger.info('Removal of login manager %s failed' % plugin_name)
275 logger.info('Removing Info providers')
276 for plugin_name in plugins['Info Provider']:
277 plugin = plugins['Info Provider'][plugin_name]
278 plugin_changes = changes['info_provider'].get(plugin_name, {})
279 if plugin.unconfigure(args, plugin_changes) == False:
280 logger.info('Removal of info provider %s failed' % plugin_name)
282 logger.info('Removing Authentication Providers')
283 for plugin_name in plugins['Auth Providers']:
284 plugin = plugins['Auth Providers'][plugin_name]
285 plugin_changes = changes['auth_provider'].get(plugin_name, {})
286 if plugin.unconfigure(args, plugin_changes) == False:
287 logger.info('Removal of auth provider %s failed' % plugin_name)
289 logger.info('Removing httpd configuration')
290 os.remove(httpd_conf)
291 logger.info('Erasing instance configuration')
292 shutil.rmtree(instance_conf)
293 logger.info('Erasing instance data')
294 shutil.rmtree(data_dir)
295 logger.info('Uninstalled instance %s' % args['instance'])
300 'Environment Helpers': EnvHelpersInstall().plugins,
301 'Login Managers': LoginMgrsInstall().plugins,
302 'Info Provider': InfoProviderInstall().plugins,
303 'Auth Providers': ProvidersInstall().plugins
308 def parse_config_profile(args):
309 config = ConfigParser.RawConfigParser()
310 files = config.read(args['config_profile'])
312 raise ConfigurationError('Config Profile file %s not found!' %
313 args['config_profile'])
315 if 'globals' in config.sections():
316 G = config.options('globals')
318 val = config.get('globals', g)
323 if k.lower() == g.lower():
327 if 'arguments' in config.sections():
328 A = config.options('arguments')
330 args[a] = config.get('arguments', a)
335 def parse_args(plugins):
336 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
337 parser.add_argument('--version',
338 action='version', version='%(prog)s 0.1')
339 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
340 help='Comma separated list of login managers')
341 parser.add_argument('--hostname',
342 help="Machine's fully qualified host name")
343 parser.add_argument('--instance', default='idp',
344 help="IdP instance name, each is a separate idp")
345 parser.add_argument('--system-user', default='ipsilon',
346 help="User account used to run the server")
347 parser.add_argument('--admin-user', default='admin',
348 help="User account that is assigned admin privileges")
349 parser.add_argument('--database-url',
350 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
351 help="The (templatized) database URL to use")
352 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
353 help="Turn on all security checks")
354 parser.add_argument('--config-profile', default=None,
355 help=argparse.SUPPRESS)
356 parser.add_argument('--server-debugging', action='store_true',
357 help="Enable debugging")
358 parser.add_argument('--uninstall', action='store_true',
359 help="Uninstall the server and all data")
360 parser.add_argument('--yes', action='store_true',
361 help="Always answer yes")
362 parser.add_argument('--admin-dburi',
363 help='Configuration database URI (override template)')
364 parser.add_argument('--users-dburi',
365 help='User configuration database URI (override '
367 parser.add_argument('--transaction-dburi',
368 help='Transaction database URI (override template)')
369 parser.add_argument('--samlsessions-dburi',
370 help='SAML 2 sessions database URI (override template)')
374 for plugin_group in plugins:
375 group = parser.add_argument_group(plugin_group)
376 for plugin_name in plugins[plugin_group]:
377 plugin = plugins[plugin_group][plugin_name]
378 if plugin.ptype == 'login':
379 lms.append(plugin.name)
380 plugin.install_args(group)
382 args = vars(parser.parse_args())
384 if args['config_profile']:
385 args = parse_config_profile(args)
387 if not args['hostname']:
388 args['hostname'] = socket.getfqdn()
390 if args['uninstall']:
393 if len(args['hostname'].split('.')) < 2:
394 raise ConfigurationError('Hostname: %s is not a FQDN')
396 for plugin_group in plugins:
397 for plugin_name in plugins[plugin_group]:
398 plugin = plugins[plugin_group][plugin_name]
399 plugin.validate_args(args)
402 pwd.getpwnam(args['system_user'])
404 raise ConfigurationError('User: %s not found on the system')
406 if args['lm_order'] is None:
407 args['lm_order'] = []
409 if args[name] == 'yes':
410 args['lm_order'].append(name)
412 args['lm_order'] = args['lm_order'].split(',')
414 if len(args['lm_order']) == 0 and args['ipa'] != 'yes':
415 sys.exit('No login plugins are enabled.')
417 #FIXME: check instance is only alphanums
421 if __name__ == '__main__':
426 fplugins = find_plugins()
427 opts = parse_args(fplugins)
429 logger.setLevel(logging.DEBUG)
431 logger.debug('Installation arguments:')
432 for k in sorted(opts.iterkeys()):
433 logger.debug('%s: %s', k, opts[k])
435 if 'uninstall' in opts and opts['uninstall'] is True:
436 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
437 logger.info('Instance %s could not be found' % opts['instance'])
439 uninstall(fplugins, opts)
441 install(fplugins, opts)
442 except Exception, e: # pylint: disable=broad-except
443 logger.debug(e, exc_info=1)
445 if 'uninstall' in opts and opts['uninstall'] is True:
446 logger.info('Uninstallation aborted.')
448 logger.info('Installation aborted.')
449 logger.info('See log file %s for details' % LOGFILE)
456 if 'uninstall' in opts and opts['uninstall'] is True:
457 logger.info('Uninstallation complete.')
459 logger.info('Installation complete.')
460 logger.info('Please restart HTTPD to enable the IdP instance.')