novajoin/novajoin/configure_ipa.py

211 lines
7.5 KiB
Python

#!/usr/bin/python
import getpass
import logging
import os
import pwd
import socket
import sys
import tempfile
from ipalib import api
from ipalib import errors
from ipapython.ipautil import run, kinit_password, user_input
from novajoin.errors import ConfigurationError
logger = logging.getLogger()
class NovajoinRole(object):
"""
One-stop shopping for creating the IPA permissions, privilege and role.
Assumes that ipalib is imported and initialized and an RPC context
already exists.
"""
def __init__(self, keytab='/etc/join/krb5.keytab', user='nova'):
self.keytab = keytab
self.user = user
self.service = u'nova/%s' % self._get_fqdn()
self.ccache_name = None
def _get_fqdn(self):
"""
Try to determine the fully-qualfied domain name of this box
"""
fqdn = ""
try:
fqdn = socket.getfqdn()
except Exception: # pylint: disable=broad-except
try:
# assume it is in the IPA domain if it comes back
# not fully-qualified
fqdn = socket.gethostname()
# pylint: disable=no-member
fqdn = fqdn + '.' + api.env.domain
except Exception: # pylint: disable=broad-except
fqdn = ""
return fqdn
def kinit(self, principal, password):
ccache_dir = tempfile.mkdtemp(prefix='krbcc')
self.ccache_name = os.path.join(ccache_dir, 'ccache')
current_ccache = os.environ.get('KRB5CCNAME')
os.environ['KRB5CCNAME'] = self.ccache_name
if principal.find('@') == -1:
# pylint: disable=no-member
principal = '%s@%s' % (principal, api.env.realm)
try:
kinit_password(principal, password, self.ccache_name)
except RuntimeError as e:
raise ConfigurationError("Kerberos authentication failed: %s" % e)
finally:
if current_ccache:
os.environ['KRB5CCNAME'] = current_ccache
def _call_ipa(self, command, args, kw):
"""
Call into the IPA API.
Duplicates are ignored to be idempotent. Other errors are
ignored implitly because they are encapsulated in the result
for some calls.
"""
try:
api.Command[command](args, **kw)
except errors.DuplicateEntry:
pass
except Exception as e: # pylint: disable=broad-except
logger.error("Unhandled exception: %s", e)
def _add_permissions(self):
self._call_ipa(u'permission_add', u'Modify host password',
{'ipapermright': u'write',
'type': u'host',
'attrs': u'userpassword'})
self._call_ipa(u'permission_add', u'Write host certificate',
{'ipapermright': u'write',
'type': u'host',
'attrs': u'usercertificate'})
self._call_ipa(u'permission_add', u'Modify host userclass',
{'ipapermright': u'write',
'type': u'host',
'attrs': u'userclass'})
def _add_privileges(self):
self._call_ipa(u'privilege_add', u'Nova Host Management',
{'description': u'Nova Host Management'})
self._call_ipa(u'privilege_add_permission', u'Nova Host Management',
{u'permission': [
u'System: add hosts',
u'System: remove hosts',
u'modify host password',
u'modify host userclass',
u'modify hosts',
u'System: revoke certificate',
u'System: manage host keytab',
u'System: write host certificate',
u'System: retrieve certificates from the ca',
u'System: modify services',
u'System: manage service keytab',
u'System: read dns entries',
u'System: remove dns entries',
u'System: add dns entries',
u'System: update dns entries']})
def _add_role(self):
self._call_ipa(u'role_add', u'Nova Host Manager',
{'description': u'Nova Host Manager'})
self._call_ipa(u'role_add_privilege', u'Nova Host Manager',
{'privilege': u'Nova Host Management'})
self._call_ipa(u'role_add_member', u'Nova Host Manager',
{u'service': self.service})
def _add_service(self):
self._call_ipa(u'service_add', self.service, {'force': True})
def _get_keytab(self):
if self.ccache_name:
current_ccache = os.environ.get('KRB5CCNAME')
os.environ['KRB5CCNAME'] = self.ccache_name
try:
if os.path.exists(self.keytab):
os.unlink(self.keytab)
except OSError as e:
sys.exit('Could not remove %s: %s' % (self.keytab, e))
try:
run(['ipa-getkeytab',
'-s', api.env.server, # pylint: disable=no-member
'-p', self.service,
'-k', self.keytab])
finally:
if current_ccache:
os.environ['KRB5CCNAME'] = current_ccache
# s/b already validated
user = pwd.getpwnam(self.user)
os.chown(self.keytab, user.pw_uid, user.pw_gid)
os.chmod(self.keytab, 0o600)
def configure_ipa(self):
self._add_service()
self._get_keytab()
self._add_permissions()
self._add_privileges()
self._add_role()
def ipa_options(parser):
parser.add_argument('--no-kinit',
help='Assume the user has already done a kinit',
action="store_true", default=False)
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')
return parser
def validate_options(args):
if args.get('no_kinit', False):
return 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.')
try:
pwd.getpwnam(args['user'])
except KeyError:
raise ConfigurationError('User: %s not found on the system' %
args['user'])