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/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 'cleanup_interval': args['cleanup_interval'],
101 'ipsilondir': BINDIR,
102 'staticdir': STATICDIR,
103 'cachedir': CACHEDIR,
104 'admindb': args['admin_dburi'] or args['database_url'] % {
105 'datadir': args['data_dir'], 'dbname': 'adminconfig'},
106 'usersdb': args['users_dburi'] or args['database_url'] % {
107 'datadir': args['data_dir'], 'dbname': 'userprefs'},
108 'transdb': args['transaction_dburi'] or args['database_url'] %
109 {'datadir': args['data_dir'], 'dbname': 'transactions'},
110 'samlsessionsdb': args['samlsessions_dburi'] or args[
111 'database_url'] % {'datadir': args['data_dir'],
112 'dbname': 'saml2sessions'},
113 'secure': "False" if args['secure'] == "no" else "True",
114 'debugging': "True" if args['server_debugging'] else "False"}
115 # Testing database sessions
116 if 'session_type' in args:
117 confopts['sesstype'] = args['session_type']
119 confopts['sesstype'] = 'file'
120 if 'session_dburi' in args:
121 confopts['sessopt'] = 'dburi'
122 confopts['sessval'] = args['session_dburi']
124 confopts['sessopt'] = 'path'
125 confopts['sessval'] = os.path.join(args['data_dir'], 'sessions')
126 # Whether to disable security (for testing)
127 if args['secure'] == 'no':
128 confopts['secure'] = "False"
129 confopts['sslrequiressl'] = ""
131 confopts['secure'] = "True"
132 confopts['sslrequiressl'] = " SSLRequireSSL"
133 if WSGI_SOCKET_PREFIX:
134 confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
136 confopts['wsgi_socket'] = ''
137 files.write_from_template(ipsilon_conf,
138 os.path.join(TEMPLATES, 'ipsilon.conf'),
140 files.write_from_template(idp_conf,
141 os.path.join(TEMPLATES, 'idp.conf'),
143 if not os.path.exists(args['httpd_conf']):
144 os.symlink(idp_conf, args['httpd_conf'])
145 if not os.path.exists(args['public_data_dir']):
146 os.makedirs(args['public_data_dir'], 0755)
147 if not os.path.exists(args['wellknown_dir']):
148 os.makedirs(args['wellknown_dir'], 0755)
149 sessdir = os.path.join(args['data_dir'], 'sessions')
150 if not os.path.exists(sessdir):
151 os.makedirs(sessdir, 0700)
152 data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
153 if not os.path.exists(data_conf):
154 os.symlink(ipsilon_conf, data_conf)
155 # Load the cherrypy config from the newly installed file so
156 # that db paths and all is properly set before configuring
158 cherrypy.config.update(ipsilon_conf)
160 # Prepare to allow plugins to save things changed during install
161 changes = {'env_helper': {},
166 # Move pre-existing dbs away
167 admin_db = cherrypy.config['admin.config.db']
168 if os.path.exists(admin_db):
169 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 # Initialize initial database schemas
175 dbupgrade.execute_upgrade(ipsilon_conf)
177 # Store primary admin
179 db.save_user_preferences(args['admin_user'], {'is_admin': 1})
181 logger.info('Configuring environment helpers')
182 for plugin_name in plugins['Environment Helpers']:
183 plugin = plugins['Environment Helpers'][plugin_name]
185 if plugin.configure_server(args, plugin_changes) == False:
186 msg = 'Configuration of environment helper %s failed' % plugin_name
187 raise ConfigurationError(msg)
188 changes['env_helper'][plugin_name] = plugin_changes
190 logger.info('Configuring login managers')
191 for plugin_name in args['lm_order']:
193 plugin = plugins['Login Managers'][plugin_name]
195 sys.exit('Login provider %s not installed' % plugin_name)
197 if plugin.configure(args, plugin_changes) == False:
198 msg = 'Configuration of login manager %s failed' % plugin_name
199 raise ConfigurationError(msg)
200 changes['login_manager'][plugin_name] = plugin_changes
202 logger.info('Configuring Info provider')
203 for plugin_name in plugins['Info Provider']:
204 plugin = plugins['Info Provider'][plugin_name]
206 if plugin.configure(args, plugin_changes) == False:
207 msg = 'Configuration of info provider %s failed' % plugin_name
208 raise ConfigurationError(msg)
209 changes['info_provider'][plugin_name] = plugin_changes
211 logger.info('Configuring Authentication Providers')
212 for plugin_name in plugins['Auth Providers']:
213 plugin = plugins['Auth Providers'][plugin_name]
215 if plugin.configure(args, plugin_changes) == False:
216 msg = 'Configuration of auth provider %s failed' % plugin_name
217 raise ConfigurationError(msg)
218 changes['auth_provider'][plugin_name] = plugin_changes
220 # Save any changes that were made
221 install_changes = os.path.join(instance_conf, 'install_changes')
222 changes = json.dumps(changes)
223 with open(install_changes, 'w+') as f:
226 # Initialize extra database schemas
227 dbupgrade.execute_upgrade(ipsilon_conf)
229 # Fixup permissions so only the ipsilon user can read these files
230 files.fix_user_dirs(instance_conf, opts['system_user'])
231 files.fix_user_dirs(args['data_dir'], opts['system_user'])
233 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
234 except Exception: # pylint: disable=broad-except
238 def uninstall(plugins, args):
239 logger.info('Uninstallation initiated')
240 instance_conf = os.path.join(CONFDIR, args['instance'])
242 httpd_conf = os.path.join(HTTPDCONFD,
243 'ipsilon-%s.conf' % args['instance'])
244 data_dir = os.path.join(DATADIR, args['instance'])
246 tconf = ConfigParser.SafeConfigParser()
247 tconf.read(os.path.join(instance_conf, 'ipsilon.conf'))
248 cache_dir = tconf.get('global', 'cache_dir').replace('"', '')
250 if not os.path.exists(instance_conf):
251 raise Exception('Could not find instance %s configuration'
253 if not os.path.exists(httpd_conf):
254 raise Exception('Could not find instance %s httpd configuration'
257 sure = raw_input(('Are you certain you want to erase instance %s ' +
261 raise Exception('Aborting')
263 # Get the details of what we changed during installation
264 install_changes = os.path.join(instance_conf, 'install_changes')
265 with open(install_changes, 'r') as f:
266 changes = json.loads(f.read())
268 logger.info('Removing environment helpers')
269 for plugin_name in plugins['Environment Helpers']:
270 plugin = plugins['Environment Helpers'][plugin_name]
271 plugin_changes = changes['env_helper'].get(plugin_name, {})
272 if plugin.unconfigure(args, plugin_changes) == False:
273 logger.info('Removal of environment helper %s failed' % plugin_name)
275 logger.info('Removing login managers')
276 for plugin_name in plugins['Login Managers']:
277 plugin = plugins['Login Managers'][plugin_name]
278 plugin_changes = changes['login_manager'].get(plugin_name, {})
279 if plugin.unconfigure(args, plugin_changes) == False:
280 logger.info('Removal of login manager %s failed' % plugin_name)
282 logger.info('Removing Info providers')
283 for plugin_name in plugins['Info Provider']:
284 plugin = plugins['Info Provider'][plugin_name]
285 plugin_changes = changes['info_provider'].get(plugin_name, {})
286 if plugin.unconfigure(args, plugin_changes) == False:
287 logger.info('Removal of info provider %s failed' % plugin_name)
289 logger.info('Removing Authentication Providers')
290 for plugin_name in plugins['Auth Providers']:
291 plugin = plugins['Auth Providers'][plugin_name]
292 plugin_changes = changes['auth_provider'].get(plugin_name, {})
293 if plugin.unconfigure(args, plugin_changes) == False:
294 logger.info('Removal of auth provider %s failed' % plugin_name)
296 logger.info('Removing httpd configuration')
297 os.remove(httpd_conf)
298 logger.info('Erasing instance configuration')
299 shutil.rmtree(instance_conf)
300 logger.info('Erasing instance data')
301 shutil.rmtree(data_dir)
302 if cache_dir and os.path.exists(cache_dir):
303 for fn in os.listdir(cache_dir):
304 os.unlink(os.path.join(cache_dir, fn))
305 logger.info('Uninstalled instance %s' % args['instance'])
310 'Environment Helpers': EnvHelpersInstall().plugins,
311 'Login Managers': LoginMgrsInstall().plugins,
312 'Info Provider': InfoProviderInstall().plugins,
313 'Auth Providers': ProvidersInstall().plugins
318 def parse_config_profile(args):
319 config = ConfigParser.RawConfigParser()
320 files = config.read(args['config_profile'])
322 raise ConfigurationError('Config Profile file %s not found!' %
323 args['config_profile'])
325 if 'globals' in config.sections():
326 G = config.options('globals')
328 val = config.get('globals', g)
333 if k.lower() == g.lower():
337 if 'arguments' in config.sections():
338 A = config.options('arguments')
340 args[a] = config.get('arguments', a)
345 def parse_args(plugins):
346 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
347 parser.add_argument('--version',
348 action='version', version='%(prog)s 0.1')
349 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
350 help='Comma separated list of login managers')
351 parser.add_argument('--hostname',
352 help="Machine's fully qualified host name")
353 parser.add_argument('--instance', default='idp',
354 help="IdP instance name, each is a separate idp")
355 parser.add_argument('--system-user', default='ipsilon',
356 help="User account used to run the server")
357 parser.add_argument('--admin-user', default='admin',
358 help="User account that is assigned admin privileges")
359 parser.add_argument('--database-url',
360 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
361 help="The (templatized) database URL to use")
362 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
363 help="Turn on all security checks")
364 parser.add_argument('--config-profile', default=None,
365 help=argparse.SUPPRESS)
366 parser.add_argument('--server-debugging', action='store_true',
367 help="Enable debugging")
368 parser.add_argument('--uninstall', action='store_true',
369 help="Uninstall the server and all data")
370 parser.add_argument('--yes', action='store_true',
371 help="Always answer yes")
372 parser.add_argument('--admin-dburi',
373 help='Configuration database URI (override template)')
374 parser.add_argument('--users-dburi',
375 help='User configuration database URI (override '
377 parser.add_argument('--transaction-dburi',
378 help='Transaction database URI (override template)')
379 parser.add_argument('--samlsessions-dburi',
380 help='SAML 2 sessions database URI (override ' +
382 parser.add_argument('--cleanup-interval', default=30,
383 help='Interval between cleaning up stale database ' +
384 'entries (in minutes, default: 30 minutes)')
388 for plugin_group in plugins:
389 group = parser.add_argument_group(plugin_group)
390 for plugin_name in plugins[plugin_group]:
391 plugin = plugins[plugin_group][plugin_name]
392 if plugin.ptype == 'login':
393 lms.append(plugin.name)
394 plugin.install_args(group)
396 args = vars(parser.parse_args())
398 if args['config_profile']:
399 args = parse_config_profile(args)
401 if not args['hostname']:
402 args['hostname'] = socket.getfqdn()
404 if args['uninstall']:
407 if len(args['hostname'].split('.')) < 2:
408 raise ConfigurationError('Hostname: %s is not a FQDN')
410 for plugin_group in plugins:
411 for plugin_name in plugins[plugin_group]:
412 plugin = plugins[plugin_group][plugin_name]
413 plugin.validate_args(args)
416 pwd.getpwnam(args['system_user'])
418 raise ConfigurationError('User: %s not found on the system')
420 if args['lm_order'] is None:
421 args['lm_order'] = []
423 if args[name] == 'yes':
424 args['lm_order'].append(name)
426 args['lm_order'] = args['lm_order'].split(',')
428 if len(args['lm_order']) == 0 and args['ipa'] != 'yes':
429 sys.exit('No login plugins are enabled.')
431 #FIXME: check instance is only alphanums
435 if __name__ == '__main__':
440 fplugins = find_plugins()
441 opts = parse_args(fplugins)
443 logger.setLevel(logging.DEBUG)
445 logger.debug('Installation arguments:')
446 for k in sorted(opts.iterkeys()):
447 logger.debug('%s: %s', k, opts[k])
449 if 'uninstall' in opts and opts['uninstall'] is True:
450 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
451 logger.info('Instance %s could not be found' % opts['instance'])
453 uninstall(fplugins, opts)
455 install(fplugins, opts)
456 except Exception, e: # pylint: disable=broad-except
457 logger.debug(e, exc_info=1)
459 if 'uninstall' in opts and opts['uninstall'] is True:
460 logger.info('Uninstallation aborted.')
462 logger.info('Installation aborted.')
463 logger.info('See log file %s for details' % LOGFILE)
470 if 'uninstall' in opts and opts['uninstall'] is True:
471 logger.info('Uninstallation complete.')
473 logger.info('Installation complete.')
474 logger.info('Please restart HTTPD to enable the IdP instance.')