From 758f38a1f6041ea6b456efdbe3871746252bce56 Mon Sep 17 00:00:00 2001 From: Vitaly Gridnev Date: Fri, 22 Jul 2016 12:54:16 +0300 Subject: [PATCH] kerberos infra deployment impl this change implements support of deploying kerberos infrastucture, including kdc server and setting up clients. can be tested on fake plugin. Partially-implements bp: initial-kerberos-integration Change-Id: I328c3ecbbc712b3a9d48a6a7248ec28477ec0f5c --- sahara/plugins/fake/plugin.py | 18 +- sahara/plugins/kerberos.py | 345 ++++++++++++++++++ sahara/plugins/resources/kdc_conf | 16 + sahara/plugins/resources/kdc_conf_redhat | 13 + .../resources/krb-client-init.sh.template | 14 + sahara/plugins/resources/krb5_config | 11 + .../resources/mit-kdc-server-init.sh.template | 34 ++ sahara/plugins/utils.py | 18 +- sahara/tests/unit/plugins/test_kerberos.py | 93 +++++ sahara/utils/ssh_remote.py | 7 +- 10 files changed, 564 insertions(+), 5 deletions(-) create mode 100644 sahara/plugins/kerberos.py create mode 100644 sahara/plugins/resources/kdc_conf create mode 100644 sahara/plugins/resources/kdc_conf_redhat create mode 100644 sahara/plugins/resources/krb-client-init.sh.template create mode 100644 sahara/plugins/resources/krb5_config create mode 100644 sahara/plugins/resources/mit-kdc-server-init.sh.template create mode 100644 sahara/tests/unit/plugins/test_kerberos.py diff --git a/sahara/plugins/fake/plugin.py b/sahara/plugins/fake/plugin.py index 0915df2551..3abfcd93bd 100644 --- a/sahara/plugins/fake/plugin.py +++ b/sahara/plugins/fake/plugin.py @@ -17,6 +17,7 @@ from sahara import context from sahara.i18n import _ from sahara.plugins import exceptions as pex from sahara.plugins.fake import edp_engine +from sahara.plugins import kerberos as krb from sahara.plugins import provisioning as p from sahara.plugins import utils as plugin_utils @@ -49,11 +50,12 @@ class FakePluginProvider(p.ProvisioningPluginBase): return { "HDFS": ["namenode", "datanode"], "MapReduce": ["tasktracker", "jobtracker"], + "Kerberos": [], } def get_configs(self, hadoop_version): - # no need to expose any configs, it could be checked using real plugins - return [] + # returning kerberos configs + return krb.get_config_list() def configure_cluster(self, cluster): with context.ThreadGroup() as tg: @@ -62,11 +64,23 @@ class FakePluginProvider(p.ProvisioningPluginBase): self._write_ops, instance) def start_cluster(self, cluster): + self.deploy_kerberos(cluster) with context.ThreadGroup() as tg: for instance in plugin_utils.get_instances(cluster): tg.spawn('fake-check-%s' % instance.id, self._check_ops, instance) + def deploy_kerberos(self, cluster): + all_instances = plugin_utils.get_instances(cluster) + namenodes = plugin_utils.get_instances(cluster, 'namenode') + server = None + if len(namenodes) > 0: + server = namenodes[0] + elif len(all_instances) > 0: + server = all_instances[0] + if server: + krb.deploy_infrastructure(cluster, server) + def scale_cluster(self, cluster, instances): with context.ThreadGroup() as tg: for instance in instances: diff --git a/sahara/plugins/kerberos.py b/sahara/plugins/kerberos.py new file mode 100644 index 0000000000..bc4875ad8c --- /dev/null +++ b/sahara/plugins/kerberos.py @@ -0,0 +1,345 @@ +# 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. + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import uuidutils as uuid + +from sahara import conductor as cond +from sahara import context +from sahara import exceptions as exc +from sahara.i18n import _ +from sahara.plugins import provisioning as base +from sahara.plugins import utils as pl_utils +from sahara.service.castellan import utils as key_manager +from sahara.utils import cluster as cl_utils +from sahara.utils import cluster_progress_ops as cpo +from sahara.utils import files + + +conductor = cond.API +LOG = logging.getLogger(__name__) +CONF = cfg.CONF +POLICY_FILES_DIR = '/tmp/UnlimitedPolicy' + + +class KDCInstallationFailed(exc.SaharaException): + code = 'KDC_INSTALL_FAILED' + message_template = _('KDC installation failed by reason: {reason}') + + def __init__(self, reason): + message = self.message_template.format(reason=reason) + super(KDCInstallationFailed, self).__init__(message) + + +def _config(**kwargs): + return base.Config( + applicable_target='Kerberos', priority=1, + is_optional=True, scope='cluster', **kwargs) + + +enable_kerberos = _config( + name='Enable Kerberos Security', config_type='bool', + default_value=False) +use_existing_kdc = _config( + name='Existing KDC', config_type='bool', + default_value=False) +kdc_server_ip = _config( + name='Server IP of KDC', config_type='string', + default_value='192.168.0.1', + description=_('Server IP of KDC server when using existing KDC')) +realm_name = _config( + name='Realm Name', config_type='string', + default_value='SAHARA-KDC', + description=_('The name of realm to be used')) +admin_principal = _config( + name='Admin principal', config_type='string', + default_value='sahara/admin', + description=_('Admin principal for existing KDC server')) +admin_password = _config( + name='Admin password', config_type='string', default_value='') +policy_url = _config( + name="JCE libraries", config_type='string', + default_value='http://sahara-files.mirantis.com/kerberos-artifacts', + description=_('Java Cryptography Extension (JCE) ' + 'Unlimited Strength Jurisdiction Policy Files location') +) + + +def get_config_list(): + return [ + enable_kerberos, + use_existing_kdc, + kdc_server_ip, + realm_name, + admin_principal, + admin_password, + policy_url, + ] + + +def get_kdc_host(cluster, server): + if using_existing_kdc(cluster): + return "server.%s" % CONF.node_domain + return server.fqdn() + + +def is_kerberos_security_enabled(cluster): + return pl_utils.get_config_value_or_default( + cluster=cluster, config=enable_kerberos) + + +def using_existing_kdc(cluster): + return pl_utils.get_config_value_or_default( + cluster=cluster, config=use_existing_kdc) + + +def get_kdc_server_ip(cluster): + return pl_utils.get_config_value_or_default( + cluster=cluster, config=kdc_server_ip) + + +def get_realm_name(cluster): + return pl_utils.get_config_value_or_default( + cluster=cluster, config=realm_name) + + +def get_admin_principal(cluster): + return pl_utils.get_config_value_or_default( + cluster=cluster, config=admin_principal) + + +def get_admin_password(cluster): + # TODO(vgridnev): support in follow-up improved secret storage for + # configs + return pl_utils.get_config_value_or_default( + cluster=cluster, config=admin_password) + + +def get_policy_url(cluster): + return pl_utils.get_config_value_or_default( + cluster=cluster, config=policy_url) + + +def setup_clients(cluster, server=None, instances=None): + if not instances: + instances = cl_utils.get_instances(cluster) + server_ip = None + cpo.add_provisioning_step( + cluster.id, _("Setting Up Kerberos clients"), len(instances)) + + if not server: + server_ip = get_kdc_server_ip(cluster) + with context.ThreadGroup() as tg: + for instance in instances: + tg.spawn('setup-client-%s' % instance.instance_name, + _setup_client_node, cluster, instance, + server, server_ip) + + +def prepare_policy_files(cluster, instances=None): + if instances is None: + instances = pl_utils.get_instances(cluster) + + remote_url = get_policy_url(cluster) + cpo.add_provisioning_step( + cluster.id, _("Preparing policy files"), len(instances)) + with context.ThreadGroup() as tg: + for inst in instances: + tg.spawn( + 'policy-files', + _prepare_policy_files, inst, remote_url) + + +def deploy_infrastructure(cluster, server=None): + if not is_kerberos_security_enabled(cluster): + LOG.debug("Kerberos security disabled for cluster") + return + if not using_existing_kdc(cluster): + deploy_kdc_server(cluster, server) + + setup_clients(cluster, server) + + +def _execute_script(client, script): + with client.remote() as remote: + script_path = '/tmp/%s' % uuid.generate_uuid()[:8] + remote.write_file_to(script_path, script) + remote.execute_command('chmod +x %s' % script_path) + remote.execute_command('bash %s' % script_path) + remote.execute_command('rm -rf %s' % script_path) + + +def _get_kdc_config(cluster, os): + if os == "ubuntu": + data = files.get_file_text('plugins/resources/kdc_conf') + else: + data = files.get_file_text('plugins/resources/kdc_conf_redhat') + return data % { + 'realm_name': get_realm_name(cluster) + } + + +def _get_krb5_config(cluster, server_fqdn): + data = files.get_file_text('plugins/resources/krb5_config') + return data % { + 'realm_name': get_realm_name(cluster), + 'server': server_fqdn, + 'node_domain': CONF.node_domain, + } + + +def _get_short_uuid(): + return "%s%s" % (uuid.generate_uuid()[:8], uuid.generate_uuid()[:8]) + + +def get_server_password(cluster): + if using_existing_kdc(cluster): + return get_admin_password(cluster) + ctx = context.ctx() + cluster = conductor.cluster_get(ctx, cluster) + extra = cluster.extra.to_dict() if cluster.extra else {} + passwd_key = 'admin-passwd-kdc' + if passwd_key not in extra: + passwd = _get_short_uuid() + key_id = key_manager.store_secret(passwd, ctx) + extra[passwd_key] = key_id + cluster = conductor.cluster_update(ctx, cluster, {'extra': extra}) + passwd = key_manager.get_secret(extra.get(passwd_key), ctx) + return passwd + + +def _get_configs_dir(os): + if os == "ubuntu": + return "/etc/krb5kdc" + return "/var/kerberos/krb5kdc" + + +def _get_kdc_conf_path(os): + return "%s/kdc.conf" % _get_configs_dir(os) + + +def _get_realm_create_command(os): + if os == 'ubuntu': + return "krb5_newrealm" + return "kdb5_util create -s" + + +def _get_acl_config_path(os): + return "%s/kadm5.acl" % _get_configs_dir(os) + + +def _get_acl_config(): + return "*/admin * " + + +def _get_start_command(os, version): + if os == "ubuntu": + return ("sudo service krb5-kdc restart && " + "sudo service krb5-admin-server restart") + + if version.startswith('6'): + return ("sudo /etc/rc.d/init.d/krb5kdc start " + "&& sudo /etc/rc.d/init.d/kadmin start") + + if version.startswith('7'): + return ("sudo systemctl start krb5kdc &&" + "sudo systemctl start kadmin") + + raise ValueError( + _("Unable to get kdc server start command")) + + +def _get_server_installation_script(cluster, server_fqdn, os, version): + data = files.get_file_text( + 'plugins/resources/mit-kdc-server-init.sh.template') + return data % { + 'kdc_conf': _get_kdc_config(cluster, os), + 'kdc_conf_path': _get_kdc_conf_path(os), + 'acl_conf': _get_acl_config(), + 'acl_conf_path': _get_acl_config_path(os), + 'realm_create': _get_realm_create_command(os), + 'krb5_conf': _get_krb5_config(cluster, server_fqdn), + 'admin_principal': get_admin_principal(cluster), + 'password': get_server_password(cluster), + 'os': os, + 'start_command': _get_start_command(os, version), + } + + +@cpo.event_wrapper(True, step=_("Deploy KDC server"), param=('cluster', 0)) +def deploy_kdc_server(cluster, server): + with server.remote() as r: + os = r.get_os_distrib() + version = r.get_os_version() + script = _get_server_installation_script( + cluster, server.fqdn(), os, version) + _execute_script(server, script) + + +def _push_etc_hosts_entry(client, entry): + with client.remote() as r: + r.execute_command('echo %s | sudo tee -a /etc/hosts' % entry) + + +def _get_client_installation_script(cluster, server_fqdn, os): + data = files.get_file_text('plugins/resources/krb-client-init.sh.template') + return data % { + 'os': os, + 'krb5_conf': _get_krb5_config(cluster, server_fqdn), + } + + +@cpo.event_wrapper(True, param=('client', 1)) +def _setup_client_node(cluster, client, server=None, server_ip=None): + if server: + server_fqdn = server.fqdn() + elif server_ip: + server_fqdn = "server." % CONF.node_domain + _push_etc_hosts_entry( + client, "%s %s %s" % (server_ip, server_fqdn, server)) + else: + raise KDCInstallationFailed(_('Server or server ip are not provided')) + with client.remote() as r: + os = r.get_os_distrib() + script = _get_client_installation_script(cluster, server_fqdn, os) + _execute_script(client, script) + + +@cpo.event_wrapper(True) +def _prepare_policy_files(instance, remote_url): + with instance.remote() as r: + cmd = 'cut -f2 -d \"=\" /etc/profile.d/99-java.sh | head -1' + exit_code, java_home = r.execute_command(cmd) + java_home = java_home.strip() + results = [ + r.execute_command( + "ls %s/local_policy.jar" % POLICY_FILES_DIR, + raise_when_error=False)[0] != 0, + r.execute_command( + "ls %s/US_export_policy.jar" % POLICY_FILES_DIR, + raise_when_error=False)[0] != 0 + ] + # a least one exit code is not zero + if any(results): + r.execute_command('mkdir %s' % POLICY_FILES_DIR) + r.execute_command( + "sudo curl %s/local_policy.jar -o %s/local_policy.jar" % ( + remote_url, POLICY_FILES_DIR)) + r.execute_command( + "sudo curl %s/US_export_policy.jar -o " + "%s/US_export_policy.jar" % ( + remote_url, POLICY_FILES_DIR)) + r.execute_command( + 'sudo cp %s/*.jar %s/lib/security/' + % (POLICY_FILES_DIR, java_home)) diff --git a/sahara/plugins/resources/kdc_conf b/sahara/plugins/resources/kdc_conf new file mode 100644 index 0000000000..356781b83f --- /dev/null +++ b/sahara/plugins/resources/kdc_conf @@ -0,0 +1,16 @@ +[kdcdefaults] + kdc_ports = 750,88 + +[realms] + %(realm_name)s = { + database_name = /var/lib/krb5kdc/principal + admin_keytab = FILE:/etc/krb5kdc/kadm5.keytab + acl_file = /etc/krb5kdc/kadm5.acl + key_stash_file = /etc/krb5kdc/stash + kdc_ports = 750,88 + max_life = 10h 0m 0s + max_renewable_life = 7d 0h 0m 0s + master_key_type = des3-hmac-sha1 + supported_enctypes = aes256-cts:normal arcfour-hmac:normal des3-hmac-sha1:normal des-cbc-crc:normal des:normal des:v4 des:norealm des:onlyrealm des:afs3 + default_principal_flags = +preauth, +renewable, +forwardable + } diff --git a/sahara/plugins/resources/kdc_conf_redhat b/sahara/plugins/resources/kdc_conf_redhat new file mode 100644 index 0000000000..b916a7899c --- /dev/null +++ b/sahara/plugins/resources/kdc_conf_redhat @@ -0,0 +1,13 @@ +[kdcdefaults] + kdc_ports = 88 + kdc_tcp_ports = 88 + +[realms] + %(realm_name)s = { + #master_key_type = aes256-cts + acl_file = /var/kerberos/krb5kdc/kadm5.acl + dict_file = /usr/share/dict/words + admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab + supported_enctypes = aes256-cts:normal aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal + default_principal_flags = +preauth, +renewable, +forwardable + } diff --git a/sahara/plugins/resources/krb-client-init.sh.template b/sahara/plugins/resources/krb-client-init.sh.template new file mode 100644 index 0000000000..6b55108642 --- /dev/null +++ b/sahara/plugins/resources/krb-client-init.sh.template @@ -0,0 +1,14 @@ +#!/bin/bash + +set -xe +export SAHARA_SCRIPT_BASE_OS=%(os)s +if [ "$SAHARA_SCRIPT_BASE_OS" = "ubuntu" ]; then + + sudo dpkg -s krb5-user || sudo DEBIAN_FRONTEND=noninteractive apt-get install -y krb5-user + sudo dpkg -s libpam-krb5 || sudo DEBIAN_FRONTEND=noninteractive apt-get install -y libpam-krb5 + sudo dpkg -s ldap-utils || sudo DEBIAN_FRONTEND=noninteractive apt-get install -y ldap-utils + +else + sudo rpm -q krb5-workstation || sudo yum install -y krb5-workstation +fi +sudo echo "%(krb5_conf)s" | sudo tee /etc/krb5.conf diff --git a/sahara/plugins/resources/krb5_config b/sahara/plugins/resources/krb5_config new file mode 100644 index 0000000000..435e59cea3 --- /dev/null +++ b/sahara/plugins/resources/krb5_config @@ -0,0 +1,11 @@ +[libdefaults] + default_realm = %(realm_name)s + forwardable = true + proxiable = true +[realms] + %(realm_name)s = { + kdc = %(server)s + admin_server = %(server)s + } +[domain_realm] + .%(node_domain)s = %(realm_name)s diff --git a/sahara/plugins/resources/mit-kdc-server-init.sh.template b/sahara/plugins/resources/mit-kdc-server-init.sh.template new file mode 100644 index 0000000000..1238e6bd34 --- /dev/null +++ b/sahara/plugins/resources/mit-kdc-server-init.sh.template @@ -0,0 +1,34 @@ +#!/bin/bash + +set -xe +export SAHARA_SCRIPT_BASE_OS=%(os)s +if [ "$SAHARA_SCRIPT_BASE_OS" = "ubuntu" ]; then + sudo dpkg -s krb5-admin-server || sudo DEBIAN_FRONTEND=noninteractive apt-get install -y krb5-admin-server + sudo dpkg -s rng-tools || sudo apt-get install rng-tools -y +else + sudo rpm -q krb5-server || sudo yum install -y krb5-server + sudo rpm -q krb5-libs || sudo yum install -y krb5-libs + sudo rpm -q krb5-workstation || sudo yum install -y krb5-workstation + sudo rpm -q rng-tools || sudo yum install -y rng-tools +fi + +sudo rngd -r /dev/urandom -W 4096 + +sudo echo "%(krb5_conf)s" | sudo tee /etc/krb5.conf +sudo echo "%(kdc_conf)s" | sudo tee %(kdc_conf_path)s +sudo echo "%(acl_conf)s" | sudo tee %(acl_conf_path)s + + +sudo %(realm_create)s <