VYPR
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.

PackageAffected versionsPatched versions
novaPyPI
< 12.0.0a012.0.0a0

Affected products

3
  • OpenStack/Nova3 versions
    cpe:2.3:a:openstack:nova:2011.3:*:*:*:*:*:*:*+ 2 more
    • cpe:2.3:a:openstack:nova:2011.3:*:*:*:*:*:*:*
    • cpe:2.3:a:openstack:nova:2012.1:*:*:*:*:*:*:*
    • cpe:2.3:a:openstack:nova:folsom:*:*:*:*:*:*:*

Patches

3
a67db4586f70

Implement quotas for security groups.

https://github.com/openstack/novaDan PrinceApr 12, 2012via ghsa
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)
    
8c8735a73afb

Implement quotas for security groups.

https://github.com/openstack/novaDan PrinceApr 11, 2012via ghsa
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)
    
1f644d210557

Implement security group quotas.

https://github.com/openstack/novaDan PrinceApr 3, 2012via ghsa
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

News mentions

0

No linked articles in our index yet.