Use python logging in install / log cherrypy at right severity
[cascardo/ipsilon.git] / ipsilon / helpers / ipa.py
1 # Copyright (C) 2014  Simo Sorce <simo@redhat.com>
2 #
3 # see file 'COPYING' for use and warranty information
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 import logging
19 import pwd
20 import os
21 import socket
22 import subprocess
23
24 from ipsilon.helpers.common import EnvHelpersInstaller
25
26
27 IPA_CONFIG_FILE = '/etc/ipa/default.conf'
28 HTTPD_IPA_KEYTAB = '/etc/httpd/conf/ipa.keytab'
29 IPA_COMMAND = '/usr/bin/ipa'
30 IPA_GETKEYTAB = '/usr/sbin/ipa-getkeytab'
31 HTTPD_USER = 'apache'
32
33 NO_CREDS_FOR_KEYTAB = """
34 Valid IPA admin credentials are required to get a keytab.
35 Please kinit with a pivileged user like 'admin' and retry.
36 """
37
38 FAILED_TO_GET_KEYTAB = """
39 A pre-existing keytab was not found and it was not possible to
40 successfully retrieve a new keytab for the IPA server. Please
41 manually provide a keytab or resolve the error that cause this
42 failure (see logs) and retry.
43 """
44
45
46 class Installer(EnvHelpersInstaller):
47
48     def __init__(self, *pargs):
49         super(Installer, self).__init__()
50         self.name = 'ipa'
51         self.ptype = 'helper'
52         self.logger = None
53         self.realm = None
54         self.domain = None
55         self.server = None
56
57     def install_args(self, group):
58         group.add_argument('--ipa', choices=['yes', 'no', 'auto'],
59                            default='auto',
60                            help='Helper for IPA joined machines')
61
62     def conf_init(self, opts):
63         logger = self.logger
64         # Do a simple check to see if machine is ipa joined
65         if not os.path.exists(IPA_CONFIG_FILE):
66             logger.info('No IPA configuration file. Skipping ipa helper...')
67             if opts['ipa'] == 'yes':
68                 raise Exception('No IPA installation found!')
69             return
70
71         # Get config vars from ipa file
72         try:
73             from ipapython import config as ipaconfig
74
75             ipaconfig.init_config()
76             self.realm = ipaconfig.config.get_realm()
77             self.domain = ipaconfig.config.get_domain()
78             self.server = ipaconfig.config.get_server()
79
80         except Exception, e:  # pylint: disable=broad-except
81             logger.info('IPA tools installation found: [%s]', e)
82             if opts['ipa'] == 'yes':
83                 raise Exception('No IPA installation found!')
84             return
85
86     def get_keytab(self, opts):
87         logger = self.logger
88         # Check if we have need ipa tools
89         if not os.path.exists(IPA_GETKEYTAB):
90             logger.info('ipa-getkeytab missing. Will skip keytab creation.')
91             if opts['ipa'] == 'yes':
92                 raise Exception('No IPA tools found!')
93
94         # Check if we already have a keytab for HTTP
95         if 'gssapi_httpd_keytab' in opts:
96             msg = "Searching for keytab in: %s" % opts['gssapi_httpd_keytab']
97             if os.path.exists(opts['gssapi_httpd_keytab']):
98                 logger.info(msg + "... Found!")
99                 return
100             else:
101                 logger.info(msg + "... Not found!")
102
103         msg = "Searching for keytab in: %s" % HTTPD_IPA_KEYTAB
104         if os.path.exists(HTTPD_IPA_KEYTAB):
105             opts['gssapi_httpd_keytab'] = HTTPD_IPA_KEYTAB
106             logger.info(msg + "... Found!")
107             return
108         else:
109             logger.info(msg + "... Not found!")
110
111         us = socket.gethostname()
112         princ = 'HTTP/%s@%s' % (us, self.realm)
113
114         # Check we have credentials to access server (for keytab)
115         from ipalib import api
116         from ipalib import errors as ipaerrors
117
118         api.bootstrap(context='ipsilon_installer')
119         api.finalize()
120
121         try:
122             api.Backend.rpcclient.connect()
123             logger.debug('Try RPC connection')
124             api.Backend.rpcclient.forward('ping')
125             logger.debug("... Succeeded!")
126         except ipaerrors.KerberosError as e:
127             logger.error('Invalid credentials: [%s]', repr(e))
128             if api.Backend.rpcclient.isconnected():
129                 api.Backend.rpcclient.disconnect()
130             raise Exception('Invalid credentials: [%s]' % e)
131         except ipaerrors.PublicError as e:
132             logger.error(
133                 'Cannot connect to the server due to generic error: %s', e)
134             if api.Backend.rpcclient.isconnected():
135                 api.Backend.rpcclient.disconnect()
136             raise Exception('Unable to connect to IPA server: %s' % e)
137
138         # Specify an older version to work on nearly any master. Force is
139         # set to True so a DNS A record is not required for adding the
140         # service.
141         try:
142             api.Backend.rpcclient.forward(
143                 'service_add',
144                 unicode(princ),
145                 force=True,
146                 version=u'2.0',
147             )
148         except ipaerrors.DuplicateEntry:
149             logger.debug('Principal %s already exists', princ)
150         except ipaerrors.NotFound as e:
151             logger.error('%s', e)
152             raise Exception('%s' % e)
153         except ipaerrors.ACIError as e:
154             logger.error(NO_CREDS_FOR_KEYTAB)
155             logger.debug('Invalid credentials: [%s]', repr(e))
156             raise Exception('Invalid credentials: [%s]' % e)
157         finally:
158             server = api.Backend.rpcclient.api.env.server
159             if api.Backend.rpcclient.isconnected():
160                 api.Backend.rpcclient.disconnect()
161
162         try:
163             msg = "Trying to fetch keytab[%s] for %s" % (
164                   opts['gssapi_httpd_keytab'], princ)
165             logger.info(msg)
166             subprocess.check_output([IPA_GETKEYTAB,
167                                      '-s', server, '-p', princ,
168                                      '-k', opts['gssapi_httpd_keytab']],
169                                     stderr=subprocess.STDOUT)
170         except subprocess.CalledProcessError, e:
171             # unfortunately this one is fatal
172             logger.error(FAILED_TO_GET_KEYTAB)
173             logger.info('Error trying to get HTTP keytab:')
174             logger.info('Cmd> %s\n%s', e.cmd, e.output)
175             raise Exception('Missing keytab: [%s]' % e)
176
177         # Fixup permissions so only the ipsilon user can read these files
178         pw = pwd.getpwnam(HTTPD_USER)
179         os.chown(opts['gssapi_httpd_keytab'], pw.pw_uid, pw.pw_gid)
180
181     def configure_server(self, opts):
182         if opts['ipa'] != 'yes' and opts['ipa'] != 'auto':
183             return
184         if opts['ipa'] != 'yes' and opts['gssapi'] == 'no':
185             return
186
187         self.logger = logging.getLogger()
188
189         self.conf_init(opts)
190
191         self.get_keytab(opts)
192
193         # Forcibly use gssapi then pam modules
194         if 'lm_order' not in opts:
195             opts['lm_order'] = []
196         opts['gssapi'] = 'yes'
197         if 'gssapi' not in opts['lm_order']:
198             opts['lm_order'].insert(0, 'gssapi')
199         opts['form'] = 'yes'
200         if not any(lm in opts['lm_order'] for lm in ('form', 'pam')):
201             opts['lm_order'].append('form')