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 if os.path.exists(ipsilon_conf):
97 shutil.move(ipsilon_conf, '%s.bakcup.%s' % (ipsilon_conf, now))
98 if os.path.exists(idp_conf):
99 shutil.move(idp_conf, '%s.backup.%s' % (idp_conf, now))
100 if not os.path.exists(instance_conf):
101 os.makedirs(instance_conf, 0700)
102 confopts = {'instance': args['instance'],
103 'datadir': args['data_dir'],
104 'sysuser': args['system_user'],
105 'ipsilondir': BINDIR,
106 'staticdir': STATICDIR,
107 'admindb': args['database_url'] % {
108 'datadir': args['data_dir'], 'dbname': 'adminconfig'},
109 'usersdb': args['database_url'] % {
110 'datadir': args['data_dir'], 'dbname': 'userprefs'},
111 'transdb': args['database_url'] % {
112 'datadir': args['data_dir'], 'dbname': 'transactions'},
113 'secure': "False" if args['secure'] == "no" else "True",
114 'debugging': "True" if args['server_debugging'] else "False"}
115 # Testing database sessions
116 if 'session_type' in args:
117 confopts['sesstype'] = args['session_type']
119 confopts['sesstype'] = 'file'
120 if 'session_dburi' in args:
121 confopts['sessopt'] = 'dburi'
122 confopts['sessval'] = args['session_dburi']
124 confopts['sessopt'] = 'path'
125 confopts['sessval'] = os.path.join(args['data_dir'], 'sessions')
126 # Whetehr to disable security (for testing)
127 if args['secure'] == 'no':
128 confopts['secure'] = "False"
129 confopts['sslrequiressl'] = ""
131 confopts['secure'] = "True"
132 confopts['sslrequiressl'] = " SSLRequireSSL"
133 if WSGI_SOCKET_PREFIX:
134 confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
136 confopts['wsgi_socket'] = ''
137 files.write_from_template(ipsilon_conf,
138 os.path.join(TEMPLATES, 'ipsilon.conf'),
140 files.write_from_template(idp_conf,
141 os.path.join(TEMPLATES, 'idp.conf'),
143 if not os.path.exists(args['httpd_conf']):
144 os.symlink(idp_conf, args['httpd_conf'])
145 sessdir = os.path.join(args['data_dir'], 'sessions')
146 if not os.path.exists(sessdir):
147 os.makedirs(sessdir, 0700)
148 data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
149 if not os.path.exists(data_conf):
150 os.symlink(ipsilon_conf, data_conf)
151 # Load the cherrypy config from the newly installed file so
152 # that db paths and all is properly set before configuring
154 cherrypy.config.update(ipsilon_conf)
156 # Move pre-existing admin db away
157 admin_db = cherrypy.config['admin.config.db']
158 if os.path.exists(admin_db):
159 shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
162 users_db = cherrypy.config['user.prefs.db']
163 if os.path.exists(users_db):
164 shutil.move(users_db, '%s.backup.%s' % (users_db, now))
166 db.save_user_preferences(args['admin_user'], {'is_admin': 1})
168 logger.info('Configuring environment helpers')
169 for plugin_name in plugins['Environment Helpers']:
170 plugin = plugins['Environment Helpers'][plugin_name]
171 plugin.configure_server(args)
173 logger.info('Configuring login managers')
174 for plugin_name in args['lm_order']:
175 plugin = plugins['Login Managers'][plugin_name]
176 plugin.configure(args)
178 logger.info('Configuring Info provider')
179 for plugin_name in plugins['Info Provider']:
180 plugin = plugins['Info Provider'][plugin_name]
181 plugin.configure(args)
183 logger.info('Configuring Authentication Providers')
184 for plugin_name in plugins['Auth Providers']:
185 plugin = plugins['Auth Providers'][plugin_name]
186 plugin.configure(args)
188 # Fixup permissions so only the ipsilon user can read these files
189 files.fix_user_dirs(instance_conf, opts['system_user'])
190 files.fix_user_dirs(args['data_dir'], opts['system_user'])
192 subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
193 except Exception: # pylint: disable=broad-except
196 def uninstall(plugins, args):
197 logger.info('Uninstallation initiated')
198 raise Exception('Not Implemented')
203 'Environment Helpers': EnvHelpersInstall().plugins,
204 'Login Managers': LoginMgrsInstall().plugins,
205 'Info Provider': InfoProviderInstall().plugins,
206 'Auth Providers': ProvidersInstall().plugins
211 def parse_config_profile(args):
212 config = ConfigParser.RawConfigParser()
213 files = config.read(args['config_profile'])
215 raise ConfigurationError('Config Profile file %s not found!' %
216 args['config_profile'])
218 if 'globals' in config.sections():
219 G = config.options('globals')
221 val = config.get('globals', g)
225 for k in globals().keys():
226 if k.lower() == g.lower():
230 if 'arguments' in config.sections():
231 A = config.options('arguments')
233 args[a] = config.get('arguments', a)
238 def parse_args(plugins):
239 parser = argparse.ArgumentParser(description='Ipsilon Install Options')
240 parser.add_argument('--version',
241 action='version', version='%(prog)s 0.1')
242 parser.add_argument('-o', '--login-managers-order', dest='lm_order',
243 help='Comma separated list of login managers')
244 parser.add_argument('--hostname',
245 help="Machine's fully qualified host name")
246 parser.add_argument('--instance', default='idp',
247 help="IdP instance name, each is a separate idp")
248 parser.add_argument('--system-user', default='ipsilon',
249 help="User account used to run the server")
250 parser.add_argument('--admin-user', default='admin',
251 help="User account that is assigned admin privileges")
252 parser.add_argument('--database-url',
253 default='sqlite:///%(datadir)s/%(dbname)s.sqlite',
254 help="The (templatized) database URL to use")
255 parser.add_argument('--secure', choices=['yes', 'no'], default='yes',
256 help="Turn on all security checks")
257 parser.add_argument('--config-profile', default=None,
258 help="File containing install options")
259 parser.add_argument('--server-debugging', action='store_true',
260 help="Uninstall the server and all data")
261 parser.add_argument('--uninstall', action='store_true',
262 help="Uninstall the server and all data")
266 for plugin_group in plugins:
267 group = parser.add_argument_group(plugin_group)
268 for plugin_name in plugins[plugin_group]:
269 plugin = plugins[plugin_group][plugin_name]
270 if plugin.ptype == 'login':
271 lms.append(plugin.name)
272 plugin.install_args(group)
274 args = vars(parser.parse_args())
276 if args['config_profile']:
277 args = parse_config_profile(args)
279 if not args['hostname']:
280 args['hostname'] = socket.getfqdn()
282 if len(args['hostname'].split('.')) < 2:
283 raise ConfigurationError('Hostname: %s is not a FQDN')
286 pwd.getpwnam(args['system_user'])
288 raise ConfigurationError('User: %s not found on the system')
290 if args['lm_order'] is None:
291 args['lm_order'] = []
293 if args[name] == 'yes':
294 args['lm_order'].append(name)
296 args['lm_order'] = args['lm_order'].split(',')
298 if len(args['lm_order']) == 0:
299 #force the basic pam provider if nothing else is selected
300 if 'pam' not in args:
303 args['lm_order'] = ['pam']
306 #FIXME: check instance is only alphanums
310 if __name__ == '__main__':
315 fplugins = find_plugins()
316 opts = parse_args(fplugins)
318 logger.setLevel(logging.DEBUG)
320 logger.info('Intallation arguments:')
321 for k in sorted(opts.iterkeys()):
322 logger.info('%s: %s', k, opts[k])
324 if 'uninstall' in opts and opts['uninstall'] is True:
325 uninstall(fplugins, opts)
327 install(fplugins, opts)
328 except Exception, e: # pylint: disable=broad-except
330 if 'uninstall' in opts and opts['uninstall'] is True:
331 print 'Uninstallation aborted.'
333 print 'Installation aborted.'
334 print 'See log file %s for details' % LOGFILE
338 if 'uninstall' in opts and opts['uninstall'] is True:
339 print 'Uninstallation complete.'
341 print 'Installation complete.'
342 print 'Please restart HTTPD to enable the IdP instance.'