Add IPA helper for server install
[cascardo/ipsilon.git] / ipsilon / helpers / ipa.py
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 import logging
21 import pwd
22 import os
23 import socket
24 import subprocess
25 import sys
26
27
28 IPA_CONFIG_FILE = '/etc/ipa/default.conf'
29 HTTPD_IPA_KEYTAB = '/etc/httpd/conf/ipa.keytab'
30 IPA_COMMAND = '/usr/bin/ipa'
31 IPA_GETKEYTAB = '/usr/sbin/ipa-getkeytab'
32 HTTPD_USER = 'apache'
33
34 NO_CREDS_FOR_KEYTAB = """
35 Valid IPA admin credentials are required to get a keytab.
36 Please kinit with a pivileged user like 'admin' and retry.
37 """
38
39 FAILED_TO_GET_KEYTAB = """
40 A pre-existing keytab was not found and it was not possible to
41 successfully retrieve a new keytab for the IPA server. Please
42 manually provide a keytab or resolve the error that cause this
43 failure (see logs) and retry.
44 """
45
46
47 class Installer(object):
48
49     def __init__(self):
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]', str(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 'krb_httpd_keytab' in opts:
96             if os.path.exists(opts['krb_httpd_keytab']):
97                 return
98
99         if os.path.exists(HTTPD_IPA_KEYTAB):
100             opts['krb_httpd_keytab'] = HTTPD_IPA_KEYTAB
101             return
102
103         us = socket.gethostname()
104         princ = 'HTTP/%s@%s' % (us, self.realm)
105
106         # Check we have credentials to access server (for keytab)
107         from ipapython import ipaldap
108         from ipalib import errors as ipaerrors
109
110         for srv in self.server:
111             try:
112                 server = srv
113                 c = ipaldap.IPAdmin(host=server)
114                 c.do_sasl_gssapi_bind()
115                 del c
116                 break
117             except ipaerrors.ACIError, e:
118                 # usually this error is returned when we have no
119                 # good credentials, ask the user to kinit and retry
120                 print >> sys.stderr, NO_CREDS_FOR_KEYTAB
121                 logger.error('Invalid credentials: [%s]', repr(e))
122                 raise Exception('Invalid credentials: [%s]', str(e))
123             except Exception, e:  # pylint: disable=broad-except
124                 # for other exceptions let's try to fail later
125                 pass
126
127         try:
128             subprocess.check_output([IPA_COMMAND, 'service-add', princ],
129                                     stderr=subprocess.STDOUT)
130         except subprocess.CalledProcessError, e:
131             # hopefully this means the service already exists
132             # otherwise we'll fail later again
133             logger.info('Error trying to create HTTP service:')
134             logger.info('Cmd> %s\n%s', e.cmd, e.output)
135
136         try:
137             subprocess.check_output([IPA_GETKEYTAB,
138                                      '-s', server, '-p', princ,
139                                      '-k', opts['krb_httpd_keytab']],
140                                     stderr=subprocess.STDOUT)
141         except subprocess.CalledProcessError, e:
142             # unfortunately this one is fatal
143             print >> sys.stderr, FAILED_TO_GET_KEYTAB
144             logger.info('Error trying to get HTTP keytab:')
145             logger.info('Cmd> %s\n%s', e.cmd, e.output)
146             raise Exception('Missing keytab: [%s]' % str(e))
147
148         # Fixup permissions so only the ipsilon user can read these files
149         pw = pwd.getpwnam(HTTPD_USER)
150         os.chown(opts['krb_httpd_keytab'], pw.pw_uid, pw.pw_gid)
151
152     def configure_server(self, opts):
153         if opts['ipa'] != 'yes' and opts['ipa'] != 'auto':
154             return
155
156         self.logger = logging.getLogger()
157
158         self.conf_init(opts)
159
160         self.get_keytab(opts)
161
162         # Forcibly use krb then pam modules
163         if not 'lm_order' in opts:
164             opts['lm_order'] = []
165         opts['krb'] = 'yes'
166         if 'krb' not in opts['lm_order']:
167             opts['lm_order'].insert(0, 'krb')
168         opts['pam'] = 'yes'
169         if 'pam' not in opts['lm_order']:
170             opts['lm_order'].append('pam')