Low severityNVD Advisory· Published Jun 7, 2012· Updated Apr 29, 2026
CVE-2012-2101
CVE-2012-2101
Description
Openstack Compute (Nova) Folsom, 2012.1, and 2011.3 does not limit the number of security group rules, which allows remote authenticated users with certain permissions to cause a denial of service (CPU and hard drive consumption) via a network request that triggers a large number of iptables rules.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
novaPyPI | < 12.0.0a0 | 12.0.0a0 |
Affected products
3Patches
3a67db4586f70Implement quotas for security groups.
10 files changed · +193 −6
nova/api/ec2/cloud.py+12 −0 modified@@ -42,6 +42,7 @@ from nova import log as logging from nova import network from nova.rpc import common as rpc_common +from nova import quota from nova import utils from nova import volume @@ -727,6 +728,13 @@ def authorize_security_group_ingress(self, context, group_name=None, raise exception.EC2APIError(err % values_for_rule) postvalues.append(values_for_rule) + allowed = quota.allowed_security_group_rules(context, + security_group['id'], + 1) + if allowed < 1: + msg = _("Quota exceeded, too many security group rules.") + raise exception.EC2APIError(msg) + rule_ids = [] for values_for_rule in postvalues: security_group_rule = db.security_group_rule_create( @@ -784,6 +792,10 @@ def create_security_group(self, context, group_name, group_description): msg = _('group %s already exists') raise exception.EC2APIError(msg % group_name) + if quota.allowed_security_groups(context, 1) < 1: + msg = _("Quota exceeded, too many security groups.") + raise exception.EC2APIError(msg) + group = {'user_id': context.user_id, 'project_id': context.project_id, 'name': group_name,
nova/api/openstack/compute/contrib/quotas.py+1 −1 modified@@ -31,7 +31,7 @@ quota_resources = ['metadata_items', 'injected_file_content_bytes', 'volumes', 'gigabytes', 'ram', 'floating_ips', 'instances', - 'injected_files', 'cores'] + 'injected_files', 'cores', 'security_groups', 'security_group_rules'] class QuotaTemplate(xmlutil.TemplateBuilder):
nova/api/openstack/compute/contrib/security_groups.py+12 −0 modified@@ -31,6 +31,7 @@ from nova import exception from nova import flags from nova import log as logging +from nova import quota from nova import utils @@ -289,6 +290,10 @@ def create(self, req, body): group_name = group_name.strip() group_description = group_description.strip() + if quota.allowed_security_groups(context, 1) < 1: + msg = _("Quota exceeded, too many security groups.") + raise exc.HTTPBadRequest(explanation=msg) + LOG.audit(_("Create Security Group %s"), group_name, context=context) self.compute_api.ensure_default_security_group(context) if db.security_group_exists(context, context.project_id, group_name): @@ -376,6 +381,13 @@ def create(self, req, body): msg = _('This rule already exists in group %s') % parent_group_id raise exc.HTTPBadRequest(explanation=msg) + allowed = quota.allowed_security_group_rules(context, + parent_group_id, + 1) + if allowed < 1: + msg = _("Quota exceeded, too many security group rules.") + raise exc.HTTPBadRequest(explanation=msg) + security_group_rule = db.security_group_rule_create(context, values) self.sgh.trigger_security_group_rule_create_refresh( context, [security_group_rule['id']])
nova/db/api.py+10 −0 modified@@ -1118,6 +1118,11 @@ def security_group_destroy(context, security_group_id): return IMPL.security_group_destroy(context, security_group_id) +def security_group_count_by_project(context, project_id): + """Count number of security groups in a project.""" + return IMPL.security_group_count_by_project(context, project_id) + + #################### @@ -1149,6 +1154,11 @@ def security_group_rule_get(context, security_group_rule_id): return IMPL.security_group_rule_get(context, security_group_rule_id) +def security_group_rule_count_by_group(context, security_group_id): + """Count rules in a given security group.""" + return IMPL.security_group_rule_count_by_group(context, security_group_id) + + ###################
nova/db/sqlalchemy/api.py+16 −0 modified@@ -2813,6 +2813,13 @@ def security_group_destroy(context, security_group_id): 'updated_at': literal_column('updated_at')}) +@require_context +def security_group_count_by_project(context, project_id): + authorize_project_context(context, project_id) + return model_query(context, models.SecurityGroup, read_deleted="no").\ + filter_by(project_id=project_id).\ + count() + ################### @@ -2871,6 +2878,14 @@ def security_group_rule_destroy(context, security_group_rule_id): security_group_rule.delete(session=session) +@require_context +def security_group_rule_count_by_group(context, security_group_id): + return model_query(context, models.SecurityGroupIngressRule, + read_deleted="no").\ + filter_by(parent_group_id=security_group_id).\ + count() + +# ################### @@ -3018,6 +3033,7 @@ def user_update(context, user_id, values): user_ref.save(session=session) +# ###################
nova/quota.py+34 −0 modified@@ -54,6 +54,12 @@ cfg.IntOpt('quota_max_injected_file_path_bytes', default=255, help='number of bytes allowed per injected file path'), + cfg.IntOpt('quota_security_groups', + default=10, + help='number of security groups per project'), + cfg.IntOpt('quota_security_group_rules', + default=20, + help='number of security rules per security group'), ] FLAGS = flags.FLAGS @@ -72,6 +78,8 @@ def _get_default_quotas(): 'injected_files': FLAGS.quota_max_injected_files, 'injected_file_content_bytes': FLAGS.quota_max_injected_file_content_bytes, + 'security_groups': FLAGS.quota_security_groups, + 'security_group_rules': FLAGS.quota_security_group_rules, } # -1 in the quota flags means unlimited for key in defaults.keys(): @@ -152,6 +160,32 @@ def allowed_floating_ips(context, requested_floating_ips): return min(requested_floating_ips, allowed_floating_ips) +def allowed_security_groups(context, requested_security_groups): + """Check quota and return min(requested, allowed) security groups.""" + project_id = context.project_id + context = context.elevated() + used_sec_groups = db.security_group_count_by_project(context, project_id) + quota = get_project_quotas(context, project_id) + allowed_sec_groups = _get_request_allotment(requested_security_groups, + used_sec_groups, + quota['security_groups']) + return min(requested_security_groups, allowed_sec_groups) + + +def allowed_security_group_rules(context, security_group_id, + requested_rules): + """Check quota and return min(requested, allowed) sec group rules.""" + project_id = context.project_id + context = context.elevated() + used_rules = db.security_group_rule_count_by_group(context, + security_group_id) + quota = get_project_quotas(context, project_id) + allowed_rules = _get_request_allotment(requested_rules, + used_rules, + quota['security_group_rules']) + return min(requested_rules, allowed_rules) + + def _calculate_simple_quota(context, resource, requested): """Check quota for resource; return min(requested, allowed).""" quota = get_project_quotas(context, context.project_id)
nova/tests/api/ec2/test_cloud.py+25 −0 modified@@ -271,6 +271,18 @@ def test_create_delete_security_group(self): delete = self.cloud.delete_security_group self.assertTrue(delete(self.context, 'testgrp')) + def test_security_group_quota_limit(self): + self.flags(quota_security_groups=10) + for i in range(1, 10): + name = 'test name %i' % i + descript = 'test description %i' % i + create = self.cloud.create_security_group + result = create(self.context, name, descript) + + # 11'th group should fail + self.assertRaises(exception.EC2APIError, + create, self.context, 'foo', 'bar') + def test_delete_security_group_by_id(self): sec = db.security_group_create(self.context, {'project_id': self.context.project_id, @@ -436,6 +448,19 @@ def test_authorize_security_group_ingress_already_exists(self): self.assertRaises(exception.EC2APIError, authz, self.context, group_name=sec['name'], **kwargs) + def test_security_group_ingress_quota_limit(self): + self.flags(quota_security_group_rules=20) + kwargs = {'project_id': self.context.project_id, 'name': 'test'} + sec_group = db.security_group_create(self.context, kwargs) + authz = self.cloud.authorize_security_group_ingress + for i in range(100, 120): + kwargs = {'to_port': i, 'from_port': i, 'ip_protocol': 'tcp'} + authz(self.context, group_id=sec_group['id'], **kwargs) + + kwargs = {'to_port': 121, 'from_port': 121, 'ip_protocol': 'tcp'} + self.assertRaises(exception.EC2APIError, authz, self.context, + group_id=sec_group['id'], **kwargs) + def _test_authorize_security_group_no_ports_with_source_group(self, proto): kwargs = {'project_id': self.context.project_id, 'name': 'test'} sec = db.security_group_create(self.context, kwargs)
nova/tests/api/openstack/compute/contrib/test_quotas.py+24 −5 modified@@ -28,7 +28,8 @@ def quota_set(id): return {'quota_set': {'id': id, 'metadata_items': 128, 'volumes': 10, 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, 'instances': 10, 'injected_files': 5, 'cores': 20, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, 'security_group_rules': 20}} def quota_set_list(): @@ -52,7 +53,10 @@ def test_format_quota_set(self): 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, - 'injected_file_content_bytes': 10240} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20, + } quota_set = quotas.QuotaSetsController()._format_quota_set('1234', raw_quota_set) @@ -68,6 +72,8 @@ def test_format_quota_set(self): self.assertEqual(qs['metadata_items'], 128) self.assertEqual(qs['injected_files'], 5) self.assertEqual(qs['injected_file_content_bytes'], 10240) + self.assertEqual(qs['security_groups'], 10) + self.assertEqual(qs['security_group_rules'], 20) def test_quotas_defaults(self): uri = '/v2/fake_tenant/os-quota-sets/fake_tenant/defaults' @@ -85,7 +91,10 @@ def test_quotas_defaults(self): 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20, + }} self.assertEqual(res_dict, expected) @@ -106,7 +115,9 @@ def test_quotas_update_as_admin(self): 'ram': 51200, 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20}} req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me', use_admin_context=True) @@ -119,7 +130,9 @@ def test_quotas_update_as_user(self): 'ram': 51200, 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20}} req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me') self.assertRaises(webob.exc.HTTPForbidden, self.controller.update, @@ -143,6 +156,8 @@ def test_serializer(self): floating_ips=60, instances=70, injected_files=80, + security_groups=10, + security_group_rules=20, cores=90)) text = self.serializer.serialize(exemplar) @@ -166,6 +181,8 @@ def test_deserializer(self): floating_ips='60', instances='70', injected_files='80', + security_groups='10', + security_group_rules='20', cores='90')) intext = ("<?xml version='1.0' encoding='UTF-8'?>\n" '<quota_set>' @@ -178,6 +195,8 @@ def test_deserializer(self): '<floating_ips>60</floating_ips>' '<instances>70</instances>' '<injected_files>80</injected_files>' + '<security_groups>10</security_groups>' + '<security_group_rules>20</security_group_rules>' '<cores>90</cores>' '</quota_set>')
nova/tests/api/openstack/compute/contrib/test_security_groups.py+31 −0 modified@@ -25,12 +25,15 @@ from nova.api.openstack import wsgi import nova.db from nova import exception +from nova import flags from nova import test from nova.tests.api.openstack import fakes FAKE_UUID = 'a47ae74e-ab08-447f-8eee-ffd43fc46c16' +FLAGS = flags.FLAGS + class AttrDict(dict): def __getattr__(self, k): @@ -219,6 +222,18 @@ def test_create_security_group_non_string_description(self): self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, {'security_group': sg}) + def test_create_security_group_quota_limit(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-security-groups') + for num in range(1, FLAGS.quota_security_groups): + name = 'test%s' % num + sg = security_group_template(name=name) + res_dict = self.controller.create(req, {'security_group': sg}) + self.assertEqual(res_dict['security_group']['name'], name) + + sg = security_group_template() + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + def test_get_security_group_list(self): groups = [] for i, name in enumerate(['default', 'test']): @@ -894,6 +909,22 @@ def test_delete_non_existing_rule_id(self): self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, req, '22222222222222') + def test_create_rule_quota_limit(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-security-group-rules') + for num in range(100, 100 + FLAGS.quota_security_group_rules): + rule = { + 'ip_protocol': 'tcp', 'from_port': num, + 'to_port': num, 'parent_group_id': '2', 'group_id': '1' + } + self.controller.create(req, {'security_group_rule': rule}) + + rule = { + 'ip_protocol': 'tcp', 'from_port': '121', 'to_port': '121', + 'parent_group_id': '2', 'group_id': '1' + } + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + class TestSecurityGroupRulesXMLDeserializer(unittest.TestCase):
nova/tests/test_quota.py+28 −0 modified@@ -235,6 +235,34 @@ def test_unlimited_floating_ips(self): floating_ips = quota.allowed_floating_ips(self.context, 101) self.assertEqual(floating_ips, 101) + def test_unlimited_security_groups(self): + self.flags(quota_security_groups=10) + security_groups = quota.allowed_security_groups(self.context, 100) + self.assertEqual(security_groups, 10) + db.quota_create(self.context, self.project_id, 'security_groups', None) + security_groups = quota.allowed_security_groups(self.context, 100) + self.assertEqual(security_groups, 100) + security_groups = quota.allowed_security_groups(self.context, 101) + self.assertEqual(security_groups, 101) + + def test_unlimited_security_group_rules(self): + + def fake_security_group_rule_count_by_group(context, sec_group_id): + return 0 + + self.stubs.Set(db, 'security_group_rule_count_by_group', + fake_security_group_rule_count_by_group) + + self.flags(quota_security_group_rules=20) + rules = quota.allowed_security_group_rules(self.context, 1234, 100) + self.assertEqual(rules, 20) + db.quota_create(self.context, self.project_id, 'security_group_rules', + None) + rules = quota.allowed_security_group_rules(self.context, 1234, 100) + self.assertEqual(rules, 100) + rules = quota.allowed_security_group_rules(self.context, 1234, 101) + self.assertEqual(rules, 101) + def test_unlimited_metadata_items(self): self.flags(quota_metadata_items=10) items = quota.allowed_metadata_items(self.context, 100)
8c8735a73afbImplement quotas for security groups.
10 files changed · +201 −6
nova/api/ec2/cloud.py+12 −0 modified@@ -42,6 +42,7 @@ from nova import log as logging from nova import network from nova import rpc +from nova import quota from nova import utils from nova import volume from nova.api.ec2 import ec2utils @@ -856,6 +857,13 @@ def authorize_security_group_ingress(self, context, group_name=None, raise exception.ApiError(_(err) % values_for_rule) postvalues.append(values_for_rule) + allowed = quota.allowed_security_group_rules(context, + security_group['id'], + 1) + if allowed < 1: + msg = _("Quota exceeded, too many security group rules.") + raise exception.ApiError(msg) + for values_for_rule in postvalues: security_group_rule = db.security_group_rule_create( context, @@ -908,6 +916,10 @@ def create_security_group(self, context, group_name, group_description): if db.security_group_exists(context, context.project_id, group_name): raise exception.ApiError(_('group %s already exists') % group_name) + if quota.allowed_security_groups(context, 1) < 1: + msg = _("Quota exceeded, too many security groups.") + raise exception.ApiError(msg) + group = {'user_id': context.user_id, 'project_id': context.project_id, 'name': group_name,
nova/api/openstack/contrib/quotas.py+4 −1 modified@@ -40,6 +40,8 @@ def _format_quota_set(self, project_id, quota_set): 'instances': quota_set['instances'], 'injected_files': quota_set['injected_files'], 'cores': quota_set['cores'], + 'security_groups': quota_set['security_groups'], + 'security_group_rules': quota_set['security_group_rules'], }} def show(self, req, id): @@ -56,7 +58,8 @@ def update(self, req, id, body): project_id = id resources = ['metadata_items', 'injected_file_content_bytes', 'volumes', 'gigabytes', 'ram', 'floating_ips', 'instances', - 'injected_files', 'cores'] + 'injected_files', 'cores', 'security_groups', + 'security_group_rules'] for key in body['quota_set'].keys(): if key in resources: value = int(body['quota_set'][key])
nova/api/openstack/contrib/security_groups.py+12 −0 modified@@ -26,6 +26,7 @@ from nova import log as logging from nova import rpc from nova import utils +from nova import quota from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import wsgi @@ -136,6 +137,10 @@ def create(self, req, body): group_name = group_name.strip() group_description = group_description.strip() + if quota.allowed_security_groups(context, 1) < 1: + msg = _("Quota exceeded, too many security groups.") + raise exc.HTTPBadRequest(explanation=msg) + LOG.audit(_("Create Security Group %s"), group_name, context=context) self.compute_api.ensure_default_security_group(context) if db.security_group_exists(context, context.project_id, group_name): @@ -219,6 +224,13 @@ def create(self, req, body): msg = _('This rule already exists in group %s') % parent_group_id raise exc.HTTPBadRequest(explanation=msg) + allowed = quota.allowed_security_group_rules(context, + parent_group_id, + 1) + if allowed < 1: + msg = _("Quota exceeded, too many security group rules.") + raise exc.HTTPBadRequest(explanation=msg) + security_group_rule = db.security_group_rule_create(context, values) self.compute_api.trigger_security_group_rules_refresh(context,
nova/db/api.py+10 −0 modified@@ -1098,6 +1098,11 @@ def security_group_destroy_all(context): return IMPL.security_group_destroy_all(context) +def security_group_count_by_project(context, project_id): + """Count number of security groups in a project.""" + return IMPL.security_group_count_by_project(context, project_id) + + #################### @@ -1129,6 +1134,11 @@ def security_group_rule_get(context, security_group_rule_id): return IMPL.security_group_rule_get(context, security_group_rule_id) +def security_group_rule_count_by_group(context, security_group_id): + """Count rules in a given security group.""" + return IMPL.security_group_rule_count_by_group(context, security_group_id) + + ###################
nova/db/sqlalchemy/api.py+19 −0 modified@@ -2803,6 +2803,16 @@ def security_group_destroy_all(context, session=None): 'updated_at': literal_column('updated_at')}) +@require_context +def security_group_count_by_project(context, project_id): + authorize_project_context(context, project_id) + session = get_session() + return session.query(models.SecurityGroup).\ + filter_by(deleted=False).\ + filter_by(project_id=project_id).\ + count() + + ################### @@ -2884,6 +2894,15 @@ def security_group_rule_destroy(context, security_group_rule_id): security_group_rule.delete(session=session) +@require_context +def security_group_rule_count_by_group(context, security_group_id): + session = get_session() + return session.query(models.SecurityGroupIngressRule).\ + filter_by(deleted=False).\ + filter_by(parent_group_id=security_group_id).\ + count() + + ###################
nova/quota.py+32 −0 modified@@ -44,6 +44,10 @@ 'number of bytes allowed per injected file') flags.DEFINE_integer('quota_max_injected_file_path_bytes', 255, 'number of bytes allowed per injected file path') +flags.DEFINE_integer('quota_security_groups', 10, + 'number of security groups per project') +flags.DEFINE_integer('quota_security_group_rules', 20, + 'number of security rules per security group') def _get_default_quotas(): @@ -58,6 +62,8 @@ def _get_default_quotas(): 'injected_files': FLAGS.quota_max_injected_files, 'injected_file_content_bytes': FLAGS.quota_max_injected_file_content_bytes, + 'security_groups': FLAGS.quota_security_groups, + 'security_group_rules': FLAGS.quota_security_group_rules, } # -1 in the quota flags means unlimited for key in defaults.keys(): @@ -134,6 +140,32 @@ def allowed_floating_ips(context, requested_floating_ips): return min(requested_floating_ips, allowed_floating_ips) +def allowed_security_groups(context, requested_security_groups): + """Check quota and return min(requested, allowed) security groups.""" + project_id = context.project_id + context = context.elevated() + used_sec_groups = db.security_group_count_by_project(context, project_id) + quota = get_project_quotas(context, project_id) + allowed_sec_groups = _get_request_allotment(requested_security_groups, + used_sec_groups, + quota['security_groups']) + return min(requested_security_groups, allowed_sec_groups) + + +def allowed_security_group_rules(context, security_group_id, + requested_rules): + """Check quota and return min(requested, allowed) sec group rules.""" + project_id = context.project_id + context = context.elevated() + used_rules = db.security_group_rule_count_by_group(context, + security_group_id) + quota = get_project_quotas(context, project_id) + allowed_rules = _get_request_allotment(requested_rules, + used_rules, + quota['security_group_rules']) + return min(requested_rules, allowed_rules) + + def _calculate_simple_quota(context, resource, requested): """Check quota for resource; return min(requested, allowed).""" quota = get_project_quotas(context, context.project_id)
nova/tests/api/openstack/contrib/test_quotas.py+12 −4 modified@@ -29,7 +29,8 @@ def quota_set(id): return {'quota_set': {'id': id, 'metadata_items': 128, 'volumes': 10, 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, 'instances': 10, 'injected_files': 5, 'cores': 20, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, 'security_group_rules': 20}} def quota_set_list(): @@ -60,7 +61,9 @@ def test_format_quota_set(self): 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, - 'injected_file_content_bytes': 10240} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20} quota_set = QuotaSetsController()._format_quota_set('1234', raw_quota_set) @@ -95,7 +98,9 @@ def test_quotas_defaults(self): 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20}} self.assertEqual(json.loads(res.body), expected) @@ -123,7 +128,10 @@ def test_quotas_update_as_admin(self): 'cores': 50, 'ram': 51200, 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 40, + 'security_group_rules': 80 + }} req = webob.Request.blank('/v1.1/fake/os-quota-sets/update_me') req.method = 'PUT'
nova/tests/api/openstack/contrib/test_security_groups.py+44 −0 modified@@ -22,10 +22,13 @@ from xml.dom import minidom from nova import exception +from nova import flags from nova import test from nova.api.openstack.contrib import security_groups from nova.tests.api.openstack import fakes +FLAGS = flags.FLAGS + def _get_create_request_json(body_dict): req = webob.Request.blank('/v1.1/fake/os-security-groups') @@ -257,6 +260,19 @@ def test_create_security_group_non_string_description_json(self): response = _create_security_group_json(security_group) self.assertEquals(response.status_int, 400) + def test_create_security_group_quota_limit(self): + security_group = {} + for num in range(1, FLAGS.quota_security_groups): + security_group['name'] = "test%i" % num + security_group['description'] = "test%i" % num + response = _create_security_group_json(security_group) + self.assertEquals(response.status_int, 200) + + security_group['name'] = "test_to_many" + security_group['description'] = "test_to_many" + response = _create_security_group_json(security_group) + self.assertEquals(response.status_int, 400) + def test_get_security_group_list(self): security_group = {} security_group['name'] = "test" @@ -918,6 +934,34 @@ def test_create_rule_with_same_group_parent_id_json(self): response = self._create_security_group_rule_json(rules) self.assertEquals(response.status_int, 400) + def test_create_rule_quota_limit(self): + #NOTE: subtract 1 because we create 1 rule in setup + for num in range(100, (100 + FLAGS.quota_security_group_rules) - 1): + rule = { + "security_group_rule": { + "ip_protocol": "tcp", + "from_port": num, + "to_port": num, + "parent_group_id": "%s" + % self.parent_security_group['id'], + } + } + response = self._create_security_group_rule_json(rule) + print response.body + self.assertEquals(response.status_int, 200) + + rule = { + "security_group_rule": { + "ip_protocol": "tcp", + "from_port": "121", + "to_port": "121", + "parent_group_id": "%s" + % self.parent_security_group['id'], + } + } + response = self._create_security_group_rule_json(rule) + self.assertEquals(response.status_int, 400) + def test_delete(self): response = self._delete_security_group_rule( self.security_group_rule['id'])
nova/tests/test_cloud.py+25 −0 modified@@ -256,6 +256,31 @@ def test_delete_security_group_no_params(self): delete = self.cloud.delete_security_group self.assertRaises(exception.ApiError, delete, self.context) + def test_security_group_ingress_quota_limit(self): + self.flags(quota_security_group_rules=20) + kwargs = {'project_id': self.context.project_id, 'name': 'test'} + sec_group = db.security_group_create(self.context, kwargs) + authz = self.cloud.authorize_security_group_ingress + for i in range(100, 120): + kwargs = {'to_port': i, 'from_port': i, 'ip_protocol': 'tcp'} + authz(self.context, group_id=sec_group['id'], **kwargs) + + kwargs = {'to_port': 121, 'from_port': 121, 'ip_protocol': 'tcp'} + self.assertRaises(exception.ApiError, authz, self.context, + group_id=sec_group['id'], **kwargs) + + def test_security_group_quota_limit(self): + self.flags(quota_security_groups=10) + for i in range(1, 10): + name = 'test name %i' % i + descript = 'test description %i' % i + create = self.cloud.create_security_group + result = create(self.context, name, descript) + + # 11'th group should fail + self.assertRaises(exception.ApiError, + create, self.context, 'foo', 'bar') + def test_authorize_security_group_ingress(self): kwargs = {'project_id': self.context.project_id, 'name': 'test'} sec = db.security_group_create(self.context, kwargs)
nova/tests/test_quota.py+31 −1 modified@@ -43,7 +43,9 @@ def setUp(self): quota_cores=4, quota_volumes=2, quota_gigabytes=20, - quota_floating_ips=1) + quota_floating_ips=1, + quota_security_groups=10, + quota_security_group_rules=20) self.network = self.network = self.start_service('network') self.user_id = 'admin' @@ -185,6 +187,34 @@ def test_unlimited_floating_ips(self): floating_ips = quota.allowed_floating_ips(self.context, 101) self.assertEqual(floating_ips, 101) + def test_unlimited_security_groups(self): + self.flags(quota_security_groups=10) + security_groups = quota.allowed_security_groups(self.context, 100) + self.assertEqual(security_groups, 10) + db.quota_create(self.context, self.project_id, 'security_groups', None) + security_groups = quota.allowed_security_groups(self.context, 100) + self.assertEqual(security_groups, 100) + security_groups = quota.allowed_security_groups(self.context, 101) + self.assertEqual(security_groups, 101) + + def test_unlimited_security_group_rules(self): + + def fake_security_group_rule_count_by_group(context, sec_group_id): + return 0 + + self.stubs.Set(db, 'security_group_rule_count_by_group', + fake_security_group_rule_count_by_group) + + self.flags(quota_security_group_rules=20) + rules = quota.allowed_security_group_rules(self.context, 1234, 100) + self.assertEqual(rules, 20) + db.quota_create(self.context, self.project_id, 'security_group_rules', + None) + rules = quota.allowed_security_group_rules(self.context, 1234, 100) + self.assertEqual(rules, 100) + rules = quota.allowed_security_group_rules(self.context, 1234, 101) + self.assertEqual(rules, 101) + def test_unlimited_metadata_items(self): self.flags(quota_metadata_items=10) items = quota.allowed_metadata_items(self.context, 100)
1f644d210557Implement security group quotas.
10 files changed · +238 −10
nova/api/ec2/cloud.py+12 −0 modified@@ -41,6 +41,7 @@ from nova.image import s3 from nova import log as logging from nova import network +from nova import quota from nova import utils from nova import volume @@ -725,6 +726,13 @@ def authorize_security_group_ingress(self, context, group_name=None, raise exception.EC2APIError(err % values_for_rule) postvalues.append(values_for_rule) + allowed = quota.allowed_security_group_rules(context, + security_group['id'], + 1) + if allowed < 1: + msg = _("Quota exceeded, too many security group rules.") + raise exception.EC2APIError(msg) + rule_ids = [] for values_for_rule in postvalues: security_group_rule = db.security_group_rule_create( @@ -782,6 +790,10 @@ def create_security_group(self, context, group_name, group_description): msg = _('group %s already exists') raise exception.EC2APIError(msg % group_name) + if quota.allowed_security_groups(context, 1) < 1: + msg = _("Quota exceeded, too many security groups.") + raise exception.EC2APIError(msg) + group = {'user_id': context.user_id, 'project_id': context.project_id, 'name': group_name,
nova/api/openstack/compute/contrib/security_groups.py+12 −0 modified@@ -31,6 +31,7 @@ from nova import exception from nova import flags from nova import log as logging +from nova import quota from nova import utils @@ -289,6 +290,10 @@ def create(self, req, body): group_name = group_name.strip() group_description = group_description.strip() + if quota.allowed_security_groups(context, 1) < 1: + msg = _("Quota exceeded, too many security groups.") + raise exc.HTTPBadRequest(explanation=msg) + LOG.audit(_("Create Security Group %s"), group_name, context=context) self.compute_api.ensure_default_security_group(context) if db.security_group_exists(context, context.project_id, group_name): @@ -376,6 +381,13 @@ def create(self, req, body): msg = _('This rule already exists in group %s') % parent_group_id raise exc.HTTPBadRequest(explanation=msg) + allowed = quota.allowed_security_group_rules(context, + parent_group_id, + 1) + if allowed < 1: + msg = _("Quota exceeded, too many security group rules.") + raise exc.HTTPBadRequest(explanation=msg) + security_group_rule = db.security_group_rule_create(context, values) self.sgh.trigger_security_group_rule_create_refresh( context, [security_group_rule['id']])
nova/db/api.py+10 −0 modified@@ -1151,6 +1151,11 @@ def security_group_destroy(context, security_group_id): return IMPL.security_group_destroy(context, security_group_id) +def security_group_count_by_project(context, project_id): + """Count number of security groups in a project.""" + return IMPL.security_group_count_by_project(context, project_id) + + #################### @@ -1182,6 +1187,11 @@ def security_group_rule_get(context, security_group_rule_id): return IMPL.security_group_rule_get(context, security_group_rule_id) +def security_group_rule_count_by_group(context, security_group_id): + """Count rules in a given security group.""" + return IMPL.security_group_rule_count_by_group(context, security_group_id) + + ###################
nova/db/sqlalchemy/api.py+15 −0 modified@@ -2903,6 +2903,13 @@ def security_group_destroy(context, security_group_id): 'updated_at': literal_column('updated_at')}) +@require_context +def security_group_count_by_project(context, project_id): + authorize_project_context(context, project_id) + return model_query(context, models.SecurityGroup, read_deleted="no").\ + filter_by(project_id=project_id).\ + count() + ################### @@ -2961,6 +2968,14 @@ def security_group_rule_destroy(context, security_group_rule_id): security_group_rule.delete(session=session) +@require_context +def security_group_rule_count_by_group(context, security_group_id): + return model_query(context, models.SecurityGroupIngressRule, + read_deleted="no").\ + filter_by(parent_group_id=security_group_id).\ + count() + +# ###################
nova/quota.py+35 −1 modified@@ -54,6 +54,12 @@ cfg.IntOpt('quota_injected_file_path_bytes', default=255, help='number of bytes allowed per injected file path'), + cfg.IntOpt('quota_security_groups', + default=10, + help='number of security groups per project'), + cfg.IntOpt('quota_security_group_rules', + default=20, + help='number of security rules per security group'), ] FLAGS = flags.FLAGS @@ -62,7 +68,7 @@ quota_resources = ['metadata_items', 'injected_file_content_bytes', 'volumes', 'gigabytes', 'ram', 'floating_ips', 'instances', - 'injected_files', 'cores'] + 'injected_files', 'cores', 'security_groups', 'security_group_rules'] def _get_default_quotas(): @@ -77,6 +83,8 @@ def _get_default_quotas(): 'injected_files': FLAGS.quota_injected_files, 'injected_file_content_bytes': FLAGS.quota_injected_file_content_bytes, + 'security_groups': FLAGS.quota_security_groups, + 'security_group_rules': FLAGS.quota_security_group_rules, } # -1 in the quota flags means unlimited return defaults @@ -170,6 +178,32 @@ def allowed_floating_ips(context, requested_floating_ips): return min(requested_floating_ips, allowed_floating_ips) +def allowed_security_groups(context, requested_security_groups): + """Check quota and return min(requested, allowed) security groups.""" + project_id = context.project_id + context = context.elevated() + used_sec_groups = db.security_group_count_by_project(context, project_id) + quota = get_project_quotas(context, project_id) + allowed_sec_groups = _get_request_allotment(requested_security_groups, + used_sec_groups, + quota['security_groups']) + return min(requested_security_groups, allowed_sec_groups) + + +def allowed_security_group_rules(context, security_group_id, + requested_rules): + """Check quota and return min(requested, allowed) sec group rules.""" + project_id = context.project_id + context = context.elevated() + used_rules = db.security_group_rule_count_by_group(context, + security_group_id) + quota = get_project_quotas(context, project_id) + allowed_rules = _get_request_allotment(requested_rules, + used_rules, + quota['security_group_rules']) + return min(requested_rules, allowed_rules) + + def _calculate_simple_quota(context, resource, requested): """Check quota for resource; return min(requested, allowed).""" quota = get_project_quotas(context, context.project_id)
nova/tests/api/ec2/test_cloud.py+25 −0 modified@@ -271,6 +271,18 @@ def test_create_delete_security_group(self): delete = self.cloud.delete_security_group self.assertTrue(delete(self.context, 'testgrp')) + def test_security_group_quota_limit(self): + self.flags(quota_security_groups=10) + for i in range(1, 10): + name = 'test name %i' % i + descript = 'test description %i' % i + create = self.cloud.create_security_group + result = create(self.context, name, descript) + + # 11'th group should fail + self.assertRaises(exception.EC2APIError, + create, self.context, 'foo', 'bar') + def test_delete_security_group_by_id(self): sec = db.security_group_create(self.context, {'project_id': self.context.project_id, @@ -436,6 +448,19 @@ def test_authorize_security_group_ingress_already_exists(self): self.assertRaises(exception.EC2APIError, authz, self.context, group_name=sec['name'], **kwargs) + def test_security_group_ingress_quota_limit(self): + self.flags(quota_security_group_rules=20) + kwargs = {'project_id': self.context.project_id, 'name': 'test'} + sec_group = db.security_group_create(self.context, kwargs) + authz = self.cloud.authorize_security_group_ingress + for i in range(100, 120): + kwargs = {'to_port': i, 'from_port': i, 'ip_protocol': 'tcp'} + authz(self.context, group_id=sec_group['id'], **kwargs) + + kwargs = {'to_port': 121, 'from_port': 121, 'ip_protocol': 'tcp'} + self.assertRaises(exception.EC2APIError, authz, self.context, + group_id=sec_group['id'], **kwargs) + def _test_authorize_security_group_no_ports_with_source_group(self, proto): kwargs = {'project_id': self.context.project_id, 'name': 'test'} sec = db.security_group_create(self.context, kwargs)
nova/tests/api/openstack/compute/contrib/test_quota_classes.py+22 −4 modified@@ -26,7 +26,8 @@ def quota_set(class_name): return {'quota_class_set': {'id': class_name, 'metadata_items': 128, 'volumes': 10, 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, 'instances': 10, 'injected_files': 5, - 'cores': 20, 'injected_file_content_bytes': 10240}} + 'cores': 20, 'injected_file_content_bytes': 10240, + 'security_groups': 10, 'security_group_rules': 20}} class QuotaClassSetsTest(test.TestCase): @@ -45,7 +46,10 @@ def test_format_quota_set(self): 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, - 'injected_file_content_bytes': 10240} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20, + } quota_set = self.controller._format_quota_set('test_class', raw_quota_set) @@ -61,6 +65,8 @@ def test_format_quota_set(self): self.assertEqual(qs['metadata_items'], 128) self.assertEqual(qs['injected_files'], 5) self.assertEqual(qs['injected_file_content_bytes'], 10240) + self.assertEqual(qs['security_groups'], 10) + self.assertEqual(qs['security_group_rules'], 20) def test_quotas_show_as_admin(self): req = fakes.HTTPRequest.blank( @@ -81,7 +87,10 @@ def test_quotas_update_as_admin(self): 'ram': 51200, 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20, + }} req = fakes.HTTPRequest.blank( '/v2/fake4/os-quota-class-sets/test_class', @@ -95,7 +104,10 @@ def test_quotas_update_as_user(self): 'ram': 51200, 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20, + }} req = fakes.HTTPRequest.blank( '/v2/fake4/os-quota-class-sets/test_class') @@ -120,6 +132,8 @@ def test_serializer(self): floating_ips=60, instances=70, injected_files=80, + security_groups=10, + security_group_rules=20, cores=90)) text = self.serializer.serialize(exemplar) @@ -144,6 +158,8 @@ def test_deserializer(self): floating_ips='60', instances='70', injected_files='80', + security_groups='10', + security_group_rules='20', cores='90')) intext = ("<?xml version='1.0' encoding='UTF-8'?>\n" '<quota_class_set>' @@ -157,6 +173,8 @@ def test_deserializer(self): '<instances>70</instances>' '<injected_files>80</injected_files>' '<cores>90</cores>' + '<security_groups>10</security_groups>' + '<security_group_rules>20</security_group_rules>' '</quota_class_set>') result = self.deserializer.deserialize(intext)['body']
nova/tests/api/openstack/compute/contrib/test_quotas.py+24 −5 modified@@ -28,7 +28,8 @@ def quota_set(id): return {'quota_set': {'id': id, 'metadata_items': 128, 'volumes': 10, 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, 'instances': 10, 'injected_files': 5, 'cores': 20, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, 'security_group_rules': 20}} class QuotaSetsTest(test.TestCase): @@ -47,7 +48,10 @@ def test_format_quota_set(self): 'metadata_items': 128, 'gigabytes': 1000, 'injected_files': 5, - 'injected_file_content_bytes': 10240} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20, + } quota_set = self.controller._format_quota_set('1234', raw_quota_set) qs = quota_set['quota_set'] @@ -62,6 +66,8 @@ def test_format_quota_set(self): self.assertEqual(qs['metadata_items'], 128) self.assertEqual(qs['injected_files'], 5) self.assertEqual(qs['injected_file_content_bytes'], 10240) + self.assertEqual(qs['security_groups'], 10) + self.assertEqual(qs['security_group_rules'], 20) def test_quotas_defaults(self): uri = '/v2/fake_tenant/os-quota-sets/fake_tenant/defaults' @@ -79,7 +85,10 @@ def test_quotas_defaults(self): 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20, + }} self.assertEqual(res_dict, expected) @@ -100,7 +109,9 @@ def test_quotas_update_as_admin(self): 'ram': 51200, 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20}} req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me', use_admin_context=True) @@ -113,7 +124,9 @@ def test_quotas_update_as_user(self): 'ram': 51200, 'volumes': 10, 'gigabytes': 1000, 'floating_ips': 10, 'metadata_items': 128, 'injected_files': 5, - 'injected_file_content_bytes': 10240}} + 'injected_file_content_bytes': 10240, + 'security_groups': 10, + 'security_group_rules': 20}} req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me') self.assertRaises(webob.exc.HTTPForbidden, self.controller.update, @@ -149,6 +162,8 @@ def test_serializer(self): floating_ips=60, instances=70, injected_files=80, + security_groups=10, + security_group_rules=20, cores=90)) text = self.serializer.serialize(exemplar) @@ -172,6 +187,8 @@ def test_deserializer(self): floating_ips='60', instances='70', injected_files='80', + security_groups='10', + security_group_rules='20', cores='90')) intext = ("<?xml version='1.0' encoding='UTF-8'?>\n" '<quota_set>' @@ -184,6 +201,8 @@ def test_deserializer(self): '<floating_ips>60</floating_ips>' '<instances>70</instances>' '<injected_files>80</injected_files>' + '<security_groups>10</security_groups>' + '<security_group_rules>20</security_group_rules>' '<cores>90</cores>' '</quota_set>')
nova/tests/api/openstack/compute/contrib/test_security_groups.py+31 −0 modified@@ -25,12 +25,15 @@ from nova.api.openstack import wsgi import nova.db from nova import exception +from nova import flags from nova import test from nova.tests.api.openstack import fakes FAKE_UUID = 'a47ae74e-ab08-447f-8eee-ffd43fc46c16' +FLAGS = flags.FLAGS + class AttrDict(dict): def __getattr__(self, k): @@ -219,6 +222,18 @@ def test_create_security_group_non_string_description(self): self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, {'security_group': sg}) + def test_create_security_group_quota_limit(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-security-groups') + for num in range(1, FLAGS.quota_security_groups): + name = 'test%s' % num + sg = security_group_template(name=name) + res_dict = self.controller.create(req, {'security_group': sg}) + self.assertEqual(res_dict['security_group']['name'], name) + + sg = security_group_template() + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + def test_get_security_group_list(self): groups = [] for i, name in enumerate(['default', 'test']): @@ -894,6 +909,22 @@ def test_delete_non_existing_rule_id(self): self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, req, '22222222222222') + def test_create_rule_quota_limit(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-security-group-rules') + for num in range(100, 100 + FLAGS.quota_security_group_rules): + rule = { + 'ip_protocol': 'tcp', 'from_port': num, + 'to_port': num, 'parent_group_id': '2', 'group_id': '1' + } + self.controller.create(req, {'security_group_rule': rule}) + + rule = { + 'ip_protocol': 'tcp', 'from_port': '121', 'to_port': '121', + 'parent_group_id': '2', 'group_id': '1' + } + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + class TestSecurityGroupRulesXMLDeserializer(unittest.TestCase):
nova/tests/test_quota.py+52 −0 modified@@ -41,6 +41,8 @@ def setUp(self): quota_volumes=10, quota_gigabytes=1000, quota_floating_ips=10, + quota_security_groups=10, + quota_security_group_rules=20, quota_metadata_items=128, quota_injected_files=5, quota_injected_file_content_bytes=10 * 1024) @@ -57,6 +59,8 @@ def fake_quota_class_get_all_by_name(context, quota_class): volumes=5, gigabytes=500, floating_ips=5, + quota_security_groups=10, + quota_security_group_rules=20, metadata_items=64, injected_files=2, injected_file_content_bytes=5 * 1024, @@ -78,6 +82,8 @@ def fake_quota_get_all_by_project(context, project_id): volumes=2, gigabytes=250, floating_ips=2, + security_groups=5, + security_group_rules=10, metadata_items=32, injected_files=1, injected_file_content_bytes=2 * 1024, @@ -97,6 +103,8 @@ def test_default_quotas(self): volumes=10, gigabytes=1000, floating_ips=10, + security_groups=10, + security_group_rules=20, metadata_items=128, injected_files=5, injected_file_content_bytes=10 * 1024, @@ -109,6 +117,8 @@ def test_default_quotas_unlimited(self): quota_volumes=-1, quota_gigabytes=-1, quota_floating_ips=-1, + quota_security_groups=-1, + quota_security_group_rules=-1, quota_metadata_items=-1, quota_injected_files=-1, quota_injected_file_content_bytes=-1) @@ -120,6 +130,8 @@ def test_default_quotas_unlimited(self): volumes=-1, gigabytes=-1, floating_ips=-1, + security_groups=-1, + security_group_rules=-1, metadata_items=-1, injected_files=-1, injected_file_content_bytes=-1, @@ -135,6 +147,8 @@ def test_class_quotas_noclass(self): volumes=10, gigabytes=1000, floating_ips=10, + security_groups=10, + security_group_rules=20, metadata_items=128, injected_files=5, injected_file_content_bytes=10 * 1024, @@ -150,6 +164,8 @@ def test_class_quotas(self): volumes=5, gigabytes=500, floating_ips=5, + security_groups=10, + security_group_rules=20, metadata_items=64, injected_files=2, injected_file_content_bytes=5 * 1024, @@ -166,6 +182,8 @@ def test_project_quotas_defaults_noclass(self): volumes=10, gigabytes=1000, floating_ips=10, + security_groups=10, + security_group_rules=20, metadata_items=128, injected_files=5, injected_file_content_bytes=10 * 1024, @@ -182,6 +200,8 @@ def test_project_quotas_overrides_noclass(self): volumes=2, gigabytes=250, floating_ips=2, + security_groups=5, + security_group_rules=10, metadata_items=32, injected_files=1, injected_file_content_bytes=2 * 1024, @@ -199,6 +219,8 @@ def test_project_quotas_defaults_withclass(self): volumes=5, gigabytes=500, floating_ips=5, + security_groups=10, + security_group_rules=20, metadata_items=64, injected_files=2, injected_file_content_bytes=5 * 1024, @@ -216,6 +238,8 @@ def test_project_quotas_overrides_withclass(self): volumes=2, gigabytes=250, floating_ips=2, + security_groups=5, + security_group_rules=10, metadata_items=32, injected_files=1, injected_file_content_bytes=2 * 1024, @@ -404,6 +428,34 @@ def test_unlimited_floating_ips(self): floating_ips = quota.allowed_floating_ips(self.context, 101) self.assertEqual(floating_ips, 101) + def test_unlimited_security_groups(self): + self.flags(quota_security_groups=10) + security_groups = quota.allowed_security_groups(self.context, 100) + self.assertEqual(security_groups, 10) + db.quota_create(self.context, self.project_id, 'security_groups', -1) + security_groups = quota.allowed_security_groups(self.context, 100) + self.assertEqual(security_groups, 100) + security_groups = quota.allowed_security_groups(self.context, 101) + self.assertEqual(security_groups, 101) + + def test_unlimited_security_group_rules(self): + + def fake_security_group_rule_count_by_group(context, sec_group_id): + return 0 + + self.stubs.Set(db, 'security_group_rule_count_by_group', + fake_security_group_rule_count_by_group) + + self.flags(quota_security_group_rules=20) + rules = quota.allowed_security_group_rules(self.context, 1234, 100) + self.assertEqual(rules, 20) + db.quota_create(self.context, self.project_id, 'security_group_rules', + -1) + rules = quota.allowed_security_group_rules(self.context, 1234, 100) + self.assertEqual(rules, 100) + rules = quota.allowed_security_group_rules(self.context, 1234, 101) + self.assertEqual(rules, 101) + def test_unlimited_metadata_items(self): self.flags(quota_metadata_items=10) items = quota.allowed_metadata_items(self.context, 100)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- secunia.com/advisories/49034nvdVendor Advisory
- secunia.com/advisories/49048nvdVendor Advisory
- github.com/advisories/GHSA-hq3f-9gf7-73r8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2012-2101ghsaADVISORY
- lists.fedoraproject.org/pipermail/package-announce/2012-May/079434.htmlnvdWEB
- lists.fedoraproject.org/pipermail/package-announce/2012-May/079551.htmlnvdWEB
- ubuntu.com/usn/usn-1438-1nvdWEB
- bugs.launchpad.net/nova/+bug/969545nvdWEB
- exchange.xforce.ibmcloud.com/vulnerabilities/75243nvdWEB
- github.com/openstack/nova/commit/1f644d210557b1254f7c7b39424b09a45329ade7nvdWEB
- github.com/openstack/nova/commit/8c8735a73afb16d5856f0aa6088e9ae406c52bebnvdWEB
- github.com/openstack/nova/commit/a67db4586f70ed881d65e80035b2a25be195ce64nvdWEB
- github.com/pypa/advisory-database/tree/main/vulns/nova/PYSEC-2012-36.yamlghsaWEB
- lists.launchpad.net/openstack/msg10268.htmlnvdWEB
- www.osvdb.org/81641nvd
News mentions
0No linked articles in our index yet.