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
209 def uninstall(plugins, args):
210 logger.info('Uninstallation initiated')
211 raise Exception('Not Implemented')
216 'Environment Helpers': EnvHelpersInstall().plugins,
217 'Login Managers': LoginMgrsInstall().plugins,
218 'Info Provider': InfoProviderInstall().plugins,
219 'Auth Providers': ProvidersInstall().plugins
224 def parse_config_profile(args):
225 config = ConfigParser.RawConfigParser()
226 files = config.read(args['config_profile'])
228 raise ConfigurationError('Config Profile file %s not found!' %
229 args['config_profile'])
231 if 'globals' in config.sections():
232 G = config.options('globals')
234 val = config.get('globals', g)
238 for k in globals().keys():
239 if k.lower() == g.lower():
243 if 'arguments' in config.sections():
244 A = config.options('arguments')
246 args[a] = config.get('arguments', a)
251 def parse_args(plugins):
252 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
253 parser.add_argument('--version',
254 action='version', version='%(prog)s 0.1')
255 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
256 help='Comma separated list of login managers')
257 parser.add_argument('--hostname',
258 help="Machine's fully qualified host name")
259 parser.add_argument('--instance', default='idp',
260 help="IdP instance name, each is a separate idp")
261 parser.add_argument('--system-user', default='ipsilon',
262 help="User account used to run the server")
263 parser.add_argument('--admin-user', default='admin',
264 help="User account that is assigned admin privileges")
265 parser.add_argument('--database-url',
266 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
267 help="The (templatized) database URL to use")
268 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
269 help="Turn on all security checks")
270 parser.add_argument('--config-profile', default=None,
271 help="File containing install options")
272 parser.add_argument('--server-debugging', action='store_true',
273 help="Enable debugging")
274 parser.add_argument('--uninstall', action='store_true',
275 help="Uninstall the server and all data")
279 for plugin_group in plugins:
280 group = parser.add_argument_group(plugin_group)
281 for plugin_name in plugins[plugin_group]:
282 plugin = plugins[plugin_group][plugin_name]
283 if plugin.ptype == 'login':
284 lms.append(plugin.name)
285 plugin.install_args(group)
287 args = vars(parser.parse_args())
289 if args['config_profile']:
290 args = parse_config_profile(args)
292 if not args['hostname']:
293 args['hostname'] = socket.getfqdn()
295 if len(args['hostname'].split('.')) < 2:
296 raise ConfigurationError('Hostname: %s is not a FQDN')
299 pwd.getpwnam(args['system_user'])
301 raise ConfigurationError('User: %s not found on the system')
303 if args['lm_order'] is None:
304 args['lm_order'] = []
306 if args[name] == 'yes':
307 args['lm_order'].append(name)
309 args['lm_order'] = args['lm_order'].split(',')
311 if len(args['lm_order']) == 0:
312 #force the basic pam provider if nothing else is selected
313 if 'pam' not in args:
316 args['lm_order'] = ['pam']
319 #FIXME: check instance is only alphanums
323 if __name__ == '__main__':
328 fplugins = find_plugins()
329 opts = parse_args(fplugins)
331 logger.setLevel(logging.DEBUG)
333 logger.info('Intallation arguments:')
334 for k in sorted(opts.iterkeys()):
335 logger.info('%s: %s', k, opts[k])
337 if 'uninstall' in opts and opts['uninstall'] is True:
338 uninstall(fplugins, opts)
340 install(fplugins, opts)
341 except Exception, e: # pylint: disable=broad-except
343 if 'uninstall' in opts and opts['uninstall'] is True:
344 print 'Uninstallation aborted.'
346 print 'Installation aborted.'
347 print 'See log file %s for details' % LOGFILE
351 if 'uninstall' in opts and opts['uninstall'] is True:
352 print 'Uninstallation complete.'
354 print 'Installation complete.'
355 print 'Please restart HTTPD to enable the IdP instance.'