diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index 1bba89fd5..79ceb776f 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -318,3 +318,11 @@ class InvalidCIDRSubnetError(EC2ClientError): "InvalidParameterValue", "invalid CIDR subnet specification: {0}" .format(cidr)) + + +class RulesPerSecurityGroupLimitExceededError(EC2ClientError): + def __init__(self): + super(RulesPerSecurityGroupLimitExceededError, self).__init__( + "RulesPerSecurityGroupLimitExceeded", + 'The maximum number of rules per security group ' + 'has been reached.') diff --git a/moto/ec2/models.py b/moto/ec2/models.py index a3e333dc7..5d220a0e2 100755 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -58,6 +58,7 @@ from .exceptions import ( InvalidVpnGatewayIdError, InvalidVpnConnectionIdError, InvalidCustomerGatewayIdError, + RulesPerSecurityGroupLimitExceededError, ) from .utils import ( EC2_RESOURCE_TO_PREFIX, @@ -1264,6 +1265,16 @@ class SecurityGroup(TaggedEC2Resource): def add_egress_rule(self, rule): self.egress_rules.append(rule) + def get_number_of_ingress_rules(self): + return sum( + len(rule.ip_ranges) + len(rule.source_groups) + for rule in self.ingress_rules) + + def get_number_of_egress_rules(self): + return sum( + len(rule.ip_ranges) + len(rule.source_groups) + for rule in self.egress_rules) + class SecurityGroupBackend(object): @@ -1360,6 +1371,10 @@ class SecurityGroupBackend(object): if not is_valid_cidr(cidr): raise InvalidCIDRSubnetError(cidr=cidr) + self._verify_group_will_respect_rule_count_limit( + group, group.get_number_of_ingress_rules(), + ip_ranges, source_group_names, source_group_ids) + source_group_names = source_group_names if source_group_names else [] source_group_ids = source_group_ids if source_group_ids else [] @@ -1425,6 +1440,10 @@ class SecurityGroupBackend(object): if not is_valid_cidr(cidr): raise InvalidCIDRSubnetError(cidr=cidr) + self._verify_group_will_respect_rule_count_limit( + group, group.get_number_of_egress_rules(), + ip_ranges, source_group_names, source_group_ids) + source_group_names = source_group_names if source_group_names else [] source_group_ids = source_group_ids if source_group_ids else [] @@ -1472,6 +1491,20 @@ class SecurityGroupBackend(object): return security_rule raise InvalidPermissionNotFoundError() + def _verify_group_will_respect_rule_count_limit( + self, group, current_rule_nb, + ip_ranges, source_group_names=None, source_group_ids=None): + max_nb_rules = 50 if group.vpc_id else 100 + future_group_nb_rules = current_rule_nb + if ip_ranges: + future_group_nb_rules += len(ip_ranges) + if source_group_ids: + future_group_nb_rules += len(source_group_ids) + if source_group_names: + future_group_nb_rules += len(source_group_names) + if future_group_nb_rules > max_nb_rules: + raise RulesPerSecurityGroupLimitExceededError + class SecurityGroupIngress(object): diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 204380562..83dad6f0c 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -379,6 +379,136 @@ def test_authorize_all_protocols_with_no_port_specification(): sg.rules[0].to_port.should.equal(None) +@mock_ec2 +def test_sec_group_rule_limit(): + ec2_conn = boto.connect_ec2() + sg = ec2_conn.create_security_group('test', 'test') + other_sg = ec2_conn.create_security_group('test_2', 'test_other') + + # INGRESS + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', + cidr_ip=['{0}.0.0.0/0'.format(i) for i in range(110)]) + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + + sg.rules.should.be.empty + # authorize a rule targeting a different sec group (because this count too) + success = ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', + src_security_group_group_id=other_sg.id) + success.should.be.true + # fill the rules up the limit + success = ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', + cidr_ip=['{0}.0.0.0/0'.format(i) for i in range(99)]) + success.should.be.true + # verify that we cannot authorize past the limit for a CIDR IP + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', cidr_ip=['100.0.0.0/0']) + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + # verify that we cannot authorize past the limit for a different sec group + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', + src_security_group_group_id=other_sg.id) + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + + # EGRESS + # authorize a rule targeting a different sec group (because this count too) + ec2_conn.authorize_security_group_egress( + group_id=sg.id, ip_protocol='-1', + src_group_id=other_sg.id) + # fill the rules up the limit + # remember that by default, when created a sec group contains 1 egress rule + # so our other_sg rule + 98 CIDR IP rules + 1 by default == 100 the limit + for i in range(98): + ec2_conn.authorize_security_group_egress( + group_id=sg.id, ip_protocol='-1', + cidr_ip='{0}.0.0.0/0'.format(i)) + # verify that we cannot authorize past the limit for a CIDR IP + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group_egress( + group_id=sg.id, ip_protocol='-1', + cidr_ip='101.0.0.0/0') + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + # verify that we cannot authorize past the limit for a different sec group + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group_egress( + group_id=sg.id, ip_protocol='-1', + src_group_id=other_sg.id) + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + + +@mock_ec2 +def test_sec_group_rule_limit_vpc(): + ec2_conn = boto.connect_ec2() + vpc_conn = boto.connect_vpc() + + vpc = vpc_conn.create_vpc('10.0.0.0/8') + + sg = ec2_conn.create_security_group('test', 'test', vpc_id=vpc.id) + other_sg = ec2_conn.create_security_group('test_2', 'test', vpc_id=vpc.id) + + # INGRESS + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', + cidr_ip=['{0}.0.0.0/0'.format(i) for i in range(110)]) + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + + sg.rules.should.be.empty + # authorize a rule targeting a different sec group (because this count too) + success = ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', + src_security_group_group_id=other_sg.id) + success.should.be.true + # fill the rules up the limit + success = ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', + cidr_ip=['{0}.0.0.0/0'.format(i) for i in range(49)]) + # verify that we cannot authorize past the limit for a CIDR IP + success.should.be.true + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', cidr_ip=['100.0.0.0/0']) + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + # verify that we cannot authorize past the limit for a different sec group + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group( + group_id=sg.id, ip_protocol='-1', + src_security_group_group_id=other_sg.id) + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + + # EGRESS + # authorize a rule targeting a different sec group (because this count too) + ec2_conn.authorize_security_group_egress( + group_id=sg.id, ip_protocol='-1', + src_group_id=other_sg.id) + # fill the rules up the limit + # remember that by default, when created a sec group contains 1 egress rule + # so our other_sg rule + 48 CIDR IP rules + 1 by default == 50 the limit + for i in range(48): + ec2_conn.authorize_security_group_egress( + group_id=sg.id, ip_protocol='-1', + cidr_ip='{0}.0.0.0/0'.format(i)) + # verify that we cannot authorize past the limit for a CIDR IP + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group_egress( + group_id=sg.id, ip_protocol='-1', + cidr_ip='50.0.0.0/0') + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + # verify that we cannot authorize past the limit for a different sec group + with assert_raises(EC2ResponseError) as cm: + ec2_conn.authorize_security_group_egress( + group_id=sg.id, ip_protocol='-1', + src_group_id=other_sg.id) + cm.exception.error_code.should.equal('RulesPerSecurityGroupLimitExceeded') + + + + ''' Boto3 '''