A security group is a stateful, allow-only firewall: there are no deny rules, and anything not explicitly permitted is dropped. Given a group's inbound rules and a batch of attempted connections, decide each one.
Evaluation is a pure OR over the rules — a connection is allowed if any rule matches, denied otherwise. Default-deny is what makes a security group safe: forgetting a rule closes a port, it never opens one.
A rule matches a connection when all three hold:
Rules are unordered, and order makes no difference to the outcome.
Implement:
def evaluate_rules(rules, connections):
Return a list of "ALLOW" / "DENY" strings, one per connection, in input order.
rules = [{"protocol": "tcp", "from_port": 22, "to_port": 22, "cidr": "10.0.0.0/16"},
{"protocol": "tcp", "from_port": 8000, "to_port": 8010, "cidr": "0.0.0.0/0"}]
conns = [{"protocol": "tcp", "port": 22, "source": "10.0.3.4"},
{"protocol": "tcp", "port": 22, "source": "203.0.113.9"},
{"protocol": "tcp", "port": 8010, "source": "203.0.113.9"}]
print(evaluate_rules(rules, conns))
Output:
['ALLOW', 'DENY', 'ALLOW']
SSH is open only to the VPC, so the public source is dropped; port 8010 is the inclusive upper end of the serving range and is open to everyone.
rules = [{"protocol": "tcp", "from_port": 22, "to_port": 22, "cidr": "10.0.0.0/16"}, {"protocol": "tcp", "from_port": 8000, "to_port": 8010, "cidr": "0.0.0.0/0"}]
conns = [{"protocol": "tcp", "port": 22, "source": "10.0.3.4"}, {"protocol": "tcp", "port": 22, "source": "203.0.113.9"}, {"protocol": "tcp", "port": 8010, "source": "203.0.113.9"}]
print(evaluate_rules(rules, conns))['ALLOW', 'DENY', 'ALLOW']
The first connection matches the SSH rule on all three axes. The second is the same port and protocol but its source is outside 10.0.0.0/16, and since no other rule covers port 22 it is denied by default. The third hits the inclusive upper bound of the 8000-8010 range from a source inside 0.0.0.0/0, so it is allowed.
"-1" matches any protocol and is given with ports 0-655350.0.0.0/0 matches every address