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']:
185 plugin = plugins['Login Managers'][plugin_name]
186 if plugin.configure(args) == False:
187 print 'Configuration of login manager %s failed' % plugin_name
189 logger.info('Configuring Info provider')
190 for plugin_name in plugins['Info Provider']:
191 plugin = plugins['Info Provider'][plugin_name]
192 if plugin.configure(args) == False:
193 print 'Configuration of info provider %s failed' % plugin_name
195 logger.info('Configuring Authentication Providers')
196 for plugin_name in plugins['Auth Providers']:
197 plugin = plugins['Auth Providers'][plugin_name]
198 if plugin.configure(args) == False:
199 print 'Configuration of auth provider %s failed' % plugin_name
201 # Fixup permissions so only the ipsilon user can read these files
202 files.fix_user_dirs(instance_conf, opts['system_user'])
203 files.fix_user_dirs(args['data_dir'], opts['system_user'])
205 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
206 except Exception: # pylint: disable=broad-except
210 def uninstall(plugins, args):
211 logger.info('Uninstallation initiated')
212 instance_conf = os.path.join(CONFDIR, args['instance'])
214 httpd_conf = os.path.join(HTTPDCONFD,
215 'ipsilon-%s.conf' % args['instance'])
216 data_dir = os.path.join(DATADIR, args['instance'])
218 if not os.path.exists(instance_conf):
219 raise Exception('Could not find instance %s configuration'
221 if not os.path.exists(httpd_conf):
222 raise Exception('Could not find instance %s httpd configuration'
225 sure = raw_input(('Are you certain you want to erase instance %s ' +
229 raise Exception('Aborting')
231 logger.info('Removing environment helpers')
232 for plugin_name in plugins['Environment Helpers']:
233 plugin = plugins['Environment Helpers'][plugin_name]
234 if plugin.unconfigure(args) == False:
235 print 'Removal of environment helper %s failed' % plugin_name
237 logger.info('Removing login managers')
238 for plugin_name in args['lm_order']:
239 plugin = plugins['Login Managers'][plugin_name]
240 if plugin.unconfigure(args) == False:
241 print 'Removal of login manager %s failed' % plugin_name
243 logger.info('Removing Info providers')
244 for plugin_name in plugins['Info Provider']:
245 plugin = plugins['Info Provider'][plugin_name]
246 if plugin.unconfigure(args) == False:
247 print 'Removal of info provider %s failed' % plugin_name
249 logger.info('Removing Authentication Providers')
250 for plugin_name in plugins['Auth Providers']:
251 plugin = plugins['Auth Providers'][plugin_name]
252 if plugin.unconfigure(args) == False:
253 print 'Removal of auth provider %s failed' % plugin_name
255 logger.info('Removing httpd configuration')
256 os.remove(httpd_conf)
257 logger.info('Erasing instance configuration')
258 shutil.rmtree(instance_conf)
259 logger.info('Erasing instance data')
260 shutil.rmtree(data_dir)
261 logger.info('Uninstalled instance %s' % args['instance'])
266 'Environment Helpers': EnvHelpersInstall().plugins,
267 'Login Managers': LoginMgrsInstall().plugins,
268 'Info Provider': InfoProviderInstall().plugins,
269 'Auth Providers': ProvidersInstall().plugins
274 def parse_config_profile(args):
275 config = ConfigParser.RawConfigParser()
276 files = config.read(args['config_profile'])
278 raise ConfigurationError('Config Profile file %s not found!' %
279 args['config_profile'])
281 if 'globals' in config.sections():
282 G = config.options('globals')
284 val = config.get('globals', g)
288 for k in globals().keys():
289 if k.lower() == g.lower():
293 if 'arguments' in config.sections():
294 A = config.options('arguments')
296 args[a] = config.get('arguments', a)
301 def parse_args(plugins):
302 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
303 parser.add_argument('--version',
304 action='version', version='%(prog)s 0.1')
305 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
306 help='Comma separated list of login managers')
307 parser.add_argument('--hostname',
308 help="Machine's fully qualified host name")
309 parser.add_argument('--instance', default='idp',
310 help="IdP instance name, each is a separate idp")
311 parser.add_argument('--system-user', default='ipsilon',
312 help="User account used to run the server")
313 parser.add_argument('--admin-user', default='admin',
314 help="User account that is assigned admin privileges")
315 parser.add_argument('--database-url',
316 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
317 help="The (templatized) database URL to use")
318 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
319 help="Turn on all security checks")
320 parser.add_argument('--config-profile', default=None,
321 help="File containing install options")
322 parser.add_argument('--server-debugging', action='store_true',
323 help="Enable debugging")
324 parser.add_argument('--uninstall', action='store_true',
325 help="Uninstall the server and all data")
326 parser.add_argument('--yes', action='store_true',
327 help="Always answer yes")
328 parser.add_argument('--admin-dburi',
329 help='Configuration database URI (override template)')
330 parser.add_argument('--users-dburi',
331 help='User configuration database URI (override '
333 parser.add_argument('--transaction-dburi',
334 help='Transaction database URI (override template)')
338 for plugin_group in plugins:
339 group = parser.add_argument_group(plugin_group)
340 for plugin_name in plugins[plugin_group]:
341 plugin = plugins[plugin_group][plugin_name]
342 if plugin.ptype == 'login':
343 lms.append(plugin.name)
344 plugin.install_args(group)
346 args = vars(parser.parse_args())
348 if args['config_profile']:
349 args = parse_config_profile(args)
351 if not args['hostname']:
352 args['hostname'] = socket.getfqdn()
354 if len(args['hostname'].split('.')) < 2:
355 raise ConfigurationError('Hostname: %s is not a FQDN')
357 for plugin_group in plugins:
358 for plugin_name in plugins[plugin_group]:
359 plugin = plugins[plugin_group][plugin_name]
360 plugin.validate_args(args)
363 pwd.getpwnam(args['system_user'])
365 raise ConfigurationError('User: %s not found on the system')
367 if args['lm_order'] is None:
368 args['lm_order'] = []
370 if args[name] == 'yes':
371 args['lm_order'].append(name)
373 args['lm_order'] = args['lm_order'].split(',')
375 if len(args['lm_order']) == 0:
376 #force the basic pam provider if nothing else is selected
377 if 'pam' not in args:
380 args['lm_order'] = ['pam']
383 #FIXME: check instance is only alphanums
387 if __name__ == '__main__':
392 fplugins = find_plugins()
393 opts = parse_args(fplugins)
395 logger.setLevel(logging.DEBUG)
397 logger.info('Intallation arguments:')
398 for k in sorted(opts.iterkeys()):
399 logger.info('%s: %s', k, opts[k])
401 if 'uninstall' in opts and opts['uninstall'] is True:
402 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
403 print 'Instance %s could not be found' % opts['instance']
405 uninstall(fplugins, opts)
407 install(fplugins, opts)
408 except Exception, e: # pylint: disable=broad-except
410 if 'uninstall' in opts and opts['uninstall'] is True:
411 print 'Uninstallation aborted.'
413 print 'Installation aborted.'
414 print 'See log file %s for details' % LOGFILE
418 if 'uninstall' in opts and opts['uninstall'] is True:
419 print 'Uninstallation complete.'
421 print 'Installation complete.'
422 print 'Please restart HTTPD to enable the IdP instance.'