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['database_url'] % {
113 'datadir': args['data_dir'], 'dbname': 'adminconfig'},
114 'usersdb': args['database_url'] % {
115 'datadir': args['data_dir'], 'dbname': 'userprefs'},
116 'transdb': 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",
121 # Testing database sessions
122 if 'session_type' in args:
123 confopts['sesstype'] = args['session_type']
125 confopts['sesstype'] = 'file'
126 if 'session_dburi' in args:
127 confopts['sessopt'] = 'dburi'
128 confopts['sessval'] = args['session_dburi']
130 confopts['sessopt'] = 'path'
131 confopts['sessval'] = os.path.join(args['data_dir'], 'sessions')
132 # Whetehr to disable security (for testing)
133 if args['secure'] == 'no':
134 confopts['secure'] = "False"
135 confopts['sslrequiressl'] = ""
137 confopts['secure'] = "True"
138 confopts['sslrequiressl'] = " SSLRequireSSL"
139 if WSGI_SOCKET_PREFIX:
140 confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
142 confopts['wsgi_socket'] = ''
143 files.write_from_template(ipsilon_conf,
144 os.path.join(TEMPLATES, 'ipsilon.conf'),
146 files.write_from_template(idp_conf,
147 os.path.join(TEMPLATES, 'idp.conf'),
149 if not os.path.exists(args['httpd_conf']):
150 os.symlink(idp_conf, args['httpd_conf'])
151 if not os.path.exists(args['public_data_dir']):
152 os.makedirs(args['public_data_dir'], 0755)
153 if not os.path.exists(args['wellknown_dir']):
154 os.makedirs(args['wellknown_dir'], 0755)
155 sessdir = os.path.join(args['data_dir'], 'sessions')
156 if not os.path.exists(sessdir):
157 os.makedirs(sessdir, 0700)
158 data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
159 if not os.path.exists(data_conf):
160 os.symlink(ipsilon_conf, data_conf)
161 # Load the cherrypy config from the newly installed file so
162 # that db paths and all is properly set before configuring
164 cherrypy.config.update(ipsilon_conf)
166 # Move pre-existing admin db 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))
172 users_db = cherrypy.config['user.prefs.db']
173 if os.path.exists(users_db):
174 shutil.move(users_db, '%s.backup.%s' % (users_db, now))
176 db.save_user_preferences(args['admin_user'], {'is_admin': 1})
178 logger.info('Configuring environment helpers')
179 for plugin_name in plugins['Environment Helpers']:
180 plugin = plugins['Environment Helpers'][plugin_name]
181 if plugin.configure_server(args) == False:
182 print 'Configuration of environment helper %s failed' % plugin_name
184 logger.info('Configuring login managers')
185 for plugin_name in args['lm_order']:
186 plugin = plugins['Login Managers'][plugin_name]
187 if plugin.configure(args) == False:
188 print 'Configuration of login manager %s failed' % plugin_name
190 logger.info('Configuring Info provider')
191 for plugin_name in plugins['Info Provider']:
192 plugin = plugins['Info Provider'][plugin_name]
193 if plugin.configure(args) == False:
194 print 'Configuration of info provider %s failed' % plugin_name
196 logger.info('Configuring Authentication Providers')
197 for plugin_name in plugins['Auth Providers']:
198 plugin = plugins['Auth Providers'][plugin_name]
199 if plugin.configure(args) == False:
200 print 'Configuration of auth provider %s failed' % plugin_name
202 # Fixup permissions so only the ipsilon user can read these files
203 files.fix_user_dirs(instance_conf, opts['system_user'])
204 files.fix_user_dirs(args['data_dir'], opts['system_user'])
206 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
207 except Exception: # pylint: disable=broad-except
211 def uninstall(plugins, args):
212 logger.info('Uninstallation initiated')
213 instance_conf = os.path.join(CONFDIR, args['instance'])
215 httpd_conf = os.path.join(HTTPDCONFD,
216 'ipsilon-%s.conf' % args['instance'])
217 data_dir = os.path.join(DATADIR, args['instance'])
219 if not os.path.exists(instance_conf):
220 raise Exception('Could not find instance %s configuration'
222 if not os.path.exists(httpd_conf):
223 raise Exception('Could not find instance %s httpd configuration'
226 sure = raw_input(('Are you certain you want to erase instance %s ' +
230 raise Exception('Aborting')
232 logger.info('Removing environment helpers')
233 for plugin_name in plugins['Environment Helpers']:
234 plugin = plugins['Environment Helpers'][plugin_name]
235 if plugin.unconfigure(args) == False:
236 print 'Removal of environment helper %s failed' % plugin_name
238 logger.info('Removing login managers')
239 for plugin_name in args['lm_order']:
240 plugin = plugins['Login Managers'][plugin_name]
241 if plugin.unconfigure(args) == False:
242 print 'Removal of login manager %s failed' % plugin_name
244 logger.info('Removing Info providers')
245 for plugin_name in plugins['Info Provider']:
246 plugin = plugins['Info Provider'][plugin_name]
247 if plugin.unconfigure(args) == False:
248 print 'Removal of info provider %s failed' % plugin_name
250 logger.info('Removing Authentication Providers')
251 for plugin_name in plugins['Auth Providers']:
252 plugin = plugins['Auth Providers'][plugin_name]
253 if plugin.unconfigure(args) == False:
254 print 'Removal of auth provider %s failed' % plugin_name
256 logger.info('Removing httpd configuration')
257 os.remove(httpd_conf)
258 logger.info('Erasing instance configuration')
259 shutil.rmtree(instance_conf)
260 logger.info('Erasing instance data')
261 shutil.rmtree(data_dir)
262 logger.info('Uninstalled instance %s' % args['instance'])
267 'Environment Helpers': EnvHelpersInstall().plugins,
268 'Login Managers': LoginMgrsInstall().plugins,
269 'Info Provider': InfoProviderInstall().plugins,
270 'Auth Providers': ProvidersInstall().plugins
275 def parse_config_profile(args):
276 config = ConfigParser.RawConfigParser()
277 files = config.read(args['config_profile'])
279 raise ConfigurationError('Config Profile file %s not found!' %
280 args['config_profile'])
282 if 'globals' in config.sections():
283 G = config.options('globals')
285 val = config.get('globals', g)
289 for k in globals().keys():
290 if k.lower() == g.lower():
294 if 'arguments' in config.sections():
295 A = config.options('arguments')
297 args[a] = config.get('arguments', a)
302 def parse_args(plugins):
303 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
304 parser.add_argument('--version',
305 action='version', version='%(prog)s 0.1')
306 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
307 help='Comma separated list of login managers')
308 parser.add_argument('--hostname',
309 help="Machine's fully qualified host name")
310 parser.add_argument('--instance', default='idp',
311 help="IdP instance name, each is a separate idp")
312 parser.add_argument('--system-user', default='ipsilon',
313 help="User account used to run the server")
314 parser.add_argument('--admin-user', default='admin',
315 help="User account that is assigned admin privileges")
316 parser.add_argument('--database-url',
317 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
318 help="The (templatized) database URL to use")
319 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
320 help="Turn on all security checks")
321 parser.add_argument('--config-profile', default=None,
322 help="File containing install options")
323 parser.add_argument('--server-debugging', action='store_true',
324 help="Enable debugging")
325 parser.add_argument('--uninstall', action='store_true',
326 help="Uninstall the server and all data")
327 parser.add_argument('--yes', action='store_true',
328 help="Always answer yes")
332 for plugin_group in plugins:
333 group = parser.add_argument_group(plugin_group)
334 for plugin_name in plugins[plugin_group]:
335 plugin = plugins[plugin_group][plugin_name]
336 if plugin.ptype == 'login':
337 lms.append(plugin.name)
338 plugin.install_args(group)
340 args = vars(parser.parse_args())
342 if args['config_profile']:
343 args = parse_config_profile(args)
345 if not args['hostname']:
346 args['hostname'] = socket.getfqdn()
348 if len(args['hostname'].split('.')) < 2:
349 raise ConfigurationError('Hostname: %s is not a FQDN')
351 for plugin_group in plugins:
352 for plugin_name in plugins[plugin_group]:
353 plugin = plugins[plugin_group][plugin_name]
354 plugin.validate_args(args)
357 pwd.getpwnam(args['system_user'])
359 raise ConfigurationError('User: %s not found on the system')
361 if args['lm_order'] is None:
362 args['lm_order'] = []
364 if args[name] == 'yes':
365 args['lm_order'].append(name)
367 args['lm_order'] = args['lm_order'].split(',')
369 if len(args['lm_order']) == 0:
370 #force the basic pam provider if nothing else is selected
371 if 'pam' not in args:
374 args['lm_order'] = ['pam']
377 #FIXME: check instance is only alphanums
381 if __name__ == '__main__':
386 fplugins = find_plugins()
387 opts = parse_args(fplugins)
389 logger.setLevel(logging.DEBUG)
391 logger.info('Intallation arguments:')
392 for k in sorted(opts.iterkeys()):
393 logger.info('%s: %s', k, opts[k])
395 if 'uninstall' in opts and opts['uninstall'] is True:
396 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
397 print 'Instance %s could not be found' % opts['instance']
399 uninstall(fplugins, opts)
401 install(fplugins, opts)
402 except Exception, e: # pylint: disable=broad-except
404 if 'uninstall' in opts and opts['uninstall'] is True:
405 print 'Uninstallation aborted.'
407 print 'Installation aborted.'
408 print 'See log file %s for details' % LOGFILE
412 if 'uninstall' in opts and opts['uninstall'] is True:
413 print 'Uninstallation complete.'
415 print 'Installation complete.'
416 print 'Please restart HTTPD to enable the IdP instance.'