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 'samlsessionsdb': args['samlsessions_dburi'] or args[
112 'database_url'] % {'datadir': args['data_dir'],
113 'dbname': 'saml2sessions'},
114 'secure': "False" if args['secure'] == "no" else "True",
115 'debugging': "True" if args['server_debugging'] else "False"}
116 # Testing database sessions
117 if 'session_type' in args:
118 confopts['sesstype'] = args['session_type']
120 confopts['sesstype'] = 'file'
121 if 'session_dburi' in args:
122 confopts['sessopt'] = 'dburi'
123 confopts['sessval'] = args['session_dburi']
125 confopts['sessopt'] = 'path'
126 confopts['sessval'] = os.path.join(args['data_dir'], 'sessions')
127 # Whether to disable security (for testing)
128 if args['secure'] == 'no':
129 confopts['secure'] = "False"
130 confopts['sslrequiressl'] = ""
132 confopts['secure'] = "True"
133 confopts['sslrequiressl'] = " SSLRequireSSL"
134 if WSGI_SOCKET_PREFIX:
135 confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
137 confopts['wsgi_socket'] = ''
138 files.write_from_template(ipsilon_conf,
139 os.path.join(TEMPLATES, 'ipsilon.conf'),
141 files.write_from_template(idp_conf,
142 os.path.join(TEMPLATES, 'idp.conf'),
144 if not os.path.exists(args['httpd_conf']):
145 os.symlink(idp_conf, args['httpd_conf'])
146 if not os.path.exists(args['public_data_dir']):
147 os.makedirs(args['public_data_dir'], 0755)
148 if not os.path.exists(args['wellknown_dir']):
149 os.makedirs(args['wellknown_dir'], 0755)
150 sessdir = os.path.join(args['data_dir'], 'sessions')
151 if not os.path.exists(sessdir):
152 os.makedirs(sessdir, 0700)
153 data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
154 if not os.path.exists(data_conf):
155 os.symlink(ipsilon_conf, data_conf)
156 # Load the cherrypy config from the newly installed file so
157 # that db paths and all is properly set before configuring
159 cherrypy.config.update(ipsilon_conf)
161 # Prepare to allow plugins to save things changed during install
162 changes = {'env_helper': {},
167 # Move pre-existing admin db away
168 admin_db = cherrypy.config['admin.config.db']
169 if os.path.exists(admin_db):
170 shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
173 users_db = cherrypy.config['user.prefs.db']
174 if os.path.exists(users_db):
175 shutil.move(users_db, '%s.backup.%s' % (users_db, now))
177 db.save_user_preferences(args['admin_user'], {'is_admin': 1})
179 logger.info('Configuring environment helpers')
180 for plugin_name in plugins['Environment Helpers']:
181 plugin = plugins['Environment Helpers'][plugin_name]
183 if plugin.configure_server(args, plugin_changes) == False:
184 logger.info('Configuration of environment helper %s failed' % plugin_name)
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 logger.info('Configuration of login manager %s failed' % plugin_name)
196 changes['login_manager'][plugin_name] = plugin_changes
198 logger.info('Configuring Info provider')
199 for plugin_name in plugins['Info Provider']:
200 plugin = plugins['Info Provider'][plugin_name]
202 if plugin.configure(args, plugin_changes) == False:
203 logger.info('Configuration of info provider %s failed' % plugin_name)
204 changes['info_provider'][plugin_name] = plugin_changes
206 logger.info('Configuring Authentication Providers')
207 for plugin_name in plugins['Auth Providers']:
208 plugin = plugins['Auth Providers'][plugin_name]
210 if plugin.configure(args, plugin_changes) == False:
211 logger.info('Configuration of auth provider %s failed' % plugin_name)
212 changes['auth_provider'][plugin_name] = plugin_changes
214 # Save any changes that were made
215 install_changes = os.path.join(instance_conf, 'install_changes')
216 changes = json.dumps(changes)
217 with open(install_changes, 'w+') as f:
220 # Fixup permissions so only the ipsilon user can read these files
221 files.fix_user_dirs(instance_conf, opts['system_user'])
222 files.fix_user_dirs(args['data_dir'], opts['system_user'])
224 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
225 except Exception: # pylint: disable=broad-except
229 def uninstall(plugins, args):
230 logger.info('Uninstallation initiated')
231 instance_conf = os.path.join(CONFDIR, args['instance'])
233 httpd_conf = os.path.join(HTTPDCONFD,
234 'ipsilon-%s.conf' % args['instance'])
235 data_dir = os.path.join(DATADIR, args['instance'])
237 if not os.path.exists(instance_conf):
238 raise Exception('Could not find instance %s configuration'
240 if not os.path.exists(httpd_conf):
241 raise Exception('Could not find instance %s httpd configuration'
244 sure = raw_input(('Are you certain you want to erase instance %s ' +
248 raise Exception('Aborting')
250 # Get the details of what we changed during installation
251 install_changes = os.path.join(instance_conf, 'install_changes')
252 with open(install_changes, 'r') as f:
253 changes = json.loads(f.read())
255 logger.info('Removing environment helpers')
256 for plugin_name in plugins['Environment Helpers']:
257 plugin = plugins['Environment Helpers'][plugin_name]
258 plugin_changes = changes['env_helper'].get(plugin_name, {})
259 if plugin.unconfigure(args, plugin_changes) == False:
260 logger.info('Removal of environment helper %s failed' % plugin_name)
262 logger.info('Removing login managers')
263 for plugin_name in plugins['Login Managers']:
264 plugin = plugins['Login Managers'][plugin_name]
265 plugin_changes = changes['login_manager'].get(plugin_name, {})
266 if plugin.unconfigure(args, plugin_changes) == False:
267 logger.info('Removal of login manager %s failed' % plugin_name)
269 logger.info('Removing Info providers')
270 for plugin_name in plugins['Info Provider']:
271 plugin = plugins['Info Provider'][plugin_name]
272 plugin_changes = changes['info_provider'].get(plugin_name, {})
273 if plugin.unconfigure(args, plugin_changes) == False:
274 logger.info('Removal of info provider %s failed' % plugin_name)
276 logger.info('Removing Authentication Providers')
277 for plugin_name in plugins['Auth Providers']:
278 plugin = plugins['Auth Providers'][plugin_name]
279 plugin_changes = changes['auth_provider'].get(plugin_name, {})
280 if plugin.unconfigure(args, plugin_changes) == False:
281 logger.info('Removal of auth provider %s failed' % plugin_name)
283 logger.info('Removing httpd configuration')
284 os.remove(httpd_conf)
285 logger.info('Erasing instance configuration')
286 shutil.rmtree(instance_conf)
287 logger.info('Erasing instance data')
288 shutil.rmtree(data_dir)
289 logger.info('Uninstalled instance %s' % args['instance'])
294 'Environment Helpers': EnvHelpersInstall().plugins,
295 'Login Managers': LoginMgrsInstall().plugins,
296 'Info Provider': InfoProviderInstall().plugins,
297 'Auth Providers': ProvidersInstall().plugins
302 def parse_config_profile(args):
303 config = ConfigParser.RawConfigParser()
304 files = config.read(args['config_profile'])
306 raise ConfigurationError('Config Profile file %s not found!' %
307 args['config_profile'])
309 if 'globals' in config.sections():
310 G = config.options('globals')
312 val = config.get('globals', g)
316 for k in globals().keys():
317 if k.lower() == g.lower():
321 if 'arguments' in config.sections():
322 A = config.options('arguments')
324 args[a] = config.get('arguments', a)
329 def parse_args(plugins):
330 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
331 parser.add_argument('--version',
332 action='version', version='%(prog)s 0.1')
333 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
334 help='Comma separated list of login managers')
335 parser.add_argument('--hostname',
336 help="Machine's fully qualified host name")
337 parser.add_argument('--instance', default='idp',
338 help="IdP instance name, each is a separate idp")
339 parser.add_argument('--system-user', default='ipsilon',
340 help="User account used to run the server")
341 parser.add_argument('--admin-user', default='admin',
342 help="User account that is assigned admin privileges")
343 parser.add_argument('--database-url',
344 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
345 help="The (templatized) database URL to use")
346 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
347 help="Turn on all security checks")
348 parser.add_argument('--config-profile', default=None,
349 help=argparse.SUPPRESS)
350 parser.add_argument('--server-debugging', action='store_true',
351 help="Enable debugging")
352 parser.add_argument('--uninstall', action='store_true',
353 help="Uninstall the server and all data")
354 parser.add_argument('--yes', action='store_true',
355 help="Always answer yes")
356 parser.add_argument('--admin-dburi',
357 help='Configuration database URI (override template)')
358 parser.add_argument('--users-dburi',
359 help='User configuration database URI (override '
361 parser.add_argument('--transaction-dburi',
362 help='Transaction database URI (override template)')
363 parser.add_argument('--samlsessions-dburi',
364 help='SAML 2 sessions database URI (override template)')
368 for plugin_group in plugins:
369 group = parser.add_argument_group(plugin_group)
370 for plugin_name in plugins[plugin_group]:
371 plugin = plugins[plugin_group][plugin_name]
372 if plugin.ptype == 'login':
373 lms.append(plugin.name)
374 plugin.install_args(group)
376 args = vars(parser.parse_args())
378 if args['config_profile']:
379 args = parse_config_profile(args)
381 if not args['hostname']:
382 args['hostname'] = socket.getfqdn()
384 if args['uninstall']:
387 if len(args['hostname'].split('.')) < 2:
388 raise ConfigurationError('Hostname: %s is not a FQDN')
390 for plugin_group in plugins:
391 for plugin_name in plugins[plugin_group]:
392 plugin = plugins[plugin_group][plugin_name]
393 plugin.validate_args(args)
396 pwd.getpwnam(args['system_user'])
398 raise ConfigurationError('User: %s not found on the system')
400 if args['lm_order'] is None:
401 args['lm_order'] = []
403 if args[name] == 'yes':
404 args['lm_order'].append(name)
406 args['lm_order'] = args['lm_order'].split(',')
408 if len(args['lm_order']) == 0:
409 sys.exit('No login plugins are enabled.')
411 #FIXME: check instance is only alphanums
415 if __name__ == '__main__':
420 fplugins = find_plugins()
421 opts = parse_args(fplugins)
423 logger.setLevel(logging.DEBUG)
425 logger.debug('Installation arguments:')
426 for k in sorted(opts.iterkeys()):
427 logger.debug('%s: %s', k, opts[k])
429 if 'uninstall' in opts and opts['uninstall'] is True:
430 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
431 logger.info('Instance %s could not be found' % opts['instance'])
433 uninstall(fplugins, opts)
435 install(fplugins, opts)
436 except Exception, e: # pylint: disable=broad-except
438 if 'uninstall' in opts and opts['uninstall'] is True:
439 logger.info('Uninstallation aborted.')
441 logger.info('Installation aborted.')
442 logger.info('See log file %s for details' % LOGFILE)
449 if 'uninstall' in opts and opts['uninstall'] is True:
450 logger.info('Uninstallation complete.')
452 logger.info('Installation complete.')
453 logger.info('Please restart HTTPD to enable the IdP instance.')