@@ -1382,7 +1382,6 @@ def test_storage_security_group_deduplication(mocker, test_datadir):
13821382
13831383 When head node, compute nodes, and login nodes all use the same security group (sg-12345678),
13841384 only one set of ingress/egress rules should be created for that security group, not three separate sets.
1385- The deduplicated rule uses the first occurrence's settings (Head node), which uses all protocols (-1).
13861385 """
13871386 mock_aws_api (mocker )
13881387 mock_bucket (mocker )
@@ -1395,23 +1394,102 @@ def test_storage_security_group_deduplication(mocker, test_datadir):
13951394 cluster_config = cluster_config , bucket = dummy_cluster_bucket (), stack_name = "clustername"
13961395 )
13971396
1398- # Test both EFS and FSx storage types
1399- for storage_type in ["EFS" , "FSX" ]:
1400- storage_sg_ingress_rules = [
1401- (name , resource )
1402- for name , resource in generated_template ["Resources" ].items ()
1403- if resource ["Type" ] == "AWS::EC2::SecurityGroupIngress"
1404- and name .startswith (storage_type )
1405- and "SecurityGroup" in name
1406- ]
1407-
1408- # Verify deduplication: only 2 rules instead of 4 (Head, Compute, Login share same SG + Storage SG)
1409- assert_that (len (storage_sg_ingress_rules )).is_equal_to (2 )
1397+ # The EFS storage security group must have 2 ingress rules:
1398+ # * allow traffic from cluster nodes (port 2049)
1399+ # * allow traffic from storage nodes (all traffic)
1400+ efs_sg_ingress_rules = [
1401+ (name , resource )
1402+ for name , resource in generated_template ["Resources" ].items ()
1403+ if resource ["Type" ] == "AWS::EC2::SecurityGroupIngress" and name .startswith ("EFS" ) and "SecurityGroup" in name
1404+ ]
1405+ assert_that (len (efs_sg_ingress_rules )).is_equal_to (2 )
1406+
1407+ # The FSx Lustre storage security group must have 3 rules:
1408+ # * allow traffic from cluster nodes (port 2049)
1409+ # * allow traffic from cluster nodes (ports 1018-1023)
1410+ # * allow traffic from storage nodes (all traffic)
1411+ fsx_sg_ingress_rules = [
1412+ (name , resource )
1413+ for name , resource in generated_template ["Resources" ].items ()
1414+ if resource ["Type" ] == "AWS::EC2::SecurityGroupIngress" and name .startswith ("FSX" ) and "SecurityGroup" in name
1415+ ]
1416+ assert_that (len (fsx_sg_ingress_rules )).is_equal_to (3 )
14101417
1411- # Verify each unique source security group has exactly one ingress rule
1418+ # Verify each storage type has the expected unique source security groups
1419+ for _storage_type , rules in [("EFS" , efs_sg_ingress_rules ), ("FSX" , fsx_sg_ingress_rules )]:
14121420 source_sgs = {
14131421 str (rule ["Properties" ].get ("SourceSecurityGroupId" ))
1414- for _ , rule in storage_sg_ingress_rules
1422+ for _ , rule in rules
14151423 if rule ["Properties" ].get ("SourceSecurityGroupId" )
14161424 }
1417- assert_that (len (storage_sg_ingress_rules )).is_equal_to (len (source_sgs ))
1425+ # Should have 2 unique source SGs (shared SG + storage SG)
1426+ assert_that (len (source_sgs )).is_equal_to (2 )
1427+
1428+
1429+ def test_storage_security_group_port_restrictions (mocker , test_datadir ):
1430+ """
1431+ Test that storage security group rules use restricted ports for head/compute/login nodes.
1432+
1433+ Security group rules should follow these principles:
1434+ 1. Storage-to-Storage: Allow all traffic (protocol -1)
1435+ 2. Head/Compute/Login nodes to EFS: Allow only TCP port 2049
1436+ 3. Head/Compute/Login nodes to FSx Lustre: Allow only TCP ports 988 and 1018-1023
1437+ """
1438+ mock_aws_api (mocker )
1439+ mock_bucket (mocker )
1440+ mock_bucket_object_utils (mocker )
1441+
1442+ input_yaml = load_yaml_dict (test_datadir / "config-shared-sg.yaml" )
1443+ cluster_config = ClusterSchema (cluster_name = "clustername" ).load (input_yaml )
1444+
1445+ generated_template , _ = CDKTemplateBuilder ().build_cluster_template (
1446+ cluster_config = cluster_config , bucket = dummy_cluster_bucket (), stack_name = "clustername"
1447+ )
1448+
1449+ # Test EFS storage - should only allow port 2049
1450+ efs_ingress_rules = [
1451+ (name , resource )
1452+ for name , resource in generated_template ["Resources" ].items ()
1453+ if resource ["Type" ] == "AWS::EC2::SecurityGroupIngress" and name .startswith ("EFS" ) and "SecurityGroup" in name
1454+ ]
1455+
1456+ for name , rule in efs_ingress_rules :
1457+ props = rule ["Properties" ]
1458+ if "Storage" in name :
1459+ # Storage-to-Storage: all traffic allowed
1460+ assert_that (props ["IpProtocol" ]).is_equal_to ("-1" )
1461+ assert_that (props ["FromPort" ]).is_equal_to (0 )
1462+ assert_that (props ["ToPort" ]).is_equal_to (65535 )
1463+ else :
1464+ # Head/Compute/Login to EFS: only TCP port 2049
1465+ assert_that (props ["IpProtocol" ]).is_equal_to ("tcp" )
1466+ assert_that (props ["FromPort" ]).is_equal_to (2049 )
1467+ assert_that (props ["ToPort" ]).is_equal_to (2049 )
1468+
1469+ # Test FSx Lustre storage - should only allow ports 988 and 1018-1023
1470+ fsx_ingress_rules = [
1471+ (name , resource )
1472+ for name , resource in generated_template ["Resources" ].items ()
1473+ if resource ["Type" ] == "AWS::EC2::SecurityGroupIngress" and name .startswith ("FSX" ) and "SecurityGroup" in name
1474+ ]
1475+
1476+ # Collect non-storage rules to verify FSx ports
1477+ fsx_node_rules = [(name , rule ) for name , rule in fsx_ingress_rules if "Storage" not in name ]
1478+ fsx_storage_rules = [(name , rule ) for name , rule in fsx_ingress_rules if "Storage" in name ]
1479+
1480+ # Verify Storage-to-Storage rule allows all traffic
1481+ for _name , rule in fsx_storage_rules :
1482+ props = rule ["Properties" ]
1483+ assert_that (props ["IpProtocol" ]).is_equal_to ("-1" )
1484+ assert_that (props ["FromPort" ]).is_equal_to (0 )
1485+ assert_that (props ["ToPort" ]).is_equal_to (65535 )
1486+
1487+ # Verify Head/Compute/Login rules use TCP and FSx Lustre ports (988, 1018-1023)
1488+ fsx_ports_found = set ()
1489+ for _name , rule in fsx_node_rules :
1490+ props = rule ["Properties" ]
1491+ assert_that (props ["IpProtocol" ]).is_equal_to ("tcp" )
1492+ fsx_ports_found .add ((props ["FromPort" ], props ["ToPort" ]))
1493+
1494+ # Should have rules for port 988 and port range 1018-1023
1495+ assert_that (fsx_ports_found ).contains ((988 , 988 ), (1018 , 1023 ))
0 commit comments