Пример #1
0
        def _validate_token_data(openrc):
            keystone_session = openstack_utils.get_keystone_session(openrc)
            keystone_client = openstack_utils.get_keystone_session_client(
                keystone_session)
            token = keystone_session.get_token()
            if (openstack_utils.get_os_release() <
                    openstack_utils.get_os_release('xenial_ocata')):
                if len(token) != 32:
                    raise zaza_exceptions.KeystoneWrongTokenProvider(
                        'We expected a UUID token and got this: "{}"'.format(
                            token))
            else:
                if len(token) < 180:
                    raise zaza_exceptions.KeystoneWrongTokenProvider(
                        'We expected a Fernet token and got this: "{}"'.format(
                            token))
            logging.info('token: "{}"'.format(pprint.pformat(token)))

            if (openstack_utils.get_os_release() <
                    openstack_utils.get_os_release('trusty_mitaka')):
                logging.info('skip: tokens.get_token_data() not allowed prior '
                             'to trusty_mitaka')
                return
            # get_token_data call also gets the service catalog
            token_data = keystone_client.tokens.get_token_data(token)
            if token_data.get('token', {}).get('catalog', None) is None:
                raise zaza_exceptions.KeystoneAuthorizationStrict(
                    # NOTE(fnordahl) the above call will probably throw a
                    # http.Forbidden exception, but just in case
                    'Regular end user not allowed to retrieve the service '
                    'catalog. ("{}")'.format(pprint.pformat(token_data)))
            logging.info('token_data: "{}"'.format(pprint.pformat(token_data)))
Пример #2
0
def get_expected_pools(radosgw=False):
    """Get expected ceph pools.

    Return a list of expected ceph pools in a ceph + cinder + glance
    test scenario, based on OpenStack release and whether ceph radosgw
    is flagged as present or not.
    :param radosgw: If radosgw is used or not
    :type radosgw: boolean
    :returns: List of pools that are expected
    :rtype: list
    """
    current_release = openstack_utils.get_os_release()
    trusty_icehouse = openstack_utils.get_os_release('trusty_icehouse')
    trusty_kilo = openstack_utils.get_os_release('trusty_kilo')
    zesty_ocata = openstack_utils.get_os_release('zesty_ocata')
    if current_release == trusty_icehouse:
        # Icehouse
        pools = ['data', 'metadata', 'rbd', 'cinder-ceph', 'glance']
    elif (trusty_kilo <= current_release <= zesty_ocata):
        # Kilo through Ocata
        pools = ['rbd', 'cinder-ceph', 'glance']
    else:
        # Pike and later
        pools = ['cinder-ceph', 'glance']

    if radosgw:
        pools.extend(
            ['.rgw.root', '.rgw.control', '.rgw', '.rgw.gc', '.users.uid'])

    return pools
Пример #3
0
 def setUpClass(cls):
     """Run class setup for running Keystone charm operation tests."""
     super(BaseKeystoneTest, cls).setUpClass()
     # Check if we are related to Vault TLS certificates
     cls.tls_rid = zaza.model.get_relation_id(
         'keystone', 'vault', remote_interface_name='certificates')
     # Check for VIP
     cls.vip = (zaza.model.get_application_config('keystone')
                .get('vip').get('value'))
     cls.keystone_ips = zaza.model.get_app_ips('keystone')
     # If we have a VIP set and we are using TLS only check the VIP
     # If you check the individual IP haproxy may send to a different
     # back end which leads to mismatched certificates.
     if cls.vip:
         if cls.tls_rid:
             cls.keystone_ips = [cls.vip]
         else:
             cls.keystone_ips.append(cls.vip)
     if (openstack_utils.get_os_release() <
             openstack_utils.get_os_release('xenial_queens')):
         cls.default_api_version = '2'
     else:
         cls.default_api_version = '3'
     cls.admin_keystone_session = (
         openstack_utils.get_overcloud_keystone_session())
     cls.admin_keystone_client = (
         openstack_utils.get_keystone_session_client(
             cls.admin_keystone_session,
             client_api_version=cls.default_api_version))
Пример #4
0
 def setUpClass(cls):
     """Run class setup for running Keystone charm operation tests."""
     super(BaseKeystoneTest, cls).setUpClass()
     cls.keystone_ips = zaza.model.get_app_ips('keystone')
     if (openstack_utils.get_os_release() <
             openstack_utils.get_os_release('xenial_queens')):
         cls.default_api_version = '2'
     else:
         cls.default_api_version = '3'
Пример #5
0
    def test_admin_project_scoped_access(self):
        """Verify cloud admin access using project scoped token.

        `admin` user in `admin_domain` should be able to access API methods
        guarded by `rule:cloud_admin` policy using a token scoped to `admin`
        project in `admin_domain`.

        We implement a policy that enables domain segregation and
        administration delegation [0].  It is important to understand that this
        differs from the default policy.

        In the initial implementation it was necessary to switch between using
        a `domain` scoped and `project` scoped token to successfully manage a
        cloud, but since the introduction of `is_admin` functionality in
        Keystone [1][2][3] and our subsequent adoption of it in Keystone charm
        [4], this is no longer necessary.

        This test here to validate this behaviour.

        0: https://github.com/openstack/keystone/commit/c7a5c6c
        1: https://github.com/openstack/keystone/commit/e702369
        2: https://github.com/openstack/keystone/commit/e923a14
        3: https://github.com/openstack/keystone/commit/9804081
        4: https://github.com/openstack/charm-keystone/commit/10e3d84
        """
        if (openstack_utils.get_os_release() <
                openstack_utils.get_os_release('trusty_mitaka')):
            logging.info('skipping test < trusty_mitaka')
            return
        with self.config_change(
            {'preferred-api-version': self.default_api_version},
            {'preferred-api-version': '3'},
                application_name="keystone"):
            for ip in self.keystone_ips:
                try:
                    logging.info('keystone IP {}'.format(ip))
                    ks_session = openstack_utils.get_keystone_session(
                        openstack_utils.get_overcloud_auth(address=ip))
                    ks_client = openstack_utils.get_keystone_session_client(
                        ks_session)
                    result = ks_client.domains.list()
                    logging.info('.domains.list: "{}"'.format(
                        pprint.pformat(result)))
                except keystoneauth1.exceptions.http.Forbidden as e:
                    raise zaza_exceptions.KeystoneAuthorizationStrict(
                        'Retrieve domain list as admin with project scoped '
                        'token FAILED. ({})'.format(e))
            logging.info('OK')
Пример #6
0
    def test_end_user_domain_admin_access(self):
        """Verify that end-user domain admin does not have elevated privileges.

        In additon to validating that the `policy.json` is written and the
        service is restarted on config-changed, the test validates that our
        `policy.json` is correct.

        Catch regressions like LP: #1651989
        """
        if (openstack_utils.get_os_release() <
                openstack_utils.get_os_release('xenial_ocata')):
            logging.info('skipping test < xenial_ocata')
            return
        with self.config_change(
            {'preferred-api-version': self.default_api_version},
            {'preferred-api-version': '3'},
                application_name="keystone"):
            for ip in self.keystone_ips:
                openrc = {
                    'API_VERSION': 3,
                    'OS_USERNAME': DEMO_ADMIN_USER,
                    'OS_PASSWORD': DEMO_ADMIN_USER_PASSWORD,
                    'OS_AUTH_URL': 'http://{}:5000/v3'.format(ip),
                    'OS_USER_DOMAIN_NAME': DEMO_DOMAIN,
                    'OS_DOMAIN_NAME': DEMO_DOMAIN,
                }
                if self.tls_rid:
                    openrc['OS_CACERT'] = openstack_utils.KEYSTONE_LOCAL_CACERT
                    openrc['OS_AUTH_URL'] = (openrc['OS_AUTH_URL'].replace(
                        'http', 'https'))
                logging.info('keystone IP {}'.format(ip))
                keystone_session = openstack_utils.get_keystone_session(
                    openrc, scope='DOMAIN')
                keystone_client = openstack_utils.get_keystone_session_client(
                    keystone_session)
                try:
                    # expect failure
                    keystone_client.domains.list()
                except keystoneauth1.exceptions.http.Forbidden as e:
                    logging.debug('Retrieve domain list as end-user domain '
                                  'admin NOT allowed...OK ({})'.format(e))
                    pass
                else:
                    raise zaza_exceptions.KeystoneAuthorizationPermissive(
                        'Retrieve domain list as end-user domain admin '
                        'allowed when it should not be.')
        logging.info('OK')
Пример #7
0
    def test_get_openstack_release(self):
        self.patch('zaza.utilities.openstack.get_current_os_release_pair',
                   new_callable=mock.MagicMock(),
                   name='_get_os_rel_pair')

        # Bad release pair
        release_pair = 'bad'
        with self.assertRaises(exceptions.ReleasePairNotFound):
            openstack_utils.get_os_release(release_pair)

        # Normal scenario
        expected = 4
        result = openstack_utils.get_os_release('xenial_mitaka')
        self.assertEqual(expected, result)

        # Normal scenario with current release pair
        self._get_os_rel_pair.return_value = 'xenial_mitaka'
        expected = 4
        result = openstack_utils.get_os_release()
        self.assertEqual(expected, result)

        # We can compare releases xenial_queens > xenial_mitaka
        xenial_queens = openstack_utils.get_os_release('xenial_queens')
        xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka')
        release_comp = xenial_queens > xenial_mitaka
        self.assertTrue(release_comp)
Пример #8
0
def add_demo_user():
    """Add a demo user to the current deployment."""
    def _v2():
        keystone_session = openstack_utils.get_overcloud_keystone_session()
        keystone_client = openstack_utils.get_keystone_session_client(
            keystone_session, client_api_version=2)
        tenant = keystone_client.tenants.create(tenant_name=DEMO_TENANT,
                                                description='Demo Tenant',
                                                enabled=True)
        keystone_client.users.create(name=DEMO_USER,
                                     password=DEMO_PASSWORD,
                                     tenant_id=tenant.id)

    def _v3():
        keystone_session = openstack_utils.get_overcloud_keystone_session()
        keystone_client = openstack_utils.get_keystone_session_client(
            keystone_session)
        domain = keystone_client.domains.create(
            DEMO_DOMAIN,
            description='Demo Domain',
            enabled=True)
        project = keystone_client.projects.create(
            DEMO_PROJECT,
            domain,
            description='Demo Project',
            enabled=True)
        demo_user = keystone_client.users.create(
            DEMO_USER,
            domain=domain,
            project=project,
            password=DEMO_PASSWORD,
            email='*****@*****.**',
            description='Demo User',
            enabled=True)
        member_role = keystone_client.roles.find(name='Member')
        keystone_client.roles.grant(
            member_role,
            user=demo_user,
            project_domain=domain,
            project=project)
        demo_admin_user = keystone_client.users.create(
            DEMO_ADMIN_USER,
            domain=domain,
            project=project,
            password=DEMO_ADMIN_USER_PASSWORD,
            email='*****@*****.**',
            description='Demo Admin User',
            enabled=True)
        admin_role = keystone_client.roles.find(name='Admin')
        keystone_client.roles.grant(
            admin_role,
            user=demo_admin_user,
            domain=domain)
        keystone_client.roles.grant(
            member_role,
            user=demo_admin_user,
            project_domain=domain,
            project=project)
        keystone_client.roles.grant(
            admin_role,
            user=demo_admin_user,
            project_domain=domain,
            project=project)

    if (openstack_utils.get_os_release() <
            openstack_utils.get_os_release('trusty_mitaka')):
        # create only V2 user
        _v2()
        return

    if (openstack_utils.get_os_release() >=
        openstack_utils.get_os_release('trusty_mitaka') and
        openstack_utils.get_os_release() <
            openstack_utils.get_os_release('xenial_queens')):
        # create V2 and V3 user
        _v2()

        _singleton = BaseKeystoneTest()
        _singleton.setUpClass()
        # Explicitly set application name in case setup is called by a charm
        # under test other than keystone.
        with _singleton.config_change(
                {'preferred-api-version': _singleton.default_api_version},
                {'preferred-api-version': '3'}, application_name="keystone"):
            _v3()
    else:
        # create only V3 user
        _v3()
Пример #9
0
    def test_key_distribution_and_rotation(self):
        """Verify key rotation.

        Note that we make the assumption that test bundle configure
        `token-expiration` to 60 and that it takes > 60s from deployment
        completes until we get to this test.
        """
        if (openstack_utils.get_os_release() <
                openstack_utils.get_os_release('xenial_ocata')):
            logging.info('skipping test < xenial_ocata')
            return

        with self.pause_resume(['apache2']):
            KEY_KEY_REPOSITORY = 'key_repository'
            CREDENTIAL_KEY_REPOSITORY = '/etc/keystone/credential-keys/'
            FERNET_KEY_REPOSITORY = '/etc/keystone/fernet-keys/'

            # get key repostiroy from leader storage
            key_repository = json.loads(
                juju_utils.leader_get(self.application_name,
                                      KEY_KEY_REPOSITORY))
            # sort keys so we can compare it to on-disk repositories
            key_repository = json.loads(
                json.dumps(key_repository, sort_keys=True),
                object_pairs_hook=collections.OrderedDict)
            logging.info('key_repository: "{}"'.format(
                pprint.pformat(key_repository)))
            for repo in [CREDENTIAL_KEY_REPOSITORY, FERNET_KEY_REPOSITORY]:
                try:
                    for key_name, key in key_repository[repo].items():
                        if int(key_name) > 1:
                            # after initialization the repository contains the
                            # staging key (0) and the primary key (1).  After
                            # rotation the repository contains at least one key
                            # with higher index.
                            break
                    else:
                        # NOTE the charm should only rotate the fernet key
                        # repostiory and not rotate the credential key
                        # repository.
                        if repo == FERNET_KEY_REPOSITORY:
                            raise zaza_exceptions.KeystoneKeyRepositoryError(
                                'Keys in Fernet key repository has not been '
                                'rotated.')
                except KeyError:
                    raise zaza_exceptions.KeystoneKeyRepositoryError(
                        'Dict in leader setting "{}" does not contain key '
                        'repository "{}"'.format(KEY_KEY_REPOSITORY, repo))

            # get on-disk key repository from all units
            on_disk = {}
            units = zaza.model.get_units(self.application_name)
            for unit in units:
                on_disk[unit.entity_id] = {}
                for repo in [CREDENTIAL_KEY_REPOSITORY, FERNET_KEY_REPOSITORY]:
                    on_disk[unit.entity_id][repo] = {}
                    result = zaza.model.run_on_unit(
                        unit.entity_id, 'sudo ls -1 {}'.format(repo))
                    for key_name in result.get('Stdout').split():
                        result = zaza.model.run_on_unit(
                            unit.entity_id,
                            'sudo cat {}/{}'.format(repo, key_name))
                        on_disk[unit.entity_id][repo][key_name] = result.get(
                            'Stdout')
            # sort keys so we can compare it to leader storage repositories
            on_disk = json.loads(json.dumps(on_disk, sort_keys=True),
                                 object_pairs_hook=collections.OrderedDict)
            logging.info('on_disk: "{}"'.format(pprint.pformat(on_disk)))

            for unit in units:
                unit_repo = on_disk[unit.entity_id]
                lead_repo = key_repository
                if unit_repo != lead_repo:
                    raise zaza_exceptions.KeystoneKeyRepositoryError(
                        'expect: "{}" actual({}): "{}"'.format(
                            pprint.pformat(lead_repo), unit.entity_id,
                            pprint.pformat(unit_repo)))
                logging.info('"{}" == "{}"'.format(pprint.pformat(unit_repo),
                                                   pprint.pformat(lead_repo)))
Пример #10
0
    def test_end_user_acccess_and_token(self):
        """Verify regular end-user access resources and validate token data.

        In effect this also validates user creation, presence of standard
        roles (`_member_`, `Member`), effect of policy and configuration
        of `token-provider`.
        """
        def _validate_token_data(openrc):
            keystone_session = openstack_utils.get_keystone_session(openrc)
            keystone_client = openstack_utils.get_keystone_session_client(
                keystone_session)
            token = keystone_session.get_token()
            if (openstack_utils.get_os_release() <
                    openstack_utils.get_os_release('xenial_ocata')):
                if len(token) != 32:
                    raise zaza_exceptions.KeystoneWrongTokenProvider(
                        'We expected a UUID token and got this: "{}"'.format(
                            token))
            else:
                if len(token) < 180:
                    raise zaza_exceptions.KeystoneWrongTokenProvider(
                        'We expected a Fernet token and got this: "{}"'.format(
                            token))
            logging.info('token: "{}"'.format(pprint.pformat(token)))

            if (openstack_utils.get_os_release() <
                    openstack_utils.get_os_release('trusty_mitaka')):
                logging.info('skip: tokens.get_token_data() not allowed prior '
                             'to trusty_mitaka')
                return
            # get_token_data call also gets the service catalog
            token_data = keystone_client.tokens.get_token_data(token)
            if token_data.get('token', {}).get('catalog', None) is None:
                raise zaza_exceptions.KeystoneAuthorizationStrict(
                    # NOTE(fnordahl) the above call will probably throw a
                    # http.Forbidden exception, but just in case
                    'Regular end user not allowed to retrieve the service '
                    'catalog. ("{}")'.format(pprint.pformat(token_data)))
            logging.info('token_data: "{}"'.format(pprint.pformat(token_data)))

        if (openstack_utils.get_os_release() <
                openstack_utils.get_os_release('xenial_queens')):
            openrc = {
                'API_VERSION': 2,
                'OS_USERNAME': DEMO_USER,
                'OS_PASSWORD': DEMO_PASSWORD,
                'OS_TENANT_NAME': DEMO_TENANT,
            }
            for ip in self.keystone_ips:
                openrc.update(
                    {'OS_AUTH_URL': 'http://{}:5000/v2.0'.format(ip)})
                _validate_token_data(openrc)

        if (openstack_utils.get_os_release() >=
                openstack_utils.get_os_release('trusty_mitaka')):
            openrc = {
                'API_VERSION': 3,
                'OS_REGION_NAME': 'RegionOne',
                'OS_USER_DOMAIN_NAME': DEMO_DOMAIN,
                'OS_USERNAME': DEMO_USER,
                'OS_PASSWORD': DEMO_PASSWORD,
                'OS_PROJECT_DOMAIN_NAME': DEMO_DOMAIN,
                'OS_PROJECT_NAME': DEMO_PROJECT,
            }
            with self.config_change(
                {'preferred-api-version': self.default_api_version},
                {'preferred-api-version': '3'}):
                for ip in self.keystone_ips:
                    openrc.update(
                        {'OS_AUTH_URL': 'http://{}:5000/v3'.format(ip)})
                    _validate_token_data(openrc)
Пример #11
0
    def test_ceph_encryption(self):
        """Test Ceph encryption.

        Verify that the new disk is added with encryption by checking for
        Ceph's encryption keys directory.
        """
        current_release = zaza_openstack.get_os_release()
        trusty_mitaka = zaza_openstack.get_os_release('trusty_mitaka')
        if current_release >= trusty_mitaka:
            logging.warn("Skipping encryption test for Mitaka and higher")
            return
        unit_name = 'ceph-osd/0'
        set_default = {
            'osd-encrypt': 'False',
            'osd-devices': '/dev/vdb /srv/ceph',
        }
        set_alternate = {
            'osd-encrypt': 'True',
            'osd-devices': '/dev/vdb /srv/ceph /srv/ceph_encrypted',
        }
        juju_service = 'ceph-osd'
        logging.info('Making config change on {}...'.format(juju_service))
        mtime = zaza_model.get_unit_time(unit_name)

        file_mtime = None

        folder_name = '/etc/ceph/dmcrypt-keys/'
        with self.config_change(set_default, set_alternate):
            with tempfile.TemporaryDirectory() as tempdir:
                # Creating a temp dir to copy keys
                temp_folder = '/tmp/dmcrypt-keys'
                cmd = 'mkdir {}'.format(temp_folder)
                ret = zaza_model.run_on_unit(unit_name, cmd)
                logging.debug('Ret for cmd {} is {}'.format(cmd, ret))
                # Copy keys from /etc to /tmp
                cmd = 'sudo cp {}* {}'.format(folder_name, temp_folder)
                ret = zaza_model.run_on_unit(unit_name, cmd)
                logging.debug('Ret for cmd {} is {}'.format(cmd, ret))
                # Changing permissions to be able to SCP the files
                cmd = 'sudo chown -R ubuntu:ubuntu {}'.format(temp_folder)
                ret = zaza_model.run_on_unit(unit_name, cmd)
                logging.debug('Ret for cmd {} is {}'.format(cmd, ret))
                # SCP to retrieve all files in folder
                # -p: preserve timestamps
                source = '/tmp/dmcrypt-keys/*'
                zaza_model.scp_from_unit(unit_name=unit_name,
                                         source=source,
                                         destination=tempdir,
                                         scp_opts='-p')
                for elt in listdir(tempdir):
                    file_path = '/'.join([tempdir, elt])
                    if path.isfile(file_path):
                        file_mtime = path.getmtime(file_path)
                        if file_mtime:
                            break

        if not file_mtime:
            logging.warn('Could not determine mtime, assuming '
                         'folder does not exist')
            raise FileNotFoundError('folder does not exist')

        if file_mtime >= mtime:
            logging.info('Folder mtime is newer than provided mtime '
                         '(%s >= %s) on %s (OK)' %
                         (file_mtime, mtime, unit_name))
        else:
            logging.warn('Folder mtime is older than provided mtime'
                         '(%s < on %s) on %s' % (file_mtime, mtime, unit_name))
            raise Exception('Folder mtime is older than provided mtime')