def test_get_keystone_session(self): self.patch_object(openstack_utils, "session") self.patch_object(openstack_utils, "v2") _auth = mock.MagicMock() self.v2.Password.return_value = _auth _openrc = { "OS_AUTH_URL": "https://keystone:5000", "OS_USERNAME": "******", "OS_PASSWORD": "******", "OS_TENANT_NAME": "tenant", } openstack_utils.get_keystone_session(_openrc) self.session.Session.assert_called_once_with(auth=_auth, verify=None)
def _find_keystone_v3_group(self, group, domain): """Find a group within a specified keystone v3 domain. :param str group: Group to search for in keystone :param str domain: group selected from which domain :return: return group if found :rtype: Optional[str] """ for ip in self.keystone_ips: logging.info('Keystone IP {}'.format(ip)) session = openstack_utils.get_keystone_session( openstack_utils.get_overcloud_auth(address=ip)) client = openstack_utils.get_keystone_session_client(session) domain_groups = client.groups.list( domain=client.domains.find(name=domain).id ) for searched_group in domain_groups: if searched_group.name.lower() == group.lower(): return searched_group logging.debug( "Group {} was not found. Returning None.".format(group) ) return None
def _find_keystone_v3_user(self, username, domain, group=None): """Find a user within a specified keystone v3 domain. :param str username: Username to search for in keystone :param str domain: username selected from which domain :param str group: group to search for in keystone for group membership :return: return username if found :rtype: Optional[str] """ for ip in self.keystone_ips: logging.info('Keystone IP {}'.format(ip)) session = openstack_utils.get_keystone_session( openstack_utils.get_overcloud_auth(address=ip)) client = openstack_utils.get_keystone_session_client(session) if group is None: domain_users = client.users.list( domain=client.domains.find(name=domain).id, ) else: domain_users = client.users.list( domain=client.domains.find(name=domain).id, group=self._find_keystone_v3_group(group, domain).id, ) usernames = [u.name.lower() for u in domain_users] if username.lower() in usernames: return username logging.debug( "User {} was not found. Returning None.".format(username) ) return None
def setup_for_attempt_operation(self, ip): """Create a loadbalancer. This is necessary so that the attempt is to show the load-balancer and this is an operator that the policy can stop. Unfortunately, octavia, whilst it has a policy for just listing load-balancers, unfortunately, it doesn't work; whereas showing the load-balancer can be stopped. NB this only works if the setup phase of the octavia tests have been completed. :param ip: the ip of for keystone. :type ip: str """ logging.info("Setting up loadbalancer.") auth = openstack_utils.get_overcloud_auth(address=ip) sess = openstack_utils.get_keystone_session(auth) octavia_client = openstack_utils.get_octavia_session_client(sess) neutron_client = openstack_utils.get_neutron_session_client(sess) if openstack_utils.dvr_enabled(): network_name = 'private_lb_fip_network' else: network_name = 'private' resp = neutron_client.list_networks(name=network_name) vip_subnet_id = resp['networks'][0]['subnets'][0] res = octavia_client.load_balancer_create( json={ 'loadbalancer': { 'description': 'Created by Zaza', 'admin_state_up': True, 'vip_subnet_id': vip_subnet_id, 'name': 'zaza-lb-0', }}) self.lb_id = res['loadbalancer']['id'] # now wait for it to get to the active state @tenacity.retry(wait=tenacity.wait_fixed(1), reraise=True, stop=tenacity.stop_after_delay(900)) def wait_for_lb_resource(client, resource_id): resp = client.load_balancer_show(resource_id) logging.info(resp['provisioning_status']) assert resp['provisioning_status'] == 'ACTIVE', ( 'load balancer resource has not reached ' 'expected provisioning status: {}' .format(resp)) return resp logging.info('Awaiting loadbalancer to reach provisioning_status ' '"ACTIVE"') resp = wait_for_lb_resource(octavia_client, self.lb_id) logging.info(resp) logging.info("Setup loadbalancer complete.")
def get_keystone_session_admin_user(self, ip): """Return the keystone session admin user. :param ip: the IP address to get the session against. :type ip: str :returns: a keystone session to the IP address :rtype: keystoneauth1.session.Session """ return openstack_utils.get_keystone_session( openstack_utils.get_overcloud_auth(address=ip))
def test_rotate_admin_password(self): """Verify action used to rotate admin user's password.""" ADMIN_PASSWD = 'admin_passwd' old_passwd = juju_utils.leader_get(self.application_name, ADMIN_PASSWD) # test access using the old password with self.v3_keystone_preferred(): for ip in self.keystone_ips: try: ks_session = openstack_utils.get_keystone_session( openstack_utils.get_overcloud_auth(address=ip)) ks_client = openstack_utils.get_keystone_session_client( ks_session) ks_client.users.list() except keystoneauth1.exceptions.http.Forbidden: raise zaza_exceptions.KeystoneAuthorizationStrict( 'Keystone auth with old password FAILED.') # run the action to rotate the password zaza.model.run_action_on_leader( self.application_name, 'rotate-admin-password', ) # test access using the new password with self.v3_keystone_preferred(): for ip in self.keystone_ips: try: ks_session = openstack_utils.get_keystone_session( openstack_utils.get_overcloud_auth(address=ip)) ks_client = openstack_utils.get_keystone_session_client( ks_session) ks_client.users.list() except keystoneauth1.exceptions.http.Forbidden: raise zaza_exceptions.KeystoneAuthorizationStrict( 'Keystone auth with new password FAILED.') # make sure the password was actually changed new_passwd = juju_utils.leader_get(self.application_name, ADMIN_PASSWD) assert old_passwd != new_passwd
def test_backward_compatible_uuid_for_default_domain(self): """Check domain named ``default`` literally has ``default`` as ID. Some third party software chooses to hard code this value for some inexplicable reason. """ with self.v3_keystone_preferred(): ks_session = openstack_utils.get_keystone_session( openstack_utils.get_overcloud_auth()) ks_client = openstack_utils.get_keystone_session_client(ks_session) domain = ks_client.domains.get('default') logging.info(pprint.pformat(domain)) assert domain.id == 'default'
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')
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')
def _get_keystone_session(self, ip, openrc, scope='DOMAIN'): """Return the keystone session for the IP address passed. :param ip: the IP address to get the session against. :type ip: str :param openrc: the params to authenticate with. :type openrc: Dict[str, str] :param scope: the scope of the token :type scope: str :returns: a keystone session to the IP address :rtype: keystoneauth1.session.Session """ logging.info('Authentication for {} on keystone IP {}'.format( openrc['OS_USERNAME'], ip)) if self.tls_rid: openrc['OS_CACERT'] = openstack_utils.get_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=scope) return keystone_session
def _validate_token_data(openrc): if self.tls_rid: openrc['OS_CACERT'] = openstack_utils.get_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) 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)))
def cleanup_for_attempt_operation(self, ip): """Remove the loadbalancer. :param ip: the ip of for keystone. :type ip: str """ logging.info("Deleting loadbalancer {}.".format(self.lb_id)) auth = openstack_utils.get_overcloud_auth(address=ip) sess = openstack_utils.get_keystone_session(auth) octavia_client = openstack_utils.get_octavia_session_client(sess) octavia_client.load_balancer_delete(self.lb_id) logging.info("Deleting loadbalancer in progress ...") @tenacity.retry(wait=tenacity.wait_fixed(1), reraise=True, stop=tenacity.stop_after_delay(900)) def wait_til_deleted(client, lb_id): lb_list = client.load_balancer_list() ids = [lb['id'] for lb in lb_list['loadbalancers']] assert lb_id not in ids, 'load balancer still deleting' wait_til_deleted(octavia_client, self.lb_id) logging.info("Deleted loadbalancer.")
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.Unauthorized as e: # This is to handle LP bug 1834287. We handle this error # separately because it's a case of the client not being # authenticated whereas the test is about checking if the # client is authenticated but not authorized. We catch it # so that we can log it properly and then re-raise it to # indicate an underlying error that this test is unable # to handle. # # Note that without catching this, the test will fail # anyway but why it failed is not immediately obvious. # This puts the reason front and center for the sake of # efficiency logging.error('Client is not authenticated. Test cannot ' 'continue...ERROR ({})'.format(e)) raise e 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')
def test_disable_service(self): """Test that service can be disabled.""" logging.info("Doing policyd override to disable listing domains") self._set_policy_with({'rule.yaml': "{'identity:list_services': '!'}"}) # verify that the policy.d override does disable the endpoint with self.config_change( { 'preferred-api-version': self.default_api_version, 'use-policyd-override': 'False' }, { 'preferred-api-version': '3', 'use-policyd-override': 'True' }, application_name="keystone"): zaza_model.block_until_all_units_idle() for ip in self.keystone_ips: try: logging.info('keystone IP {}'.format(ip)) openrc = { 'API_VERSION': 3, 'OS_USERNAME': ch_keystone.DEMO_ADMIN_USER, 'OS_PASSWORD': ch_keystone.DEMO_ADMIN_USER_PASSWORD, 'OS_AUTH_URL': 'http://{}:5000/v3'.format(ip), 'OS_USER_DOMAIN_NAME': ch_keystone.DEMO_DOMAIN, 'OS_DOMAIN_NAME': ch_keystone.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)) keystone_client.services.list() raise zaza_exceptions.PolicydError( 'Retrieve service list as admin with project scoped ' 'token passed and should have failed. IP = {}'.format( ip)) except keystoneauth1.exceptions.http.Forbidden: logging.info("keystone IP:{} policyd override disabled " "services listing by demo user".format(ip)) # now verify (with the config off) that we can actually access # these points with self.config_change( {'preferred-api-version': self.default_api_version}, {'preferred-api-version': '3'}, application_name="keystone"): zaza_model.block_until_all_units_idle() for ip in self.keystone_ips: try: logging.info('keystone IP {}'.format(ip)) openrc = { 'API_VERSION': 3, 'OS_USERNAME': ch_keystone.DEMO_ADMIN_USER, 'OS_PASSWORD': ch_keystone.DEMO_ADMIN_USER_PASSWORD, 'OS_AUTH_URL': 'http://{}:5000/v3'.format(ip), 'OS_USER_DOMAIN_NAME': ch_keystone.DEMO_DOMAIN, 'OS_DOMAIN_NAME': ch_keystone.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)) keystone_client.services.list() logging.info("keystone IP:{} without policyd override " "services list working".format(ip)) except keystoneauth1.exceptions.http.Forbidden: raise zaza_exceptions.PolicydError( 'Retrieve services list as demo user with project ' 'scoped token passed and should have passed. IP = {}'. format(ip)) logging.info('OK')