diff --git a/zaza/openstack/charm_tests/kerberos/__init__.py b/zaza/openstack/charm_tests/kerberos/__init__.py new file mode 100644 index 0000000..e257ccd --- /dev/null +++ b/zaza/openstack/charm_tests/kerberos/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Collection of code for setting up and testing keystone-kerberos.""" + + +class KerberosConfigurationError(Exception): + """Custom exception for Kerberos test server.""" + + pass diff --git a/zaza/openstack/charm_tests/kerberos/setup.py b/zaza/openstack/charm_tests/kerberos/setup.py new file mode 100644 index 0000000..f58a961 --- /dev/null +++ b/zaza/openstack/charm_tests/kerberos/setup.py @@ -0,0 +1,228 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Setup for keystone-kerberos tests.""" + +import logging +import tempfile +import zaza.model +from zaza.openstack.utilities import openstack as openstack_utils +from zaza.openstack.charm_tests.kerberos import KerberosConfigurationError + + +def get_unit_full_hostname(unit_name): + """Retrieve the full hostname of a unit.""" + for unit in zaza.model.get_units(unit_name): + result = zaza.model.run_on_unit(unit.entity_id, 'hostname -f') + hostname = result['Stdout'].rstrip() + return hostname + + +def add_empty_resource_file_to_keystone_kerberos(): + """Add an empty resource to keystone kerberos to complete the setup.""" + logging.info('Attaching an empty keystone keytab to the keystone-kerberos' + ' unit') + tmp_file = '/tmp/empty.keytab' + with open(tmp_file, 'w'): + pass + + zaza.model.attach_resource('keystone-kerberos', + 'keystone_keytab', + tmp_file) + logging.info('Waiting for keystone-kerberos unit to be active and idle') + unit_name = zaza.model.get_units('keystone-kerberos')[0].name + zaza.model.block_until_unit_wl_status(unit_name, "active") + zaza.model.block_until_all_units_idle() + + +def add_dns_entry(kerberos_hostname="kerberos.testubuntu.com"): + """Add a dns entry in /etc/hosts for the kerberos test server. + + :param kerberos_hostname: FQDN of Kerberos server + :type kerberos_hostname: string + """ + logging.info('Retrieving kerberos IP and hostname') + kerberos_ip = zaza.model.get_app_ips("kerberos-server")[0] + cmd = "sudo sed -i '/localhost/i\\{}\t{}' /etc/hosts"\ + .format(kerberos_ip, kerberos_hostname) + + app_names = ['keystone', 'ubuntu-test-host'] + for app_name in app_names: + logging.info('Adding dns entry to the {} unit'.format(app_name)) + zaza_unit = zaza.model.get_units(app_name)[0] + zaza.model.run_on_unit(zaza_unit.entity_id, cmd) + + +def configure_keystone_service_in_kerberos(): + """Configure the keystone service in Kerberos. + + A principal needs to be added to the kerberos server to get a keytab for + this service. The keytab is used for the authentication of the keystone + service. + """ + logging.info('Configure keystone service in Kerberos') + unit = zaza.model.get_units('kerberos-server')[0] + keystone_hostname = get_unit_full_hostname('keystone') + commands = ['sudo kadmin.local -q "addprinc -randkey -clearpolicy ' + 'HTTP/{}"'.format(keystone_hostname), + 'sudo kadmin.local -q "ktadd ' + '-k /home/ubuntu/keystone.keytab ' + 'HTTP/{}"'.format(keystone_hostname), + 'sudo chmod 777 /home/ubuntu/keystone.keytab'] + + try: + for command in commands: + logging.info( + 'Sending command to the kerberos-server: {}'.format(command)) + result = zaza.model.run_on_unit(unit.name, command) + if result['Stderr']: + raise KerberosConfigurationError + elif result['Stdout']: + logging.info('Stdout: {}'.format(result['Stdout'])) + except KerberosConfigurationError: + logging.error('An error occured : {}'.format(result['Stderr'])) + + +def retrieve_and_attach_keytab(): + """Retrieve and attach the keytab to the keystone-kerberos unit.""" + kerberos_server = zaza.model.get_units('kerberos-server')[0] + + dump_file = "keystone.keytab" + remote_file = "/home/ubuntu/keystone.keytab" + with tempfile.TemporaryDirectory() as tmpdirname: + tmp_file = "{}/{}".format(tmpdirname, dump_file) + logging.info('Retrieving keystone.keytab from the kerberos server.') + zaza.model.scp_from_unit( + kerberos_server.name, + remote_file, + tmp_file) + logging.info('Attaching the keystone_keytab resource to ' + 'keystone-kerberos') + zaza.model.attach_resource('keystone-kerberos', + 'keystone_keytab', + tmp_file) + + +def openstack_setup_kerberos(): + """Create a test domain, project, and user for kerberos tests.""" + kerberos_domain = 'k8s' + kerberos_project = 'k8s' + kerberos_user = 'admin' + kerberos_password = 'password123' + role = 'admin' + + logging.info('Retrieving a keystone session and client.') + keystone_session = openstack_utils.get_overcloud_keystone_session() + keystone_client = openstack_utils.get_keystone_session_client( + keystone_session) + logging.info('Creating domain, project and user for Kerberos tests.') + domain = keystone_client.domains.create(kerberos_domain, + description='Kerberos Domain', + enabled=True) + project = keystone_client.projects.create(kerberos_project, + domain, + description='Test project', + enabled=True) + demo_user = keystone_client.users.create(kerberos_user, + domain=domain, + project=project, + password=kerberos_password, + email='demo@demo.com', + description='Demo User', + enabled=True) + admin_role = keystone_client.roles.find(name=role) + keystone_client.roles.grant( + admin_role, + user=demo_user, + project_domain=domain, + project=project + ) + keystone_client.roles.grant( + admin_role, + user=demo_user, + domain=domain + ) + + +def setup_kerberos_configuration_for_test_host(): + """Retrieve the keytab and krb5.conf to setup the ubuntu test host.""" + kerberos_server = zaza.model.get_units('kerberos-server')[0] + ubuntu_test_host = zaza.model.get_units('ubuntu-test-host')[0] + + dump_file = "krb5.keytab" + remote_file = "/etc/krb5.keytab" + host_keytab_path = '/home/ubuntu/krb5.keytab' + with tempfile.TemporaryDirectory() as tmpdirname: + tmp_file = "{}/{}".format(tmpdirname, dump_file) + logging.info("Retrieving {} from {}.".format(remote_file, + kerberos_server.name)) + zaza.model.scp_from_unit( + kerberos_server.name, + remote_file, + tmp_file) + + logging.info("SCP {} to {} on {}.".format(tmp_file, + host_keytab_path, + ubuntu_test_host.name)) + zaza.model.scp_to_unit( + ubuntu_test_host.name, + tmp_file, + host_keytab_path) + + dump_file = "krb5.conf" + remote_file = "/etc/krb5.conf" + temp_krb5_path = "/home/ubuntu/krb5.conf" + with tempfile.TemporaryDirectory() as tmpdirname: + tmp_file = "{}/{}".format(tmpdirname, dump_file) + logging.info("Retrieving {} from {}".format(remote_file, + kerberos_server.name)) + zaza.model.scp_from_unit( + kerberos_server.name, + remote_file, + tmp_file) + + logging.info("SCP {} to {} on {}.".format(tmp_file, + temp_krb5_path, + ubuntu_test_host)) + zaza.model.scp_to_unit( + ubuntu_test_host.name, + tmp_file, + temp_krb5_path) + logging.info('Moving {} to {} on {}.'.format(temp_krb5_path, + remote_file, ubuntu_test_host.name)) + zaza.model.run_on_unit(ubuntu_test_host.name, ('sudo mv {} {}'. + format(temp_krb5_path, remote_file))) + + +def install_apt_packages_on_ubuntu_test_host(): + """Install apt packages on a zaza unit.""" + ubuntu_test_host = zaza.model.get_units('ubuntu-test-host')[0] + packages = ['krb5-user', 'python3-openstackclient', + 'python3-requests-kerberos'] + for package in packages: + logging.info('Installing {}'.format(package)) + result = zaza.model.run_on_unit(ubuntu_test_host.name, + "apt install {} -y".format(package)) + assert result['Code'] == '0', result['Stderr'] + + +def run_all_configuration_steps(): + """Execute all the necessary functions for the tests setup.""" + add_empty_resource_file_to_keystone_kerberos() + add_dns_entry() + configure_keystone_service_in_kerberos() + retrieve_and_attach_keytab() + openstack_setup_kerberos() + setup_kerberos_configuration_for_test_host() + install_apt_packages_on_ubuntu_test_host() diff --git a/zaza/openstack/charm_tests/kerberos/tests.py b/zaza/openstack/charm_tests/kerberos/tests.py new file mode 100644 index 0000000..e122352 --- /dev/null +++ b/zaza/openstack/charm_tests/kerberos/tests.py @@ -0,0 +1,74 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Keystone Kerberos Tests.""" + +import logging + +import zaza.model +from zaza.openstack.charm_tests.kerberos.setup import get_unit_full_hostname +from zaza.openstack.charm_tests.keystone import BaseKeystoneTest +from zaza.openstack.utilities import openstack as openstack_utils + + +class CharmKeystoneKerberosTest(BaseKeystoneTest): + """Charm Keystone Kerberos Test.""" + + @classmethod + def setUpClass(cls): + """Run class setup for running Keystone Kerberos charm tests.""" + super(CharmKeystoneKerberosTest, cls).setUpClass() + + def test_keystone_kerberos_authentication(self): + """Validate auth to Openstack through the kerberos method.""" + logging.info('Retrieving a kerberos token with kinit for admin user') + + ubuntu_test_host = zaza.model.get_units('ubuntu-test-host')[0] + result = zaza.model.run_on_unit(ubuntu_test_host.name, + "echo password123 | kinit admin") + assert result['Code'] == '0', result['Stderr'] + + logging.info('Changing token mod for user access') + result = zaza.model.run_on_unit( + ubuntu_test_host.name, + "sudo install -m 777 /tmp/krb5cc_0 /tmp/krb5cc_1000" + ) + assert result['Code'] == '0', result['Stderr'] + + logging.info('Fetching user/project info in Openstack') + domain_name = 'k8s' + project_name = 'k8s' + keystone_session = openstack_utils.get_overcloud_keystone_session() + keystone_client = openstack_utils.get_keystone_session_client( + keystone_session) + domain_id = keystone_client.domains.find(name=domain_name).id + project_id = keystone_client.projects.find(name=project_name).id + keystone_hostname = get_unit_full_hostname('keystone') + + logging.info('Retrieving an Openstack token to validate auth') + cmd = 'openstack token issue -f value -c id ' \ + '--os-auth-url http://{}:5000/krb/v3 ' \ + '--os-project-id {} ' \ + '--os-project-name {} ' \ + '--os-project-domain-id {} ' \ + '--os-region-name RegionOne ' \ + '--os-interface public ' \ + '--os-identity-api-version 3 ' \ + '--os-auth-type v3kerberos'.format(keystone_hostname, + project_id, + project_name, + domain_id) + + result = zaza.model.run_on_unit(ubuntu_test_host.name, cmd) + assert result['Code'] == '0', result['Stderr']