# Copyright 2022 Red Hat, Inc. # All Rights Reserved. # # 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 neutron_lib import constants from neutron_tempest_plugin.common import ssh from neutron_tempest_plugin.common import utils from neutron_tempest_plugin import config from neutron_tempest_plugin.scenario import base from oslo_log import log import paramiko from tempest.lib.common.utils import data_utils from tempest.lib import decorators from tempest.lib import exceptions as lib_exc CONF = config.CONF LOG = log.getLogger(__name__) class NetworkDefaultSecGroupTest(base.BaseTempestTestCase): credentials = ['primary', 'admin'] required_extensions = ['router', 'security-group'] @classmethod def resource_setup(cls): super(NetworkDefaultSecGroupTest, cls).resource_setup() # setup basic topology for servers we can log into it cls.network = cls.create_network() cls.subnet = cls.create_subnet(cls.network, reserve_cidr=True) cls.router = cls.create_router_by_client() cls.create_router_interface(cls.router['id'], cls.subnet['id']) cls.keypair = cls.create_keypair() def create_vm_testing_sec_grp(self, num_servers=2, security_groups=None, separate_nets=False): servers, fips, server_ssh_clients = ([], [], []) for i in range(num_servers): # if separate_nets is True, each VM is connected to a different # tenant network if separate_nets and len(self.networks) < i + 1: new_network = self.create_network() new_subnet = self.create_subnet(new_network, reserve_cidr=True) self.create_router_interface(self.router['id'], new_subnet['id']) network_id = self.networks[-1]['id'] servers.append(self.create_server( flavor_ref=CONF.compute.flavor_ref, image_ref=CONF.compute.image_ref, key_name=self.keypair['name'], networks=[{'uuid': network_id}], security_groups=security_groups)) for i, server in enumerate(servers): if separate_nets: network_id = self.networks[i]['id'] else: network_id = self.networks[0]['id'] port = self.client.list_ports( network_id=network_id, device_id=server['server'][ 'id'])['ports'][0] fips.append(self.create_and_associate_floatingip(port['id'])) server_ssh_clients.append(ssh.Client( fips[i]['floating_ip_address'], CONF.validation.image_ssh_user, pkey=self.keypair['private_key'])) return server_ssh_clients, fips, servers def _check_connectivity_udp(self, ssh_server, ssh_client, server_ip, port_num, should_succeed=True, server_id=''): """Check connectivity to a UDP port :param ssh_client(RemoteClient): SSH client of the client side :param ssh_server(RemoteClient)): SSH client of the server side :param server_ip(dict): The IP address of the server's side (either FIP or internal IP) :param port_num(int): The UDP port :param should_succeed(boolean): Should the connection be successful :param server_id(str): ID of server where nc listens """ def _run_commands(cmd1, cmd2, cmd3, cmd4, cmd5): ssh_server.exec_command(cmd1) # cmd2 needs to be executed with execute_script method on RHEL8 # instances because it includes a command that runs in background # An exception will be launched for RHEL7 and cirros instances and # exec_command method will be used for them try: ssh_server.execute_script(cmd2) except paramiko.SSHException: ssh_server.exec_command(cmd2) ssh_client.exec_command(cmd3) output = ssh_server.exec_command(cmd5) if should_succeed: result = expected_output in output else: result = expected_output not in output ssh_server.exec_command(cmd4) return result # Creating a script on the server to write sg_check # to /tmp/1 file expected_output = "sg_check_" + server_id cmd1 = ("echo '#!/bin/sh\necho {expected_output} > /tmp/{server_ip}' " "> /tmp/{server_ip}.sh && chmod +x /tmp/{server_ip}.sh && " "touch /tmp/{server_ip}".format( expected_output=expected_output, server_ip=server_ip)) # Listen on UDP port and execute the script when a connection is made cmd2 = ("sudo nc -l -u -p {port_num} -e /tmp/{server_ip}.sh > " "/dev/null &\n".format(port_num=port_num, server_ip=server_ip)) # Make sure that ncat listener process is running # It takes a bit on RHEL to launch a ncat listener process cmd2 += ("for i in `seq 1 5`; do\n" "$(PATH=$PATH:/usr/sbin which pidof) nc && exit 0\n" "sleep 0.2\n" "done\n" "exit 1\n") # Initiate a UDP connection cmd3 = "echo sg | nc -w 10 -u %s %s" % (server_ip, port_num) # Kill the nc process to avoid process creation failure cmd4 = 'sudo kill -9 $($(PATH=$PATH:/usr/sbin which pidof) nc) || true' cmd5 = "cat /tmp/{server_ip}".format(server_ip=server_ip) try: result = _run_commands(cmd1, cmd2, cmd3, cmd4, cmd5) except base.SSH_EXC_TUPLE as ssh_e: LOG.debug(ssh_e) self._log_console_output() self._log_local_network_status() raise return result @decorators.idempotent_id('921f91f8-7734-4c42-8934-a1438d45747b') def test_securitygroup_udp(self): """Test connectivity between VMs when they have security group with UDP port configured """ PORT_UDP = 60000 secgrp_name = 'udp-secgrp' secgrp = self.os_primary.network_client.create_security_group( name=secgrp_name) self.security_groups.append(secgrp['security_group']) self.create_loginable_secgroup_rule( secgroup_id=secgrp['security_group']['id']) ssh_clients, fips, servers = self.create_vm_testing_sec_grp( security_groups=[{'name': secgrp_name}]) # Testing the negative case before adding UDP port sec group rule # Using FIP utils.wait_until_true( lambda: self._check_connectivity_udp( ssh_server=ssh_clients[1], ssh_client=ssh_clients[0], server_ip=fips[1]['floating_ip_address'], port_num=PORT_UDP, should_succeed=False, server_id=servers[1]['server']['id']), exception=RuntimeError( "Timed out waiting for message from server {!r} ".format( servers[1]['server']['id']))) # Testing the negative case before adding UDP port sec group rule # Using internal IP utils.wait_until_true( lambda: self._check_connectivity_udp( ssh_server=ssh_clients[1], ssh_client=ssh_clients[0], server_ip=fips[1]['fixed_ip_address'], port_num=PORT_UDP, should_succeed=False, server_id=servers[1]['server']['id']), exception=RuntimeError( "Timed out waiting for message from server {!r} ".format( servers[1]['server']['id']))) rulesets = [{'protocol': constants.PROTO_NAME_UDP, 'port_range_min': PORT_UDP, 'port_range_max': PORT_UDP, 'direction': 'ingress', 'remote_ip_prefix': '0.0.0.0/0'}] self.create_secgroup_rules(rulesets, secgrp['security_group']['id']) # Testing the UDP connectivity after adding UDP port sec group rule # Using FIP utils.wait_until_true( lambda: self._check_connectivity_udp( ssh_server=ssh_clients[0], ssh_client=ssh_clients[1], server_ip=fips[0]['floating_ip_address'], port_num=PORT_UDP, should_succeed=True, server_id=servers[0]['server']['id']), exception=RuntimeError( "Timed out waiting for message from server {!r} ".format( servers[0]['server']['id']))) # Testing the UDP connectivity after adding UDP port sec group rule # Using internal IP utils.wait_until_true( lambda: self._check_connectivity_udp( ssh_server=ssh_clients[0], ssh_client=ssh_clients[1], server_ip=fips[0]['fixed_ip_address'], port_num=PORT_UDP, should_succeed=True, server_id=servers[0]['server']['id']), exception=RuntimeError( "Timed out waiting for message from server {!r} ".format( servers[0]['server']['id']))) @decorators.idempotent_id('db6ab68e-0b9b-46e8-9b15-c9612da57a08') def test_securitygroup_udp_dns(self): """Test connectivity between VMs on two different tenant networks through the DNS port (UDP port 53) """ PORT_UDP = 53 secgrp_name = 'udp-dns-secgrp' secgrp = self.os_primary.network_client.create_security_group( name=secgrp_name) self.security_groups.append(secgrp['security_group']) self.create_loginable_secgroup_rule( secgroup_id=secgrp['security_group']['id']) ssh_clients, fips, servers = self.create_vm_testing_sec_grp( security_groups=[{'name': secgrp_name}], separate_nets=True) # Testing the negative case before adding UDP port sec group rule # Using FIP utils.wait_until_true( lambda: self._check_connectivity_udp( ssh_server=ssh_clients[1], ssh_client=ssh_clients[0], server_ip=fips[1]['floating_ip_address'], port_num=PORT_UDP, should_succeed=False, server_id=servers[1]['server']['id']), exception=RuntimeError( "Timed out waiting for message from server {!r} ".format( servers[1]['server']['id']))) # Testing the negative case before adding UDP port sec group rule # Using internal IP utils.wait_until_true( lambda: self._check_connectivity_udp( ssh_server=ssh_clients[1], ssh_client=ssh_clients[0], server_ip=fips[1]['fixed_ip_address'], port_num=PORT_UDP, should_succeed=False, server_id=servers[1]['server']['id']), exception=RuntimeError( "Timed out waiting for message from server {!r} ".format( servers[1]['server']['id']))) rulesets = [{'protocol': constants.PROTO_NAME_UDP, 'port_range_min': PORT_UDP, 'port_range_max': PORT_UDP, 'direction': 'ingress', 'remote_ip_prefix': '0.0.0.0/0'}] self.create_secgroup_rules(rulesets, secgrp['security_group']['id']) # Testing the UDP connectivity after adding UDP port sec group rule # Using FIP utils.wait_until_true( lambda: self._check_connectivity_udp( ssh_server=ssh_clients[0], ssh_client=ssh_clients[1], server_ip=fips[0]['floating_ip_address'], port_num=PORT_UDP, should_succeed=True, server_id=servers[0]['server']['id']), exception=RuntimeError( "Timed out waiting for message from server {!r} ".format( servers[0]['server']['id']))) # Testing the UDP connectivity after adding UDP port sec group rule # Using internal IP utils.wait_until_true( lambda: self._check_connectivity_udp( ssh_server=ssh_clients[0], ssh_client=ssh_clients[1], server_ip=fips[0]['fixed_ip_address'], port_num=PORT_UDP, should_succeed=True, server_id=servers[0]['server']['id']), exception=RuntimeError( "Timed out waiting for message from server {!r} ".format( servers[0]['server']['id']))) @decorators.attr(type='negative') @decorators.idempotent_id('cc5f679e-7196-4e68-8a82-10b73bf11e48') def test_remove_security_group_negative(self): # Create "existing security group" existing_sg = self.os_primary.network_client.create_security_group( name=data_utils.rand_name('test_sg')) # Create "not existing security group" not_existing_sg = self.os_primary.network_client.create_security_group( name=data_utils.rand_name('test_sg')) self.delete_security_group(not_existing_sg['security_group']) # Create the VM server_args = { 'flavor_ref': CONF.compute.flavor_ref, 'image_ref': CONF.compute.image_ref, 'key_name': self.keypair['name'], 'networks': [{'uuid': self.network['id']}] } server = self.create_server(**server_args) LOG.info('test_negative server: %s', server['server']) # Try to remove "Existing SG ID" that is not associated to VM Network # from VM. self.assertRaises( lib_exc.NotFound, self.os_primary.servers_client.remove_security_group, server_id=server['server']['id'], name=existing_sg['security_group']['id']) # Try to remove "Not Existing SG ID" from VM. self.assertRaises( lib_exc.NotFound, self.os_primary.servers_client.remove_security_group, server_id=server['server']['id'], name=not_existing_sg['security_group']['id'])