#!/usr/bin/python # Copyright 2016 Red Hat, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import logging import os import pwd import shutil import socket import sys import time import copy import tempfile import getpass from ipapython.ipautil import run, kinit_password, user_input from ipalib import api from ipalib import errors IPACONF = '/etc/ipa/default.conf' KEYTAB = '/etc/join/krb5.keytab' class ConfigurationError(StandardError): def __init__(self, message): StandardError.__init__(self, message) LOGFILE = '/var/log/novajoin-ipa-install.log' logger = logging.getLogger() def openlogs(): global logger # pylint: disable=W0603 if os.path.isfile(LOGFILE): try: created = '%s' % time.strftime( '%Y%m%d%H%M%SZ', time.gmtime(os.path.getctime(LOGFILE))) shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created)) except IOError: pass logger = logging.getLogger() try: lh = logging.FileHandler(LOGFILE) except IOError as e: print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e)) lh = logging.StreamHandler(sys.stderr) formatter = logging.Formatter('[%(asctime)s] %(message)s') lh.setFormatter(formatter) lh.setLevel(logging.DEBUG) logger.addHandler(lh) logger.propagate = False ch = logging.StreamHandler(sys.stdout) formatter = logging.Formatter('%(message)s') ch.setFormatter(formatter) ch.setLevel(logging.INFO) logger.addHandler(ch) def install(args): logger.info('Installation initiated') if not os.path.exists('/etc/ipa/default.conf'): raise ConfigurationError('Must be enrolled in IPA') api.bootstrap(context='novajoin') api.finalize() ccache_dir = tempfile.mkdtemp(prefix='krbcc') ccache_name = os.path.join(ccache_dir, 'ccache') env = {"PATH": "/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:/usr/bin:/usr/sbin" } env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = ccache_name principal = args['principal'] if principal.find('@') == -1: principal = '%s@%s' % (principal, api.env.realm) try: kinit_password(principal, args['password'], ccache_name) except RuntimeError as e: raise ConfigurationError("Kerberos authentication failed: %s" % e) api.Backend.rpcclient.connect() try: result = api.Command.service_add(u'nova/%s@%s' % (args['hostname'], api.env.realm), force=True) except errors.DuplicateEntry: pass except Exception as e: raise ConfigurationError( 'Failed to add service: %s' % e) try: if os.path.exists(KEYTAB): os.unlink(KEYTAB) except OSError as e: raise ConfigurationError( 'Could not remove %s: %s' % (KEYTAB, e) ) run(['ipa-getkeytab', '-s', api.env.server, '-p', 'nova/%s@%s' % (args['hostname'], api.env.realm), '-k', KEYTAB], env=env) logger.info('Installing default config files') try: user = pwd.getpwnam(args['user']) except KeyError: raise ConfigurationError('User: %s not found on the system' % args['user']) os.chown(KEYTAB, user.pw_uid, user.pw_gid) os.chmod(KEYTAB, 0o600) logger.info('Creating IPA permissions') (stdout, stderr, returncode) = run( ['/usr/libexec/novajoin-ipa-setup.sh'], raiseonerr=False) if returncode != 0: logger.error('Creating IPA permissions failed') def parse_args(): parser = argparse.ArgumentParser(description='Nova join Install Options') parser.add_argument('--hostname', help='Machine\'s fully qualified host name') parser.add_argument('--user', help='User that nova services run as', default='nova') parser.add_argument('--principal', dest='principal', default='admin', help='principal to use to setup IPA integration') parser.add_argument('--password', dest='password', help='password for the principal') parser.add_argument('--password-file', dest='passwordfile', help='path to file containing password for ' 'the principal') args = vars(parser.parse_args()) if not args['principal']: args['principal'] = user_input("IPA admin user", "admin", allow_empty=False) if args['passwordfile']: try: with open(args['passwordfile']) as f: args['password'] = f.read() except IOError as e: raise ConfigurationError('Unable to read password file: %s' % e) if not args['password']: try: args['password'] = getpass.getpass("Password for %s: " % args['principal']) except EOFError: args['password'] = None if not args['password']: raise ConfigurationError('Password must be provided.') if not args['hostname']: args['hostname'] = socket.getfqdn() if len(args['hostname'].split('.')) < 2: raise ConfigurationError('Hostname: %s is not a FQDN' % args['hostname']) try: pwd.getpwnam(args['user']) except KeyError: raise ConfigurationError('User: %s not found on the system' % args['user']) return args if __name__ == '__main__': opts = [] out = 0 openlogs() logger.setLevel(logging.DEBUG) try: opts = parse_args() logger.debug('Installation arguments:') for k in sorted(opts.iterkeys()): logger.debug('%s: %s', k, opts[k]) install(opts) except Exception as e: # pylint: disable=broad-except logger.info(str(e)) # emit message to console logger.debug(e, exc_info=1) # add backtrace information to logfile logger.info('Installation aborted.') logger.info('See log file %s for details' % LOGFILE) out = 1 except SystemExit: out = 1 raise finally: if out == 0: logger.info('Installation complete.') logger.info( 'Please restart nova-api to enable the join service.') sys.exit(out)