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'])
247 tconf = ConfigParser.SafeConfigParser()
248 tconf.read(os.path.join(instance_conf, 'ipsilon.conf'))
249 cache_dir = tconf.get('global', 'cache_dir')
250 except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
253 cache_dir = cache_dir.replace('"', '')
255 if not os.path.exists(instance_conf):
256 raise Exception('Could not find instance %s configuration'
258 if not os.path.exists(httpd_conf):
259 raise Exception('Could not find instance %s httpd configuration'
262 sure = raw_input(('Are you certain you want to erase instance %s ' +
266 raise Exception('Aborting')
268 # Get the details of what we changed during installation
269 install_changes = os.path.join(instance_conf, 'install_changes')
270 with open(install_changes, 'r') as f:
271 changes = json.loads(f.read())
273 logger.info('Removing environment helpers')
274 for plugin_name in plugins['Environment Helpers']:
275 plugin = plugins['Environment Helpers'][plugin_name]
276 plugin_changes = changes['env_helper'].get(plugin_name, {})
277 if plugin.unconfigure(args, plugin_changes) == False:
278 logger.info('Removal of environment helper %s failed' % plugin_name)
280 logger.info('Removing login managers')
281 for plugin_name in plugins['Login Managers']:
282 plugin = plugins['Login Managers'][plugin_name]
283 plugin_changes = changes['login_manager'].get(plugin_name, {})
284 if plugin.unconfigure(args, plugin_changes) == False:
285 logger.info('Removal of login manager %s failed' % plugin_name)
287 logger.info('Removing Info providers')
288 for plugin_name in plugins['Info Provider']:
289 plugin = plugins['Info Provider'][plugin_name]
290 plugin_changes = changes['info_provider'].get(plugin_name, {})
291 if plugin.unconfigure(args, plugin_changes) == False:
292 logger.info('Removal of info provider %s failed' % plugin_name)
294 logger.info('Removing Authentication Providers')
295 for plugin_name in plugins['Auth Providers']:
296 plugin = plugins['Auth Providers'][plugin_name]
297 plugin_changes = changes['auth_provider'].get(plugin_name, {})
298 if plugin.unconfigure(args, plugin_changes) == False:
299 logger.info('Removal of auth provider %s failed' % plugin_name)
301 logger.info('Removing httpd configuration')
302 os.remove(httpd_conf)
303 logger.info('Erasing instance configuration')
304 shutil.rmtree(instance_conf)
305 logger.info('Erasing instance data')
306 shutil.rmtree(data_dir)
307 if cache_dir and os.path.exists(cache_dir):
308 for fn in os.listdir(cache_dir):
309 os.unlink(os.path.join(cache_dir, fn))
310 logger.info('Uninstalled instance %s' % args['instance'])
315 'Environment Helpers': EnvHelpersInstall().plugins,
316 'Login Managers': LoginMgrsInstall().plugins,
317 'Info Provider': InfoProviderInstall().plugins,
318 'Auth Providers': ProvidersInstall().plugins
323 def parse_config_profile(args):
324 config = ConfigParser.RawConfigParser()
325 files = config.read(args['config_profile'])
327 raise ConfigurationError('Config Profile file %s not found!' %
328 args['config_profile'])
330 if 'globals' in config.sections():
331 G = config.options('globals')
333 val = config.get('globals', g)
338 if k.lower() == g.lower():
342 if 'arguments' in config.sections():
343 A = config.options('arguments')
345 args[a] = config.get('arguments', a)
350 def parse_args(plugins):
351 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
352 parser.add_argument('--version',
353 action='version', version='%(prog)s 0.1')
354 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
355 help='Comma separated list of login managers')
356 parser.add_argument('--hostname',
357 help="Machine's fully qualified host name")
358 parser.add_argument('--instance', default='idp',
359 help="IdP instance name, each is a separate idp")
360 parser.add_argument('--system-user', default='ipsilon',
361 help="User account used to run the server")
362 parser.add_argument('--admin-user', default='admin',
363 help="User account that is assigned admin privileges")
364 parser.add_argument('--database-url',
365 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
366 help="The (templatized) database URL to use")
367 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
368 help="Turn on all security checks")
369 parser.add_argument('--config-profile', default=None,
370 help=argparse.SUPPRESS)
371 parser.add_argument('--server-debugging', action='store_true',
372 help="Enable debugging")
373 parser.add_argument('--uninstall', action='store_true',
374 help="Uninstall the server and all data")
375 parser.add_argument('--yes', action='store_true',
376 help="Always answer yes")
377 parser.add_argument('--admin-dburi',
378 help='Configuration database URI (override template)')
379 parser.add_argument('--users-dburi',
380 help='User configuration database URI (override '
382 parser.add_argument('--transaction-dburi',
383 help='Transaction database URI (override template)')
384 parser.add_argument('--samlsessions-dburi',
385 help='SAML 2 sessions database URI (override ' +
387 parser.add_argument('--cleanup-interval', default=30,
388 help='Interval between cleaning up stale database ' +
389 'entries (in minutes, default: 30 minutes)')
393 for plugin_group in plugins:
394 group = parser.add_argument_group(plugin_group)
395 for plugin_name in plugins[plugin_group]:
396 plugin = plugins[plugin_group][plugin_name]
397 if plugin.ptype == 'login':
398 lms.append(plugin.name)
399 plugin.install_args(group)
401 args = vars(parser.parse_args())
403 if args['config_profile']:
404 args = parse_config_profile(args)
406 if not args['hostname']:
407 args['hostname'] = socket.getfqdn()
409 if args['uninstall']:
412 if len(args['hostname'].split('.')) < 2:
413 raise ConfigurationError('Hostname: %s is not a FQDN')
415 for plugin_group in plugins:
416 for plugin_name in plugins[plugin_group]:
417 plugin = plugins[plugin_group][plugin_name]
418 plugin.validate_args(args)
421 pwd.getpwnam(args['system_user'])
423 raise ConfigurationError('User: %s not found on the system')
425 if args['lm_order'] is None:
426 args['lm_order'] = []
428 if args[name] == 'yes':
429 args['lm_order'].append(name)
431 args['lm_order'] = args['lm_order'].split(',')
433 if len(args['lm_order']) == 0 and args['ipa'] != 'yes':
434 sys.exit('No login plugins are enabled.')
436 #FIXME: check instance is only alphanums
440 if __name__ == '__main__':
444 logger.setLevel(logging.DEBUG)
447 fplugins = find_plugins()
448 opts = parse_args(fplugins)
450 logger.debug('Installation arguments:')
451 for k in sorted(opts.iterkeys()):
452 logger.debug('%s: %s', k, opts[k])
454 if 'uninstall' in opts and opts['uninstall'] is True:
455 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
456 logger.info('Instance %s could not be found' % opts['instance'])
458 uninstall(fplugins, opts)
460 install(fplugins, opts)
461 except Exception, e: # pylint: disable=broad-except
462 logger.info(str(e)) # emit message to console
463 logger.debug(e, exc_info=1) # add backtrace information to logfile
465 if 'uninstall' in opts and opts['uninstall'] is True:
466 logger.info('Uninstallation aborted.')
468 logger.info('Installation aborted.')
469 logger.info('See log file %s for details' % LOGFILE)
476 if 'uninstall' in opts and opts['uninstall'] is True:
477 logger.info('Uninstallation complete.')
479 logger.info('Installation complete.')
480 logger.info('Please restart HTTPD to enable the IdP instance.')