Additional parametrization of template files
[cascardo/ipsilon.git] / ipsilon / install / ipsilon-server-install
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2014  Simo Sorce <simo@redhat.com>
4 #
5 # see file 'COPYING' for use and warranty information
6 #
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.
11 #
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.
16 #
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/>.
19
20 from ipsilon.login.common import LoginMgrsInstall
21 from ipsilon.providers.common import ProvidersInstall
22 from ipsilon.helpers.common import EnvHelpersInstall
23 from ipsilon.util.data import Store
24 from ipsilon.tools import files
25 import ConfigParser
26 import argparse
27 import cherrypy
28 import logging
29 import os
30 import pwd
31 import shutil
32 import socket
33 import subprocess
34 import sys
35 import time
36
37
38 TEMPLATES = '/usr/share/ipsilon/templates/install'
39 CONFDIR = '/etc/ipsilon'
40 DATADIR = '/var/lib/ipsilon'
41 HTTPDCONFD = '/etc/httpd/conf.d'
42 BINDIR = '/usr/sbin'
43 STATICDIR = '/usr/share/ipsilon'
44
45
46 class ConfigurationError(Exception):
47
48     def __init__(self, message):
49         super(ConfigurationError, self).__init__(message)
50         self.message = message
51
52     def __str__(self):
53         return repr(self.message)
54
55
56 #Silence cherrypy logging to screen
57 cherrypy.log.screen = False
58
59 # Regular logging
60 LOGFILE = '/var/log/ipsilon-install.log'
61 logger = logging.getLogger()
62
63
64 def openlogs():
65     global logger  # pylint: disable=W0603
66     if os.path.isfile(LOGFILE):
67         try:
68             created = '%s' % time.ctime(os.path.getctime(LOGFILE))
69             shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created))
70         except IOError:
71             pass
72     logger = logging.getLogger()
73     try:
74         lh = logging.FileHandler(LOGFILE)
75     except IOError, e:
76         print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e))
77         lh = logging.StreamHandler(sys.stderr)
78     formatter = logging.Formatter('[%(asctime)s] %(message)s')
79     lh.setFormatter(formatter)
80     logger.addHandler(lh)
81
82
83 def install(plugins, args):
84     logger.info('Installation initiated')
85     now = time.strftime("%Y%m%d%H%M%S", time.gmtime())
86     instance_conf = os.path.join(CONFDIR, args['instance'])
87
88     logger.info('Installing default config files')
89     ipsilon_conf = os.path.join(instance_conf, 'ipsilon.conf')
90     idp_conf = os.path.join(instance_conf, 'idp.conf')
91     args['httpd_conf'] = os.path.join(HTTPDCONFD,
92                                       'ipsilon-%s.conf' % args['instance'])
93     args['data_dir'] = os.path.join(DATADIR, args['instance'])
94     if os.path.exists(ipsilon_conf):
95         shutil.move(ipsilon_conf, '%s.bakcup.%s' % (ipsilon_conf, now))
96     if os.path.exists(idp_conf):
97         shutil.move(idp_conf, '%s.backup.%s' % (idp_conf, now))
98     if not os.path.exists(instance_conf):
99         os.makedirs(instance_conf, 0700)
100     confopts = {'instance': args['instance'],
101                 'datadir': args['data_dir'],
102                 'sysuser': args['system_user'],
103                 'ipsilondir': BINDIR,
104                 'staticdir': STATICDIR}
105     files.write_from_template(ipsilon_conf,
106                               os.path.join(TEMPLATES, 'ipsilon.conf'),
107                               confopts)
108     files.write_from_template(idp_conf,
109                               os.path.join(TEMPLATES, 'idp.conf'),
110                               confopts)
111     if not os.path.exists(args['httpd_conf']):
112         os.symlink(idp_conf, args['httpd_conf'])
113     sessdir = os.path.join(args['data_dir'], 'sessions')
114     if not os.path.exists(sessdir):
115         os.makedirs(sessdir, 0700)
116     data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
117     if not os.path.exists(data_conf):
118         os.symlink(ipsilon_conf, data_conf)
119     # Load the cherrypy config from the newly installed file so
120     # that db paths and all is properly set before configuring
121     # components
122     cherrypy.config.update(ipsilon_conf)
123
124     # Move pre-existing admin db away
125     admin_db = cherrypy.config['admin.config.db']
126     if os.path.exists(admin_db):
127         shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
128
129     # Rebuild user db
130     users_db = cherrypy.config['user.prefs.db']
131     if os.path.exists(users_db):
132         shutil.move(users_db, '%s.backup.%s' % (users_db, now))
133     db = Store()
134     db.save_user_preferences(args['admin_user'], {'is_admin': 1})
135
136     logger.info('Configuring environment helpers')
137     for plugin_name in plugins['Environment Helpers']:
138         plugin = plugins['Environment Helpers'][plugin_name]
139         plugin.configure_server(args)
140
141     logger.info('Configuring login managers')
142     for plugin_name in args['lm_order']:
143         plugin = plugins['Login Managers'][plugin_name]
144         plugin.configure(args)
145
146     logger.info('Configuring Authentication Providers')
147     for plugin_name in plugins['Auth Providers']:
148         plugin = plugins['Auth Providers'][plugin_name]
149         plugin.configure(args)
150
151     # Fixup permissions so only the ipsilon user can read these files
152     files.fix_user_dirs(instance_conf, opts['system_user'])
153     files.fix_user_dirs(args['data_dir'], opts['system_user'])
154     try:
155         subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
156     except Exception:  # pylint: disable=broad-except
157         pass
158
159 def uninstall(plugins, args):
160     logger.info('Uninstallation initiated')
161     raise Exception('Not Implemented')
162
163
164 def find_plugins():
165     plugins = {
166         'Environment Helpers': EnvHelpersInstall().plugins,
167         'Login Managers': LoginMgrsInstall().plugins,
168         'Auth Providers': ProvidersInstall().plugins
169     }
170     return plugins
171
172
173 def parse_config_profile(args):
174     config = ConfigParser.ConfigParser()
175     files = config.read(args['config_profile'])
176     if len(files) == 0:
177         raise ConfigurationError('Config Profile file %s not found!' %
178                                  args['config_profile'])
179
180     if 'globals' in config.sections():
181         G = config.options('globals')
182         for g in G:
183             val = config.get('globals', g)
184             if g in globals():
185                 globals()[g] = val
186             else:
187                 for k in globals().keys():
188                     if k.lower() == g.lower():
189                         globals()[k] = val
190                         break
191
192     if 'arguments' in config.sections():
193         A = config.options('arguments')
194         for a in A:
195             args[a] = config.get('arguments', a)
196
197     return args
198
199
200 def parse_args(plugins):
201     parser = argparse.ArgumentParser(description='Ipsilon Install Options')
202     parser.add_argument('--version',
203                         action='version', version='%(prog)s 0.1')
204     parser.add_argument('-o', '--login-managers-order', dest='lm_order',
205                         help='Comma separated list of login managers')
206     parser.add_argument('--hostname',
207                         help="Machine's fully qualified host name")
208     parser.add_argument('--instance', default='idp',
209                         help="IdP instance name, each is a separate idp")
210     parser.add_argument('--system-user', default='ipsilon',
211                         help="User account used to run the server")
212     parser.add_argument('--admin-user', default='admin',
213                         help="User account that is assigned admin privileges")
214     parser.add_argument('--config-profile', default=None,
215                         help="File containing install options")
216     parser.add_argument('--uninstall', action='store_true',
217                         help="Uninstall the server and all data")
218
219     lms = []
220
221     for plugin_group in plugins:
222         group = parser.add_argument_group(plugin_group)
223         for plugin_name in plugins[plugin_group]:
224             plugin = plugins[plugin_group][plugin_name]
225             if plugin.ptype == 'login':
226                 lms.append(plugin.name)
227             plugin.install_args(group)
228
229     args = vars(parser.parse_args())
230
231     if args['config_profile']:
232         args = parse_config_profile(args)
233
234     if not args['hostname']:
235         args['hostname'] = socket.getfqdn()
236
237     if len(args['hostname'].split('.')) < 2:
238         raise ConfigurationError('Hostname: %s is not a FQDN')
239
240     try:
241         pwd.getpwnam(args['system_user'])
242     except KeyError:
243         raise ConfigurationError('User: %s not found on the system')
244
245     if args['lm_order'] is None:
246         args['lm_order'] = []
247         for name in lms:
248             if args[name] == 'yes':
249                 args['lm_order'].append(name)
250     else:
251         args['lm_order'] = args['lm_order'].split(',')
252
253     if len(args['lm_order']) == 0:
254         #force the basic pam provider if nothing else is selected
255         if 'pam' not in args:
256             parser.print_help()
257             sys.exit(-1)
258         args['lm_order'] = ['pam']
259         args['pam'] = 'yes'
260
261     #FIXME: check instance is only alphanums
262
263     return args
264
265 if __name__ == '__main__':
266     opts = []
267     out = 0
268     openlogs()
269     try:
270         fplugins = find_plugins()
271         opts = parse_args(fplugins)
272
273         logger.setLevel(logging.DEBUG)
274
275         logger.info('Intallation arguments:')
276         for k in sorted(opts.iterkeys()):
277             logger.info('%s: %s', k, opts[k])
278
279         if 'uninstall' in opts and opts['uninstall'] is True:
280             uninstall(fplugins, opts)
281
282         install(fplugins, opts)
283     except Exception, e:  # pylint: disable=broad-except
284         logger.exception(e)
285         if 'uninstall' in opts and opts['uninstall'] is True:
286             print 'Uninstallation aborted.'
287         else:
288             print 'Installation aborted.'
289         print 'See log file %s for details' % LOGFILE
290         out = 1
291     finally:
292         if out == 0:
293             if 'uninstall' in opts and opts['uninstall'] is True:
294                 print 'Uninstallation complete.'
295             else:
296                 print 'Installation complete.'
297                 print 'Please restart HTTPD to enable the IdP instance.'
298     sys.exit(out)