3 # Copyright (C) 2014 Simo Sorce <simo@redhat.com>
5 # see file 'COPYING' for use and warranty information
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from ipsilon.login.common import LoginMgrsInstall
21 from ipsilon.info.common import InfoProviderInstall
22 from ipsilon.providers.common import ProvidersInstall
23 from ipsilon.helpers.common import EnvHelpersInstall
24 from ipsilon.util.data import UserStore
25 from ipsilon.tools import files
39 TEMPLATES = '/usr/share/ipsilon/templates/install'
40 CONFDIR = '/etc/ipsilon'
41 DATADIR = '/var/lib/ipsilon'
42 HTTPDCONFD = '/etc/httpd/conf.d'
44 STATICDIR = '/usr/share/ipsilon'
45 WSGI_SOCKET_PREFIX = None
48 class ConfigurationError(Exception):
50 def __init__(self, message):
51 super(ConfigurationError, self).__init__(message)
52 self.message = message
55 return repr(self.message)
58 #Silence cherrypy logging to screen
59 cherrypy.log.screen = False
62 LOGFILE = '/var/log/ipsilon-install.log'
63 logger = logging.getLogger()
67 global logger # pylint: disable=W0603
68 if os.path.isfile(LOGFILE):
70 created = '%s' % time.ctime(os.path.getctime(LOGFILE))
71 shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created))
74 logger = logging.getLogger()
76 lh = logging.FileHandler(LOGFILE)
78 print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e))
79 lh = logging.StreamHandler(sys.stderr)
80 formatter = logging.Formatter('[%(asctime)s] %(message)s')
81 lh.setFormatter(formatter)
85 def install(plugins, args):
86 logger.info('Installation initiated')
87 now = time.strftime("%Y%m%d%H%M%S", time.gmtime())
88 instance_conf = os.path.join(CONFDIR, args['instance'])
90 logger.info('Installing default config files')
91 ipsilon_conf = os.path.join(instance_conf, 'ipsilon.conf')
92 idp_conf = os.path.join(instance_conf, 'idp.conf')
93 args['httpd_conf'] = os.path.join(HTTPDCONFD,
94 'ipsilon-%s.conf' % args['instance'])
95 args['data_dir'] = os.path.join(DATADIR, args['instance'])
96 args['public_data_dir'] = os.path.join(args['data_dir'], 'public')
97 args['wellknown_dir'] = os.path.join(args['public_data_dir'],
99 if os.path.exists(ipsilon_conf):
100 shutil.move(ipsilon_conf, '%s.bakcup.%s' % (ipsilon_conf, now))
101 if os.path.exists(idp_conf):
102 shutil.move(idp_conf, '%s.backup.%s' % (idp_conf, now))
103 if not os.path.exists(instance_conf):
104 os.makedirs(instance_conf, 0700)
105 confopts = {'instance': args['instance'],
106 'datadir': args['data_dir'],
107 'publicdatadir': args['public_data_dir'],
108 'wellknowndir': args['wellknown_dir'],
109 'sysuser': args['system_user'],
110 'ipsilondir': BINDIR,
111 'staticdir': STATICDIR,
112 'admindb': args['admin_dburi'] or args['database_url'] % {
113 'datadir': args['data_dir'], 'dbname': 'adminconfig'},
114 'usersdb': args['users_dburi'] or args['database_url'] % {
115 'datadir': args['data_dir'], 'dbname': 'userprefs'},
116 'transdb': args['transaction_dburi'] or args['database_url'] %
117 {'datadir': args['data_dir'], 'dbname': 'transactions'},
118 'secure': "False" if args['secure'] == "no" else "True",
119 'debugging': "True" if args['server_debugging'] else "False"}
120 # Testing database sessions
121 if 'session_type' in args:
122 confopts['sesstype'] = args['session_type']
124 confopts['sesstype'] = 'file'
125 if 'session_dburi' in args:
126 confopts['sessopt'] = 'dburi'
127 confopts['sessval'] = args['session_dburi']
129 confopts['sessopt'] = 'path'
130 confopts['sessval'] = os.path.join(args['data_dir'], 'sessions')
131 # Whether to disable security (for testing)
132 if args['secure'] == 'no':
133 confopts['secure'] = "False"
134 confopts['sslrequiressl'] = ""
136 confopts['secure'] = "True"
137 confopts['sslrequiressl'] = " SSLRequireSSL"
138 if WSGI_SOCKET_PREFIX:
139 confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
141 confopts['wsgi_socket'] = ''
142 files.write_from_template(ipsilon_conf,
143 os.path.join(TEMPLATES, 'ipsilon.conf'),
145 files.write_from_template(idp_conf,
146 os.path.join(TEMPLATES, 'idp.conf'),
148 if not os.path.exists(args['httpd_conf']):
149 os.symlink(idp_conf, args['httpd_conf'])
150 if not os.path.exists(args['public_data_dir']):
151 os.makedirs(args['public_data_dir'], 0755)
152 if not os.path.exists(args['wellknown_dir']):
153 os.makedirs(args['wellknown_dir'], 0755)
154 sessdir = os.path.join(args['data_dir'], 'sessions')
155 if not os.path.exists(sessdir):
156 os.makedirs(sessdir, 0700)
157 data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
158 if not os.path.exists(data_conf):
159 os.symlink(ipsilon_conf, data_conf)
160 # Load the cherrypy config from the newly installed file so
161 # that db paths and all is properly set before configuring
163 cherrypy.config.update(ipsilon_conf)
165 # Move pre-existing admin db 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))
171 users_db = cherrypy.config['user.prefs.db']
172 if os.path.exists(users_db):
173 shutil.move(users_db, '%s.backup.%s' % (users_db, now))
175 db.save_user_preferences(args['admin_user'], {'is_admin': 1})
177 logger.info('Configuring environment helpers')
178 for plugin_name in plugins['Environment Helpers']:
179 plugin = plugins['Environment Helpers'][plugin_name]
180 if plugin.configure_server(args) == False:
181 print 'Configuration of environment helper %s failed' % plugin_name
183 logger.info('Configuring login managers')
184 for plugin_name in args['lm_order']:
186 plugin = plugins['Login Managers'][plugin_name]
188 sys.exit('Login provider %s not installed' % plugin_name)
189 if plugin.configure(args) == False:
190 print 'Configuration of login manager %s failed' % plugin_name
192 logger.info('Configuring Info provider')
193 for plugin_name in plugins['Info Provider']:
194 plugin = plugins['Info Provider'][plugin_name]
195 if plugin.configure(args) == False:
196 print 'Configuration of info provider %s failed' % plugin_name
198 logger.info('Configuring Authentication Providers')
199 for plugin_name in plugins['Auth Providers']:
200 plugin = plugins['Auth Providers'][plugin_name]
201 if plugin.configure(args) == False:
202 print 'Configuration of auth provider %s failed' % plugin_name
204 # Fixup permissions so only the ipsilon user can read these files
205 files.fix_user_dirs(instance_conf, opts['system_user'])
206 files.fix_user_dirs(args['data_dir'], opts['system_user'])
208 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
209 except Exception: # pylint: disable=broad-except
213 def uninstall(plugins, args):
214 logger.info('Uninstallation initiated')
215 instance_conf = os.path.join(CONFDIR, args['instance'])
217 httpd_conf = os.path.join(HTTPDCONFD,
218 'ipsilon-%s.conf' % args['instance'])
219 data_dir = os.path.join(DATADIR, args['instance'])
221 if not os.path.exists(instance_conf):
222 raise Exception('Could not find instance %s configuration'
224 if not os.path.exists(httpd_conf):
225 raise Exception('Could not find instance %s httpd configuration'
228 sure = raw_input(('Are you certain you want to erase instance %s ' +
232 raise Exception('Aborting')
234 logger.info('Removing environment helpers')
235 for plugin_name in plugins['Environment Helpers']:
236 plugin = plugins['Environment Helpers'][plugin_name]
237 if plugin.unconfigure(args) == False:
238 print 'Removal of environment helper %s failed' % plugin_name
240 logger.info('Removing login managers')
241 for plugin_name in plugins['Login Managers']:
242 plugin = plugins['Login Managers'][plugin_name]
243 if plugin.unconfigure(args) == False:
244 print 'Removal of login manager %s failed' % plugin_name
246 logger.info('Removing Info providers')
247 for plugin_name in plugins['Info Provider']:
248 plugin = plugins['Info Provider'][plugin_name]
249 if plugin.unconfigure(args) == False:
250 print 'Removal of info provider %s failed' % plugin_name
252 logger.info('Removing Authentication Providers')
253 for plugin_name in plugins['Auth Providers']:
254 plugin = plugins['Auth Providers'][plugin_name]
255 if plugin.unconfigure(args) == False:
256 print 'Removal of auth provider %s failed' % plugin_name
258 logger.info('Removing httpd configuration')
259 os.remove(httpd_conf)
260 logger.info('Erasing instance configuration')
261 shutil.rmtree(instance_conf)
262 logger.info('Erasing instance data')
263 shutil.rmtree(data_dir)
264 logger.info('Uninstalled instance %s' % args['instance'])
269 'Environment Helpers': EnvHelpersInstall().plugins,
270 'Login Managers': LoginMgrsInstall().plugins,
271 'Info Provider': InfoProviderInstall().plugins,
272 'Auth Providers': ProvidersInstall().plugins
277 def parse_config_profile(args):
278 config = ConfigParser.RawConfigParser()
279 files = config.read(args['config_profile'])
281 raise ConfigurationError('Config Profile file %s not found!' %
282 args['config_profile'])
284 if 'globals' in config.sections():
285 G = config.options('globals')
287 val = config.get('globals', g)
291 for k in globals().keys():
292 if k.lower() == g.lower():
296 if 'arguments' in config.sections():
297 A = config.options('arguments')
299 args[a] = config.get('arguments', a)
304 def parse_args(plugins):
305 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
306 parser.add_argument('--version',
307 action='version', version='%(prog)s 0.1')
308 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
309 help='Comma separated list of login managers')
310 parser.add_argument('--hostname',
311 help="Machine's fully qualified host name")
312 parser.add_argument('--instance', default='idp',
313 help="IdP instance name, each is a separate idp")
314 parser.add_argument('--system-user', default='ipsilon',
315 help="User account used to run the server")
316 parser.add_argument('--admin-user', default='admin',
317 help="User account that is assigned admin privileges")
318 parser.add_argument('--database-url',
319 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
320 help="The (templatized) database URL to use")
321 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
322 help="Turn on all security checks")
323 parser.add_argument('--config-profile', default=None,
324 help=argparse.SUPPRESS)
325 parser.add_argument('--server-debugging', action='store_true',
326 help="Enable debugging")
327 parser.add_argument('--uninstall', action='store_true',
328 help="Uninstall the server and all data")
329 parser.add_argument('--yes', action='store_true',
330 help="Always answer yes")
331 parser.add_argument('--admin-dburi',
332 help='Configuration database URI (override template)')
333 parser.add_argument('--users-dburi',
334 help='User configuration database URI (override '
336 parser.add_argument('--transaction-dburi',
337 help='Transaction database URI (override template)')
341 for plugin_group in plugins:
342 group = parser.add_argument_group(plugin_group)
343 for plugin_name in plugins[plugin_group]:
344 plugin = plugins[plugin_group][plugin_name]
345 if plugin.ptype == 'login':
346 lms.append(plugin.name)
347 plugin.install_args(group)
349 args = vars(parser.parse_args())
351 if args['config_profile']:
352 args = parse_config_profile(args)
354 if not args['hostname']:
355 args['hostname'] = socket.getfqdn()
357 if args['uninstall']:
360 if len(args['hostname'].split('.')) < 2:
361 raise ConfigurationError('Hostname: %s is not a FQDN')
363 for plugin_group in plugins:
364 for plugin_name in plugins[plugin_group]:
365 plugin = plugins[plugin_group][plugin_name]
366 plugin.validate_args(args)
369 pwd.getpwnam(args['system_user'])
371 raise ConfigurationError('User: %s not found on the system')
373 if args['lm_order'] is None:
374 args['lm_order'] = []
376 if args[name] == 'yes':
377 args['lm_order'].append(name)
379 args['lm_order'] = args['lm_order'].split(',')
381 if len(args['lm_order']) == 0:
382 sys.exit('No login plugins are enabled.')
384 #FIXME: check instance is only alphanums
388 if __name__ == '__main__':
393 fplugins = find_plugins()
394 opts = parse_args(fplugins)
396 logger.setLevel(logging.DEBUG)
398 logger.info('Intallation arguments:')
399 for k in sorted(opts.iterkeys()):
400 logger.info('%s: %s', k, opts[k])
402 if 'uninstall' in opts and opts['uninstall'] is True:
403 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
404 print 'Instance %s could not be found' % opts['instance']
406 uninstall(fplugins, opts)
408 install(fplugins, opts)
409 except Exception, e: # pylint: disable=broad-except
411 if 'uninstall' in opts and opts['uninstall'] is True:
412 print 'Uninstallation aborted.'
414 print 'Installation aborted.'
415 print 'See log file %s for details' % LOGFILE
422 if 'uninstall' in opts and opts['uninstall'] is True:
423 print 'Uninstallation complete.'
425 print 'Installation complete.'
426 print 'Please restart HTTPD to enable the IdP instance.'