blob: d4de109a8bae00322ec88116dc64d8f2077f31d2 [file] [log] [blame]
Yair Fried4d7efa62013-11-17 17:12:29 +02001# Copyright 2013 Red Hat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Andrea Frittolif9cde7e2014-02-18 09:57:04 +000016from tempest import clients
Yair Fried4d7efa62013-11-17 17:12:29 +020017from tempest.common import debug
18from tempest.common.utils import data_utils
Matthew Treinish6c072292014-01-29 19:15:52 +000019from tempest import config
Yair Fried4d7efa62013-11-17 17:12:29 +020020from tempest import exceptions
21from tempest.openstack.common import log as logging
22from tempest.scenario import manager
Yair Fried4d7efa62013-11-17 17:12:29 +020023from tempest.test import attr
24from tempest.test import call_until_true
25from tempest.test import services
26
Matthew Treinish6c072292014-01-29 19:15:52 +000027CONF = config.CONF
28
Yair Fried4d7efa62013-11-17 17:12:29 +020029LOG = logging.getLogger(__name__)
30
31
Yair Friedbf2e2c42014-01-28 12:06:38 +020032class TestSecurityGroupsBasicOps(manager.NetworkScenarioTest):
Yair Fried4d7efa62013-11-17 17:12:29 +020033
34 """
35 This test suite assumes that Nova has been configured to
36 boot VM's with Neutron-managed networking, and attempts to
37 verify cross tenant connectivity as follows
38
39 ssh:
40 in order to overcome "ip namespace", each tenant has an "access point"
41 VM with floating-ip open to incoming ssh connection allowing network
42 commands (ping/ssh) to be executed from within the
43 tenant-network-namespace
44 Tempest host performs key-based authentication to the ssh server via
45 floating IP address
46
47 connectivity test is done by pinging destination server via source server
48 ssh connection.
49 success - ping returns
50 failure - ping_timeout reached
51
52 setup:
Yair Friedbf2e2c42014-01-28 12:06:38 +020053 for primary tenant:
Yair Fried4d7efa62013-11-17 17:12:29 +020054 1. create a network&subnet
55 2. create a router (if public router isn't configured)
56 3. connect tenant network to public network via router
57 4. create an access point:
58 a. a security group open to incoming ssh connection
59 b. a VM with a floating ip
60 5. create a general empty security group (same as "default", but
61 without rules allowing in-tenant traffic)
Yair Fried4d7efa62013-11-17 17:12:29 +020062
63 tests:
64 1. _verify_network_details
65 2. _verify_mac_addr: for each access point verify that
66 (subnet, fix_ip, mac address) are as defined in the port list
67 3. _test_in_tenant_block: test that in-tenant traffic is disabled
68 without rules allowing it
69 4. _test_in_tenant_allow: test that in-tenant traffic is enabled
70 once an appropriate rule has been created
71 5. _test_cross_tenant_block: test that cross-tenant traffic is disabled
72 without a rule allowing it on destination tenant
73 6. _test_cross_tenant_allow:
74 * test that cross-tenant traffic is enabled once an appropriate
75 rule has been created on destination tenant.
76 * test that reverse traffic is still blocked
77 * test than revesre traffic is enabled once an appropriate rule has
78 been created on source tenant
79
80 assumptions:
Yair Friedbf2e2c42014-01-28 12:06:38 +020081 1. alt_tenant/user existed and is different from primary_tenant/user
Yair Fried4d7efa62013-11-17 17:12:29 +020082 2. Public network is defined and reachable from the Tempest host
83 3. Public router can either be:
84 * defined, in which case all tenants networks can connect directly
85 to it, and cross tenant check will be done on the private IP of the
86 destination tenant
87 or
88 * not defined (empty string), in which case each tanant will have
89 its own router connected to the public network
90 """
91
92 class TenantProperties():
Yair Friedbf2e2c42014-01-28 12:06:38 +020093 """
Yair Fried4d7efa62013-11-17 17:12:29 +020094 helper class to save tenant details
95 id
96 credentials
97 network
98 subnet
99 security groups
100 servers
101 access point
Yair Friedbf2e2c42014-01-28 12:06:38 +0200102 """
Yair Fried4d7efa62013-11-17 17:12:29 +0200103
104 def __init__(self, tenant_id, tenant_user, tenant_pass, tenant_name):
Andrea Frittolif9cde7e2014-02-18 09:57:04 +0000105 self.manager = clients.OfficialClientManager(
Yair Fried4d7efa62013-11-17 17:12:29 +0200106 tenant_user,
107 tenant_pass,
108 tenant_name
109 )
Yair Friedbf2e2c42014-01-28 12:06:38 +0200110 self.keypair = None
Yair Fried4d7efa62013-11-17 17:12:29 +0200111 self.tenant_id = tenant_id
112 self.tenant_name = tenant_name
113 self.tenant_user = tenant_user
114 self.tenant_pass = tenant_pass
115 self.network = None
116 self.subnet = None
117 self.router = None
118 self.security_groups = {}
119 self.servers = list()
120
Yair Friedbf2e2c42014-01-28 12:06:38 +0200121 def set_network(self, network, subnet, router):
Yair Fried4d7efa62013-11-17 17:12:29 +0200122 self.network = network
123 self.subnet = subnet
124 self.router = router
125
126 def _get_tenant_credentials(self):
127 return self.tenant_user, self.tenant_pass, self.tenant_name
128
129 @classmethod
130 def check_preconditions(cls):
Yair Friedbf2e2c42014-01-28 12:06:38 +0200131 super(TestSecurityGroupsBasicOps, cls).check_preconditions()
Yair Fried4d7efa62013-11-17 17:12:29 +0200132 if (cls.alt_tenant_id is None) or (cls.tenant_id is cls.alt_tenant_id):
133 msg = 'No alt_tenant defined'
134 cls.enabled = False
135 raise cls.skipException(msg)
Matthew Treinish6c072292014-01-29 19:15:52 +0000136 if not (CONF.network.tenant_networks_reachable or
137 CONF.network.public_network_id):
Yair Fried4d7efa62013-11-17 17:12:29 +0200138 msg = ('Either tenant_networks_reachable must be "true", or '
139 'public_network_id must be defined.')
140 cls.enabled = False
141 raise cls.skipException(msg)
142
143 @classmethod
144 def setUpClass(cls):
Yair Friedbf2e2c42014-01-28 12:06:38 +0200145 super(TestSecurityGroupsBasicOps, cls).setUpClass()
Yair Fried769bbff2013-12-18 16:33:17 +0200146 alt_creds = cls.alt_credentials()
Yair Fried4d7efa62013-11-17 17:12:29 +0200147 cls.alt_tenant_id = cls.manager._get_identity_client(
Yair Fried769bbff2013-12-18 16:33:17 +0200148 *alt_creds
Yair Fried4d7efa62013-11-17 17:12:29 +0200149 ).tenant_id
150 cls.check_preconditions()
151 # TODO(mnewby) Consider looking up entities as needed instead
152 # of storing them as collections on the class.
Yair Fried4d7efa62013-11-17 17:12:29 +0200153 cls.networks = []
154 cls.subnets = []
155 cls.routers = []
Yair Fried4d7efa62013-11-17 17:12:29 +0200156 cls.floating_ips = {}
157 cls.tenants = {}
Yair Friedbf2e2c42014-01-28 12:06:38 +0200158 cls.primary_tenant = cls.TenantProperties(cls.tenant_id,
159 *cls.credentials())
160 cls.alt_tenant = cls.TenantProperties(cls.alt_tenant_id,
161 *alt_creds)
162 for tenant in [cls.primary_tenant, cls.alt_tenant]:
Yair Fried4d7efa62013-11-17 17:12:29 +0200163 cls.tenants[tenant.tenant_id] = tenant
Yair Friedbf2e2c42014-01-28 12:06:38 +0200164 cls.floating_ip_access = not CONF.network.public_router_id
Yair Fried4d7efa62013-11-17 17:12:29 +0200165
Yair Friedbf2e2c42014-01-28 12:06:38 +0200166 def cleanup_wrapper(self, resource):
167 self.cleanup_resource(resource, self.__class__.__name__)
168
169 def setUp(self):
170 super(TestSecurityGroupsBasicOps, self).setUp()
171 self._deploy_tenant(self.primary_tenant)
172 self._verify_network_details(self.primary_tenant)
173 self._verify_mac_addr(self.primary_tenant)
Yair Fried4d7efa62013-11-17 17:12:29 +0200174
175 def _create_tenant_keypairs(self, tenant_id):
Yair Friedbf2e2c42014-01-28 12:06:38 +0200176 keypair = self.create_keypair(
Yair Fried4d7efa62013-11-17 17:12:29 +0200177 name=data_utils.rand_name('keypair-smoke-'))
Yair Friedbf2e2c42014-01-28 12:06:38 +0200178 self.addCleanup(self.cleanup_wrapper, keypair)
179 self.tenants[tenant_id].keypair = keypair
Yair Fried4d7efa62013-11-17 17:12:29 +0200180
181 def _create_tenant_security_groups(self, tenant):
Yair Fried4d7efa62013-11-17 17:12:29 +0200182 access_sg = self._create_empty_security_group(
183 namestart='secgroup_access-',
184 tenant_id=tenant.tenant_id
185 )
Yair Friedbf2e2c42014-01-28 12:06:38 +0200186 self.addCleanup(self.cleanup_wrapper, access_sg)
187
Yair Fried4d7efa62013-11-17 17:12:29 +0200188 # don't use default secgroup since it allows in-tenant traffic
189 def_sg = self._create_empty_security_group(
190 namestart='secgroup_general-',
191 tenant_id=tenant.tenant_id
192 )
Yair Friedbf2e2c42014-01-28 12:06:38 +0200193 self.addCleanup(self.cleanup_wrapper, def_sg)
Yair Fried4d7efa62013-11-17 17:12:29 +0200194 tenant.security_groups.update(access=access_sg, default=def_sg)
195 ssh_rule = dict(
196 protocol='tcp',
197 port_range_min=22,
198 port_range_max=22,
199 direction='ingress',
200 )
Yair Friedbf2e2c42014-01-28 12:06:38 +0200201 rule = self._create_security_group_rule(secgroup=access_sg,
202 **ssh_rule)
203 self.addCleanup(self.cleanup_wrapper, rule)
Yair Fried4d7efa62013-11-17 17:12:29 +0200204
205 def _verify_network_details(self, tenant):
206 # Checks that we see the newly created network/subnet/router via
207 # checking the result of list_[networks,routers,subnets]
208 # Check that (router, subnet) couple exist in port_list
209 seen_nets = self._list_networks()
210 seen_names = [n['name'] for n in seen_nets]
211 seen_ids = [n['id'] for n in seen_nets]
212
213 self.assertIn(tenant.network.name, seen_names)
214 self.assertIn(tenant.network.id, seen_ids)
215
216 seen_subnets = [(n['id'], n['cidr'], n['network_id'])
217 for n in self._list_subnets()]
218 mysubnet = (tenant.subnet.id, tenant.subnet.cidr, tenant.network.id)
219 self.assertIn(mysubnet, seen_subnets)
220
221 seen_routers = self._list_routers()
222 seen_router_ids = [n['id'] for n in seen_routers]
223 seen_router_names = [n['name'] for n in seen_routers]
224
225 self.assertIn(tenant.router.name, seen_router_names)
226 self.assertIn(tenant.router.id, seen_router_ids)
227
228 myport = (tenant.router.id, tenant.subnet.id)
229 router_ports = [(i['device_id'], i['fixed_ips'][0]['subnet_id']) for i
230 in self.network_client.list_ports()['ports']
231 if i['device_owner'] == 'network:router_interface']
232
233 self.assertIn(myport, router_ports)
234
235 def _create_server(self, name, tenant, security_groups=None):
236 """
237 creates a server and assigns to security group
238 """
239 self._set_compute_context(tenant)
240 if security_groups is None:
241 security_groups = [tenant.security_groups['default'].name]
242 create_kwargs = {
243 'nics': [
244 {'net-id': tenant.network.id},
245 ],
Yair Friedbf2e2c42014-01-28 12:06:38 +0200246 'key_name': tenant.keypair.name,
Yair Fried4d7efa62013-11-17 17:12:29 +0200247 'security_groups': security_groups,
248 'tenant_id': tenant.tenant_id
249 }
250 server = self.create_server(name=name, create_kwargs=create_kwargs)
Yair Friedbf2e2c42014-01-28 12:06:38 +0200251 self.addCleanup(self.cleanup_wrapper, server)
Yair Fried4d7efa62013-11-17 17:12:29 +0200252 return server
253
254 def _create_tenant_servers(self, tenant, num=1):
255 for i in range(num):
256 name = 'server-{tenant}-gen-{num}-'.format(
257 tenant=tenant.tenant_name,
258 num=i
259 )
260 name = data_utils.rand_name(name)
261 server = self._create_server(name, tenant)
Yair Fried4d7efa62013-11-17 17:12:29 +0200262 tenant.servers.append(server)
263
264 def _set_access_point(self, tenant):
265 """
266 creates a server in a secgroup with rule allowing external ssh
267 in order to access tenant internal network
268 workaround ip namespace
269 """
270 secgroups = [sg.name for sg in tenant.security_groups.values()]
271 name = 'server-{tenant}-access_point-'.format(tenant=tenant.tenant_name
272 )
273 name = data_utils.rand_name(name)
274 server = self._create_server(name, tenant,
275 security_groups=secgroups)
Yair Fried4d7efa62013-11-17 17:12:29 +0200276 tenant.access_point = server
277 self._assign_floating_ips(server)
278
279 def _assign_floating_ips(self, server):
Matthew Treinish6c072292014-01-29 19:15:52 +0000280 public_network_id = CONF.network.public_network_id
Yair Fried4d7efa62013-11-17 17:12:29 +0200281 floating_ip = self._create_floating_ip(server, public_network_id)
Yair Friedbf2e2c42014-01-28 12:06:38 +0200282 self.addCleanup(self.cleanup_wrapper, floating_ip)
Yair Fried4d7efa62013-11-17 17:12:29 +0200283 self.floating_ips.setdefault(server, floating_ip)
284
285 def _create_tenant_network(self, tenant):
Yair Friedbf2e2c42014-01-28 12:06:38 +0200286 network, subnet, router = self._create_networks(tenant.tenant_id)
287 for r in [network, router, subnet]:
288 self.addCleanup(self.cleanup_wrapper, r)
289 tenant.set_network(network, subnet, router)
Yair Fried4d7efa62013-11-17 17:12:29 +0200290
291 def _set_compute_context(self, tenant):
292 self.compute_client = tenant.manager.compute_client
293 return self.compute_client
294
295 def _deploy_tenant(self, tenant_or_id):
296 """
297 creates:
298 network
299 subnet
300 router (if public not defined)
301 access security group
302 access-point server
Yair Fried4d7efa62013-11-17 17:12:29 +0200303 """
304 if not isinstance(tenant_or_id, self.TenantProperties):
305 tenant = self.tenants[tenant_or_id]
306 tenant_id = tenant_or_id
307 else:
308 tenant = tenant_or_id
309 tenant_id = tenant.tenant_id
310 self._set_compute_context(tenant)
311 self._create_tenant_keypairs(tenant_id)
312 self._create_tenant_network(tenant)
313 self._create_tenant_security_groups(tenant)
Yair Fried4d7efa62013-11-17 17:12:29 +0200314 self._set_access_point(tenant)
315
316 def _get_server_ip(self, server, floating=False):
Yair Friedbf2e2c42014-01-28 12:06:38 +0200317 """
Yair Fried4d7efa62013-11-17 17:12:29 +0200318 returns the ip (floating/internal) of a server
Yair Friedbf2e2c42014-01-28 12:06:38 +0200319 """
Yair Fried4d7efa62013-11-17 17:12:29 +0200320 if floating:
321 return self.floating_ips[server].floating_ip_address
322 else:
323 network_name = self.tenants[server.tenant_id].network.name
324 return server.networks[network_name][0]
325
326 def _connect_to_access_point(self, tenant):
327 """
328 create ssh connection to tenant access point
329 """
330 access_point_ssh = \
331 self.floating_ips[tenant.access_point].floating_ip_address
Yair Friedbf2e2c42014-01-28 12:06:38 +0200332 private_key = tenant.keypair.private_key
Yair Fried4d7efa62013-11-17 17:12:29 +0200333 access_point_ssh = self._ssh_to_server(access_point_ssh,
334 private_key=private_key)
335 return access_point_ssh
336
337 def _test_remote_connectivity(self, source, dest, should_succeed=True):
338 """
339 check ping server via source ssh connection
340
341 :param source: RemoteClient: an ssh connection from which to ping
342 :param dest: and IP to ping against
343 :param should_succeed: boolean should ping succeed or not
344 :returns: boolean -- should_succeed == ping
345 :returns: ping is false if ping failed
346 """
347 def ping_remote():
348 try:
349 source.ping_host(dest)
350 except exceptions.SSHExecCommandFailed as ex:
351 LOG.debug(ex)
352 return not should_succeed
353 return should_succeed
354
355 return call_until_true(ping_remote,
Matthew Treinish6c072292014-01-29 19:15:52 +0000356 CONF.compute.ping_timeout,
Yair Fried4d7efa62013-11-17 17:12:29 +0200357 1)
358
359 def _check_connectivity(self, access_point, ip, should_succeed=True):
360 if should_succeed:
361 msg = "Timed out waiting for %s to become reachable" % ip
362 else:
363 # todo(yfried): remove this line when bug 1252620 is fixed
364 return True
365 msg = "%s is reachable" % ip
366 try:
367 self.assertTrue(self._test_remote_connectivity(access_point, ip,
368 should_succeed),
369 msg)
370 except Exception:
371 debug.log_ip_ns()
Yair Fried4d7efa62013-11-17 17:12:29 +0200372 raise
373
374 def _test_in_tenant_block(self, tenant):
375 access_point_ssh = self._connect_to_access_point(tenant)
376 for server in tenant.servers:
377 self._check_connectivity(access_point=access_point_ssh,
378 ip=self._get_server_ip(server),
379 should_succeed=False)
380
381 def _test_in_tenant_allow(self, tenant):
382 ruleset = dict(
383 protocol='icmp',
384 remote_group_id=tenant.security_groups['default'].id,
385 direction='ingress'
386 )
387 rule = self._create_security_group_rule(
388 secgroup=tenant.security_groups['default'],
389 **ruleset
390 )
Yair Friedbf2e2c42014-01-28 12:06:38 +0200391 self.addCleanup(self.cleanup_wrapper, rule)
Yair Fried4d7efa62013-11-17 17:12:29 +0200392 access_point_ssh = self._connect_to_access_point(tenant)
393 for server in tenant.servers:
394 self._check_connectivity(access_point=access_point_ssh,
395 ip=self._get_server_ip(server))
Yair Fried4d7efa62013-11-17 17:12:29 +0200396
397 def _test_cross_tenant_block(self, source_tenant, dest_tenant):
Yair Friedbf2e2c42014-01-28 12:06:38 +0200398 """
Yair Fried4d7efa62013-11-17 17:12:29 +0200399 if public router isn't defined, then dest_tenant access is via
400 floating-ip
Yair Friedbf2e2c42014-01-28 12:06:38 +0200401 """
Yair Fried4d7efa62013-11-17 17:12:29 +0200402 access_point_ssh = self._connect_to_access_point(source_tenant)
403 ip = self._get_server_ip(dest_tenant.access_point,
404 floating=self.floating_ip_access)
405 self._check_connectivity(access_point=access_point_ssh, ip=ip,
406 should_succeed=False)
407
408 def _test_cross_tenant_allow(self, source_tenant, dest_tenant):
Yair Friedbf2e2c42014-01-28 12:06:38 +0200409 """
Yair Fried4d7efa62013-11-17 17:12:29 +0200410 check for each direction:
411 creating rule for tenant incoming traffic enables only 1way traffic
Yair Friedbf2e2c42014-01-28 12:06:38 +0200412 """
Yair Fried4d7efa62013-11-17 17:12:29 +0200413 ruleset = dict(
414 protocol='icmp',
415 direction='ingress'
416 )
417 rule_s2d = self._create_security_group_rule(
418 secgroup=dest_tenant.security_groups['default'],
419 **ruleset
420 )
Yair Friedbf2e2c42014-01-28 12:06:38 +0200421 self.addCleanup(self.cleanup_wrapper, rule_s2d)
422 access_point_ssh = self._connect_to_access_point(source_tenant)
423 ip = self._get_server_ip(dest_tenant.access_point,
424 floating=self.floating_ip_access)
425 self._check_connectivity(access_point_ssh, ip)
Yair Fried4d7efa62013-11-17 17:12:29 +0200426
Yair Friedbf2e2c42014-01-28 12:06:38 +0200427 # test that reverse traffic is still blocked
428 self._test_cross_tenant_block(dest_tenant, source_tenant)
Yair Fried4d7efa62013-11-17 17:12:29 +0200429
Yair Friedbf2e2c42014-01-28 12:06:38 +0200430 # allow reverse traffic and check
431 rule_d2s = self._create_security_group_rule(
432 secgroup=source_tenant.security_groups['default'],
433 **ruleset
434 )
435 self.addCleanup(self.cleanup_wrapper, rule_d2s)
Yair Fried4d7efa62013-11-17 17:12:29 +0200436
Yair Friedbf2e2c42014-01-28 12:06:38 +0200437 access_point_ssh_2 = self._connect_to_access_point(dest_tenant)
438 ip = self._get_server_ip(source_tenant.access_point,
439 floating=self.floating_ip_access)
440 self._check_connectivity(access_point_ssh_2, ip)
Yair Fried4d7efa62013-11-17 17:12:29 +0200441
442 def _verify_mac_addr(self, tenant):
443 """
444 verify that VM (tenant's access point) has the same ip,mac as listed in
445 port list
446 """
447 access_point_ssh = self._connect_to_access_point(tenant)
448 mac_addr = access_point_ssh.get_mac_address()
449 mac_addr = mac_addr.strip().lower()
450 port_list = self.network_client.list_ports()['ports']
451 port_detail_list = [
452 (port['fixed_ips'][0]['subnet_id'],
453 port['fixed_ips'][0]['ip_address'],
454 port['mac_address'].lower()) for port in port_list
455 ]
456 server_ip = self._get_server_ip(tenant.access_point)
457 subnet_id = tenant.subnet.id
458 self.assertIn((subnet_id, server_ip, mac_addr), port_detail_list)
459
460 @attr(type='smoke')
461 @services('compute', 'network')
462 def test_cross_tenant_traffic(self):
Nachi Ueno26b4c972014-01-17 06:15:13 -0800463 try:
Yair Friedbf2e2c42014-01-28 12:06:38 +0200464 # deploy new tenant
465 self._deploy_tenant(self.alt_tenant)
466 self._verify_network_details(self.alt_tenant)
467 self._verify_mac_addr(self.alt_tenant)
Yair Fried4d7efa62013-11-17 17:12:29 +0200468
Nachi Ueno26b4c972014-01-17 06:15:13 -0800469 # cross tenant check
Yair Friedbf2e2c42014-01-28 12:06:38 +0200470 source_tenant = self.primary_tenant
Nachi Ueno26b4c972014-01-17 06:15:13 -0800471 dest_tenant = self.alt_tenant
472 self._test_cross_tenant_block(source_tenant, dest_tenant)
473 self._test_cross_tenant_allow(source_tenant, dest_tenant)
474 except Exception:
Yair Friedbf2e2c42014-01-28 12:06:38 +0200475 for tenant in self.tenants.values():
476 self._log_console_output(servers=tenant.servers)
477 raise
478
479 @attr(type='smoke')
480 @services('compute', 'network')
481 def test_in_tenant_traffic(self):
482 try:
483 self._create_tenant_servers(self.primary_tenant, num=1)
484
485 # in-tenant check
486 self._test_in_tenant_block(self.primary_tenant)
487 self._test_in_tenant_allow(self.primary_tenant)
488
489 except Exception:
490 for tenant in self.tenants.values():
491 self._log_console_output(servers=tenant.servers)
Nachi Ueno26b4c972014-01-17 06:15:13 -0800492 raise