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"}
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 # Whetehr 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")
331 for plugin_group in plugins:
332 group = parser.add_argument_group(plugin_group)
333 for plugin_name in plugins[plugin_group]:
334 plugin = plugins[plugin_group][plugin_name]
335 if plugin.ptype == 'login':
336 lms.append(plugin.name)
337 plugin.install_args(group)
339 args = vars(parser.parse_args())
341 if args['config_profile']:
342 args = parse_config_profile(args)
344 if not args['hostname']:
345 args['hostname'] = socket.getfqdn()
347 if len(args['hostname'].split('.')) < 2:
348 raise ConfigurationError('Hostname: %s is not a FQDN')
351 pwd.getpwnam(args['system_user'])
353 raise ConfigurationError('User: %s not found on the system')
355 if args['lm_order'] is None:
356 args['lm_order'] = []
358 if args[name] == 'yes':
359 args['lm_order'].append(name)
361 args['lm_order'] = args['lm_order'].split(',')
363 if len(args['lm_order']) == 0:
364 #force the basic pam provider if nothing else is selected
365 if 'pam' not in args:
368 args['lm_order'] = ['pam']
371 #FIXME: check instance is only alphanums
375 if __name__ == '__main__':
380 fplugins = find_plugins()
381 opts = parse_args(fplugins)
383 logger.setLevel(logging.DEBUG)
385 logger.info('Intallation arguments:')
386 for k in sorted(opts.iterkeys()):
387 logger.info('%s: %s', k, opts[k])
389 if 'uninstall' in opts and opts['uninstall'] is True:
390 if not os.path.exists(os.path.join(CONFDIR, opts['instance'])):
391 print 'Instance %s could not be found' % opts['instance']
393 uninstall(fplugins, opts)
395 install(fplugins, opts)
396 except Exception, e: # pylint: disable=broad-except
398 if 'uninstall' in opts and opts['uninstall'] is True:
399 print 'Uninstallation aborted.'
401 print 'Installation aborted.'
402 print 'See log file %s for details' % LOGFILE
406 if 'uninstall' in opts and opts['uninstall'] is True:
407 print 'Uninstallation complete.'
409 print 'Installation complete.'
410 print 'Please restart HTTPD to enable the IdP instance.'