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