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