stackport 0.3.6__tar.gz → 0.3.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {stackport-0.3.6/stackport.egg-info → stackport-0.3.7}/PKG-INFO +1 -1
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/ec2.py +156 -0
- {stackport-0.3.6 → stackport-0.3.7}/pyproject.toml +1 -1
- {stackport-0.3.6 → stackport-0.3.7/stackport.egg-info}/PKG-INFO +1 -1
- {stackport-0.3.6 → stackport-0.3.7}/stackport.egg-info/SOURCES.txt +6 -6
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_ec2_routes.py +273 -0
- stackport-0.3.6/ui/dist/assets/StateMachineGraph-DnspmX4c.js → stackport-0.3.7/ui/dist/assets/StateMachineGraph-C3a2WdTx.js +1 -1
- stackport-0.3.6/ui/dist/assets/index-CVBAWnD3.js → stackport-0.3.7/ui/dist/assets/index-B-seC0pm.js +123 -123
- stackport-0.3.7/ui/dist/assets/index-BZr5kWvs.css +1 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/index.html +2 -2
- stackport-0.3.6/ui/dist/assets/index-BoJK2una.css +0 -1
- {stackport-0.3.6 → stackport-0.3.7}/LICENSE +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/MANIFEST.in +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/README.md +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/__init__.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/aws_client.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/cache.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/cli.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/config.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/endpoint_store.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/main.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/__init__.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/common.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/dynamodb.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/endpoints.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/iam.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/lambda_svc.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/logs.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/rds.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/resources.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/s3.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/secretsmanager.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/sqs.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/stats.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/stepfunctions.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/routes/tags.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/__init__.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/dynamodb.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/endpoints.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/lambda_svc.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/s3.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/secretsmanager.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/sqs.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/stepfunctions.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/schemas/tags.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/backend/websocket.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/setup.cfg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/stackport.egg-info/dependency_links.txt +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/stackport.egg-info/entry_points.txt +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/stackport.egg-info/requires.txt +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/stackport.egg-info/top_level.txt +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_cache.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_cli.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_client.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_config.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_dynamodb_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_endpoint_store.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_endpoints.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_iam_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_lambda_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_logs_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_multi_endpoint.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_rds_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_readonly_middleware.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_registries.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_s3_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_s3_upload_limit_env.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_secretsmanager_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_sqs_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_tags_routes.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/tests/test_websocket.py +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/assets/stepfunctions-graph-B6nvU4OB.js +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/acm.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/apigateway.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/appsync.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/athena.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/cloudformation.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/cloudfront.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/cognito-idp.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/dynamodb.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/ec2.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/ecr.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/ecs.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/elasticache.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/elasticfilesystem.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/elasticloadbalancing.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/elasticmapreduce.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/events.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/firehose.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/glue.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/iam.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/kinesis.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/kms.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/lambda.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/logs.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/monitoring.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/rds.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/route53.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/s3.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/secretsmanager.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/ses.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/sns.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/sqs.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/ssm.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/stepfunctions.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/aws-icons/wafv2.svg +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/favicon.png +0 -0
- {stackport-0.3.6 → stackport-0.3.7}/ui/dist/favicon.svg +0 -0
|
@@ -301,6 +301,162 @@ def list_security_groups(ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[
|
|
|
301
301
|
raise HTTPException(status_code=500, detail=str(e))
|
|
302
302
|
|
|
303
303
|
|
|
304
|
+
def _get_rule_id(rule: dict, rule_type: str, idx: int) -> str:
|
|
305
|
+
"""Generate a rule ID based on rule properties."""
|
|
306
|
+
return f"{rule_type}-sgrule-{idx:04d}"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _get_ip_version(ip_range: dict | None, ipv6_range: dict | None) -> str:
|
|
310
|
+
"""Determine IP version from rule."""
|
|
311
|
+
if ipv6_range:
|
|
312
|
+
return "IPv6"
|
|
313
|
+
if ip_range:
|
|
314
|
+
cidr = ip_range.get("CidrIp", "")
|
|
315
|
+
return "IPv6" if ":" in cidr else "IPv4"
|
|
316
|
+
return "IPv4"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _format_port_range(protocol: str, from_port: int | None, to_port: int | None) -> str:
|
|
320
|
+
"""Format port range for display."""
|
|
321
|
+
if protocol == "-1" or protocol == "all":
|
|
322
|
+
return "All"
|
|
323
|
+
if from_port is None and to_port is None:
|
|
324
|
+
return "All"
|
|
325
|
+
if from_port is not None and to_port is not None and from_port != to_port:
|
|
326
|
+
return f"{from_port}-{to_port}"
|
|
327
|
+
if from_port is not None:
|
|
328
|
+
return str(from_port)
|
|
329
|
+
return "All"
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _expand_rules(ip_permissions: list[dict], rule_type: str) -> list[dict]:
|
|
333
|
+
"""Expand IP permissions into individual rule rows."""
|
|
334
|
+
expanded = []
|
|
335
|
+
rule_idx = 0
|
|
336
|
+
|
|
337
|
+
for perm in ip_permissions:
|
|
338
|
+
protocol = perm.get("IpProtocol", "tcp")
|
|
339
|
+
from_port = perm.get("FromPort")
|
|
340
|
+
to_port = perm.get("ToPort")
|
|
341
|
+
|
|
342
|
+
# Normalize protocol display
|
|
343
|
+
if protocol == "-1":
|
|
344
|
+
protocol = "All"
|
|
345
|
+
|
|
346
|
+
# Expand IPv4 ranges
|
|
347
|
+
for ip_range in perm.get("IpRanges", []):
|
|
348
|
+
expanded.append({
|
|
349
|
+
"ruleId": _get_rule_id(perm, rule_type, rule_idx),
|
|
350
|
+
"ipVersion": "IPv4",
|
|
351
|
+
"type": rule_type.capitalize(),
|
|
352
|
+
"protocol": protocol,
|
|
353
|
+
"portRange": _format_port_range(protocol, from_port, to_port),
|
|
354
|
+
"source": ip_range.get("CidrIp", ""),
|
|
355
|
+
"description": ip_range.get("Description", ""),
|
|
356
|
+
})
|
|
357
|
+
rule_idx += 1
|
|
358
|
+
|
|
359
|
+
# Expand IPv6 ranges
|
|
360
|
+
for ipv6_range in perm.get("Ipv6Ranges", []):
|
|
361
|
+
expanded.append({
|
|
362
|
+
"ruleId": _get_rule_id(perm, rule_type, rule_idx),
|
|
363
|
+
"ipVersion": "IPv6",
|
|
364
|
+
"type": rule_type.capitalize(),
|
|
365
|
+
"protocol": protocol,
|
|
366
|
+
"portRange": _format_port_range(protocol, from_port, to_port),
|
|
367
|
+
"source": ipv6_range.get("CidrIpv6", ""),
|
|
368
|
+
"description": ipv6_range.get("Description", ""),
|
|
369
|
+
})
|
|
370
|
+
rule_idx += 1
|
|
371
|
+
|
|
372
|
+
# Expand prefix list IDs
|
|
373
|
+
for prefix_list in perm.get("PrefixListIds", []):
|
|
374
|
+
expanded.append({
|
|
375
|
+
"ruleId": _get_rule_id(perm, rule_type, rule_idx),
|
|
376
|
+
"ipVersion": "IPv4",
|
|
377
|
+
"type": rule_type.capitalize(),
|
|
378
|
+
"protocol": protocol,
|
|
379
|
+
"portRange": _format_port_range(protocol, from_port, to_port),
|
|
380
|
+
"source": prefix_list.get("PrefixListId", ""),
|
|
381
|
+
"description": "",
|
|
382
|
+
})
|
|
383
|
+
rule_idx += 1
|
|
384
|
+
|
|
385
|
+
# Expand user ID group pairs (security group references)
|
|
386
|
+
for group_pair in perm.get("UserIdGroupPairs", []):
|
|
387
|
+
expanded.append({
|
|
388
|
+
"ruleId": _get_rule_id(perm, rule_type, rule_idx),
|
|
389
|
+
"ipVersion": "IPv4",
|
|
390
|
+
"type": rule_type.capitalize(),
|
|
391
|
+
"protocol": protocol,
|
|
392
|
+
"portRange": _format_port_range(protocol, from_port, to_port),
|
|
393
|
+
"source": group_pair.get("GroupId", "") or group_pair.get("GroupName", ""),
|
|
394
|
+
"description": group_pair.get("Description", ""),
|
|
395
|
+
})
|
|
396
|
+
rule_idx += 1
|
|
397
|
+
|
|
398
|
+
# If no rules found, return empty list (don't add placeholder)
|
|
399
|
+
return expanded
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@router.get("/security-groups/{group_id}/inbound")
|
|
403
|
+
def get_security_group_inbound_rules(group_id: str, ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[str, Any]:
|
|
404
|
+
"""Get inbound rules for a specific security group with detailed rule information."""
|
|
405
|
+
try:
|
|
406
|
+
client = get_client("ec2", **ep.client_kwargs())
|
|
407
|
+
response = client.describe_security_groups(GroupIds=[group_id])
|
|
408
|
+
|
|
409
|
+
if not response.get("SecurityGroups"):
|
|
410
|
+
raise HTTPException(status_code=404, detail=f"Security group {group_id} not found")
|
|
411
|
+
|
|
412
|
+
sg = response["SecurityGroups"][0]
|
|
413
|
+
inbound_rules = _expand_rules(sg.get("IpPermissions", []), "inbound")
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
"groupId": group_id,
|
|
417
|
+
"groupName": sg["GroupName"],
|
|
418
|
+
"inboundRules": inbound_rules,
|
|
419
|
+
}
|
|
420
|
+
except HTTPException:
|
|
421
|
+
raise
|
|
422
|
+
except client.exceptions.ClientError as e:
|
|
423
|
+
error_code = e.response["Error"]["Code"]
|
|
424
|
+
if error_code == "InvalidGroupId.NotFound":
|
|
425
|
+
raise HTTPException(status_code=404, detail=f"Security group {group_id} not found")
|
|
426
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
427
|
+
except Exception as e:
|
|
428
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@router.get("/security-groups/{group_id}/outbound")
|
|
432
|
+
def get_security_group_outbound_rules(group_id: str, ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[str, Any]:
|
|
433
|
+
"""Get outbound rules for a specific security group with detailed rule information."""
|
|
434
|
+
try:
|
|
435
|
+
client = get_client("ec2", **ep.client_kwargs())
|
|
436
|
+
response = client.describe_security_groups(GroupIds=[group_id])
|
|
437
|
+
|
|
438
|
+
if not response.get("SecurityGroups"):
|
|
439
|
+
raise HTTPException(status_code=404, detail=f"Security group {group_id} not found")
|
|
440
|
+
|
|
441
|
+
sg = response["SecurityGroups"][0]
|
|
442
|
+
outbound_rules = _expand_rules(sg.get("IpPermissionsEgress", []), "outbound")
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
"groupId": group_id,
|
|
446
|
+
"groupName": sg["GroupName"],
|
|
447
|
+
"outboundRules": outbound_rules,
|
|
448
|
+
}
|
|
449
|
+
except HTTPException:
|
|
450
|
+
raise
|
|
451
|
+
except client.exceptions.ClientError as e:
|
|
452
|
+
error_code = e.response["Error"]["Code"]
|
|
453
|
+
if error_code == "InvalidGroupId.NotFound":
|
|
454
|
+
raise HTTPException(status_code=404, detail=f"Security group {group_id} not found")
|
|
455
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
456
|
+
except Exception as e:
|
|
457
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
458
|
+
|
|
459
|
+
|
|
304
460
|
@router.get("/vpcs")
|
|
305
461
|
def list_vpcs(ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[str, Any]:
|
|
306
462
|
"""List all VPCs with their subnets."""
|
|
@@ -13,9 +13,9 @@ backend/websocket.py
|
|
|
13
13
|
backend/../ui/dist/favicon.png
|
|
14
14
|
backend/../ui/dist/favicon.svg
|
|
15
15
|
backend/../ui/dist/index.html
|
|
16
|
-
backend/../ui/dist/assets/StateMachineGraph-
|
|
17
|
-
backend/../ui/dist/assets/index-
|
|
18
|
-
backend/../ui/dist/assets/index-
|
|
16
|
+
backend/../ui/dist/assets/StateMachineGraph-C3a2WdTx.js
|
|
17
|
+
backend/../ui/dist/assets/index-B-seC0pm.js
|
|
18
|
+
backend/../ui/dist/assets/index-BZr5kWvs.css
|
|
19
19
|
backend/../ui/dist/assets/stepfunctions-graph-B6nvU4OB.js
|
|
20
20
|
backend/../ui/dist/aws-icons/acm.svg
|
|
21
21
|
backend/../ui/dist/aws-icons/apigateway.svg
|
|
@@ -107,9 +107,9 @@ tests/test_websocket.py
|
|
|
107
107
|
ui/dist/favicon.png
|
|
108
108
|
ui/dist/favicon.svg
|
|
109
109
|
ui/dist/index.html
|
|
110
|
-
ui/dist/assets/StateMachineGraph-
|
|
111
|
-
ui/dist/assets/index-
|
|
112
|
-
ui/dist/assets/index-
|
|
110
|
+
ui/dist/assets/StateMachineGraph-C3a2WdTx.js
|
|
111
|
+
ui/dist/assets/index-B-seC0pm.js
|
|
112
|
+
ui/dist/assets/index-BZr5kWvs.css
|
|
113
113
|
ui/dist/assets/stepfunctions-graph-B6nvU4OB.js
|
|
114
114
|
ui/dist/aws-icons/acm.svg
|
|
115
115
|
ui/dist/aws-icons/apigateway.svg
|
|
@@ -362,3 +362,276 @@ class TestListAutoscalingGroups:
|
|
|
362
362
|
assert instance["lifecycleState"] == "InService"
|
|
363
363
|
assert instance["healthStatus"] == "Healthy"
|
|
364
364
|
assert instance["availabilityZone"] == "us-east-1a"
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class TestGetSecurityGroupInboundRules:
|
|
368
|
+
@patch("backend.routes.ec2.get_client")
|
|
369
|
+
def test_get_security_group_inbound_rules(self, mock_get_client):
|
|
370
|
+
mock_ec2 = MagicMock()
|
|
371
|
+
mock_get_client.return_value = mock_ec2
|
|
372
|
+
mock_ec2.describe_security_groups.return_value = {
|
|
373
|
+
"SecurityGroups": [
|
|
374
|
+
{
|
|
375
|
+
"GroupId": "sg-123456",
|
|
376
|
+
"GroupName": "web-sg",
|
|
377
|
+
"Description": "Web security group",
|
|
378
|
+
"VpcId": "vpc-abc123",
|
|
379
|
+
"IpPermissions": [
|
|
380
|
+
{
|
|
381
|
+
"IpProtocol": "tcp",
|
|
382
|
+
"FromPort": 80,
|
|
383
|
+
"ToPort": 80,
|
|
384
|
+
"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTP access"}],
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"IpProtocol": "tcp",
|
|
388
|
+
"FromPort": 443,
|
|
389
|
+
"ToPort": 443,
|
|
390
|
+
"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTPS access"}],
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"IpProtocol": "tcp",
|
|
394
|
+
"FromPort": 22,
|
|
395
|
+
"ToPort": 22,
|
|
396
|
+
"IpRanges": [{"CidrIp": "10.0.0.0/8", "Description": "SSH access"}],
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
"IpPermissionsEgress": [],
|
|
400
|
+
"Tags": [],
|
|
401
|
+
}
|
|
402
|
+
]
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
resp = client.get("/api/ec2/security-groups/sg-123456/inbound")
|
|
406
|
+
assert resp.status_code == 200
|
|
407
|
+
data = resp.json()
|
|
408
|
+
assert data["groupId"] == "sg-123456"
|
|
409
|
+
assert data["groupName"] == "web-sg"
|
|
410
|
+
assert len(data["inboundRules"]) == 3
|
|
411
|
+
rule = data["inboundRules"][0]
|
|
412
|
+
assert rule["ruleId"].startswith("inbound-sgrule-")
|
|
413
|
+
assert rule["protocol"] == "tcp"
|
|
414
|
+
assert rule["portRange"] == "80"
|
|
415
|
+
assert rule["ipVersion"] == "IPv4"
|
|
416
|
+
assert rule["type"] == "Inbound"
|
|
417
|
+
assert rule["source"] == "0.0.0.0/0"
|
|
418
|
+
assert rule["description"] == "HTTP access"
|
|
419
|
+
|
|
420
|
+
@patch("backend.routes.ec2.get_client")
|
|
421
|
+
def test_get_security_group_inbound_rules_not_found(self, mock_get_client):
|
|
422
|
+
mock_ec2 = MagicMock()
|
|
423
|
+
mock_get_client.return_value = mock_ec2
|
|
424
|
+
mock_ec2.describe_security_groups.return_value = {"SecurityGroups": []}
|
|
425
|
+
|
|
426
|
+
resp = client.get("/api/ec2/security-groups/sg-nonexistent/inbound")
|
|
427
|
+
assert resp.status_code == 404
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class TestGetSecurityGroupOutboundRules:
|
|
431
|
+
@patch("backend.routes.ec2.get_client")
|
|
432
|
+
def test_get_security_group_outbound_rules(self, mock_get_client):
|
|
433
|
+
mock_ec2 = MagicMock()
|
|
434
|
+
mock_get_client.return_value = mock_ec2
|
|
435
|
+
mock_ec2.describe_security_groups.return_value = {
|
|
436
|
+
"SecurityGroups": [
|
|
437
|
+
{
|
|
438
|
+
"GroupId": "sg-123456",
|
|
439
|
+
"GroupName": "web-sg",
|
|
440
|
+
"Description": "Web security group",
|
|
441
|
+
"VpcId": "vpc-abc123",
|
|
442
|
+
"IpPermissions": [],
|
|
443
|
+
"IpPermissionsEgress": [
|
|
444
|
+
{
|
|
445
|
+
"IpProtocol": "tcp",
|
|
446
|
+
"FromPort": 443,
|
|
447
|
+
"ToPort": 443,
|
|
448
|
+
"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTPS outbound"}],
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"IpProtocol": "tcp",
|
|
452
|
+
"FromPort": 80,
|
|
453
|
+
"ToPort": 80,
|
|
454
|
+
"IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTP outbound"}],
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
"Tags": [],
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
resp = client.get("/api/ec2/security-groups/sg-123456/outbound")
|
|
463
|
+
assert resp.status_code == 200
|
|
464
|
+
data = resp.json()
|
|
465
|
+
assert data["groupId"] == "sg-123456"
|
|
466
|
+
assert data["groupName"] == "web-sg"
|
|
467
|
+
assert len(data["outboundRules"]) == 2
|
|
468
|
+
rule = data["outboundRules"][0]
|
|
469
|
+
assert rule["ruleId"].startswith("outbound-sgrule-")
|
|
470
|
+
assert rule["protocol"] == "tcp"
|
|
471
|
+
assert rule["portRange"] == "443"
|
|
472
|
+
assert rule["ipVersion"] == "IPv4"
|
|
473
|
+
assert rule["type"] == "Outbound"
|
|
474
|
+
assert rule["source"] == "0.0.0.0/0"
|
|
475
|
+
assert rule["description"] == "HTTPS outbound"
|
|
476
|
+
|
|
477
|
+
@patch("backend.routes.ec2.get_client")
|
|
478
|
+
def test_get_security_group_outbound_rules_not_found(self, mock_get_client):
|
|
479
|
+
mock_ec2 = MagicMock()
|
|
480
|
+
mock_get_client.return_value = mock_ec2
|
|
481
|
+
mock_ec2.describe_security_groups.return_value = {"SecurityGroups": []}
|
|
482
|
+
|
|
483
|
+
resp = client.get("/api/ec2/security-groups/sg-nonexistent/outbound")
|
|
484
|
+
assert resp.status_code == 404
|
|
485
|
+
|
|
486
|
+
@patch("backend.routes.ec2.get_client")
|
|
487
|
+
def test_get_security_group_outbound_rules_empty(self, mock_get_client):
|
|
488
|
+
mock_ec2 = MagicMock()
|
|
489
|
+
mock_get_client.return_value = mock_ec2
|
|
490
|
+
mock_ec2.describe_security_groups.return_value = {
|
|
491
|
+
"SecurityGroups": [
|
|
492
|
+
{
|
|
493
|
+
"GroupId": "sg-123456",
|
|
494
|
+
"GroupName": "empty-sg",
|
|
495
|
+
"Description": "Empty security group",
|
|
496
|
+
"VpcId": "vpc-abc123",
|
|
497
|
+
"IpPermissions": [],
|
|
498
|
+
"IpPermissionsEgress": [],
|
|
499
|
+
"Tags": [],
|
|
500
|
+
}
|
|
501
|
+
]
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
resp = client.get("/api/ec2/security-groups/sg-123456/outbound")
|
|
505
|
+
assert resp.status_code == 200
|
|
506
|
+
data = resp.json()
|
|
507
|
+
assert data["groupId"] == "sg-123456"
|
|
508
|
+
assert data["groupName"] == "empty-sg"
|
|
509
|
+
assert len(data["outboundRules"]) == 0
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class TestGetSecurityGroupInboundRulesProtocolAll:
|
|
513
|
+
"""Test handling of protocol -1 (all traffic)."""
|
|
514
|
+
|
|
515
|
+
@patch("backend.routes.ec2.get_client")
|
|
516
|
+
def test_get_security_group_inbound_rules_protocol_all(self, mock_get_client):
|
|
517
|
+
"""Test that protocol -1 is converted to 'All' and port range shows 'All'."""
|
|
518
|
+
mock_ec2 = MagicMock()
|
|
519
|
+
mock_get_client.return_value = mock_ec2
|
|
520
|
+
mock_ec2.describe_security_groups.return_value = {
|
|
521
|
+
"SecurityGroups": [
|
|
522
|
+
{
|
|
523
|
+
"GroupId": "sg-123456",
|
|
524
|
+
"GroupName": "ecs-tasks-sg",
|
|
525
|
+
"Description": "ECS tasks security group",
|
|
526
|
+
"VpcId": "vpc-abc123",
|
|
527
|
+
"IpPermissions": [
|
|
528
|
+
{
|
|
529
|
+
"IpProtocol": "-1",
|
|
530
|
+
"FromPort": None,
|
|
531
|
+
"ToPort": None,
|
|
532
|
+
"IpRanges": [{"CidrIp": "10.1.0.0/16", "Description": "VPC traffic"}],
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
"IpPermissionsEgress": [],
|
|
536
|
+
"Tags": [],
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
resp = client.get("/api/ec2/security-groups/sg-123456/inbound")
|
|
542
|
+
assert resp.status_code == 200
|
|
543
|
+
data = resp.json()
|
|
544
|
+
assert data["groupId"] == "sg-123456"
|
|
545
|
+
assert data["groupName"] == "ecs-tasks-sg"
|
|
546
|
+
assert len(data["inboundRules"]) == 1
|
|
547
|
+
rule = data["inboundRules"][0]
|
|
548
|
+
assert rule["ruleId"].startswith("inbound-sgrule-")
|
|
549
|
+
assert rule["protocol"] == "All"
|
|
550
|
+
assert rule["portRange"] == "All"
|
|
551
|
+
assert rule["ipVersion"] == "IPv4"
|
|
552
|
+
assert rule["type"] == "Inbound"
|
|
553
|
+
assert rule["source"] == "10.1.0.0/16"
|
|
554
|
+
assert rule["description"] == "VPC traffic"
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class TestGetSecurityGroupOutboundRulesProtocolAll:
|
|
558
|
+
"""Test handling of protocol -1 (all traffic) for outbound rules."""
|
|
559
|
+
|
|
560
|
+
@patch("backend.routes.ec2.get_client")
|
|
561
|
+
def test_get_security_group_outbound_rules_protocol_all(self, mock_get_client):
|
|
562
|
+
"""Test that protocol -1 is converted to 'All' and port range shows 'All' for outbound."""
|
|
563
|
+
mock_ec2 = MagicMock()
|
|
564
|
+
mock_get_client.return_value = mock_ec2
|
|
565
|
+
mock_ec2.describe_security_groups.return_value = {
|
|
566
|
+
"SecurityGroups": [
|
|
567
|
+
{
|
|
568
|
+
"GroupId": "sg-00220f1150c89205a",
|
|
569
|
+
"GroupName": "web-saas-development-ecs-tasks-sg",
|
|
570
|
+
"Description": "ECS tasks security group",
|
|
571
|
+
"VpcId": "vpc-abc123",
|
|
572
|
+
"IpPermissions": [],
|
|
573
|
+
"IpPermissionsEgress": [
|
|
574
|
+
{
|
|
575
|
+
"IpProtocol": "-1",
|
|
576
|
+
"FromPort": None,
|
|
577
|
+
"ToPort": None,
|
|
578
|
+
"IpRanges": [{"CidrIp": "0.0.0.0/0"}],
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
"Tags": [],
|
|
582
|
+
}
|
|
583
|
+
]
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
resp = client.get("/api/ec2/security-groups/sg-00220f1150c89205a/outbound")
|
|
587
|
+
assert resp.status_code == 200
|
|
588
|
+
data = resp.json()
|
|
589
|
+
assert data["groupId"] == "sg-00220f1150c89205a"
|
|
590
|
+
assert data["groupName"] == "web-saas-development-ecs-tasks-sg"
|
|
591
|
+
assert len(data["outboundRules"]) == 1
|
|
592
|
+
rule = data["outboundRules"][0]
|
|
593
|
+
assert rule["ruleId"].startswith("outbound-sgrule-")
|
|
594
|
+
assert rule["protocol"] == "All"
|
|
595
|
+
assert rule["portRange"] == "All"
|
|
596
|
+
assert rule["ipVersion"] == "IPv4"
|
|
597
|
+
assert rule["type"] == "Outbound"
|
|
598
|
+
assert rule["source"] == "0.0.0.0/0"
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
class TestSecurityGroupRulesPortRanges:
|
|
602
|
+
"""Test handling of port ranges."""
|
|
603
|
+
|
|
604
|
+
@patch("backend.routes.ec2.get_client")
|
|
605
|
+
def test_get_security_group_port_range(self, mock_get_client):
|
|
606
|
+
"""Test that port ranges are formatted correctly."""
|
|
607
|
+
mock_ec2 = MagicMock()
|
|
608
|
+
mock_get_client.return_value = mock_ec2
|
|
609
|
+
mock_ec2.describe_security_groups.return_value = {
|
|
610
|
+
"SecurityGroups": [
|
|
611
|
+
{
|
|
612
|
+
"GroupId": "sg-123456",
|
|
613
|
+
"GroupName": "port-range-sg",
|
|
614
|
+
"Description": "Security group with port range",
|
|
615
|
+
"VpcId": "vpc-abc123",
|
|
616
|
+
"IpPermissions": [
|
|
617
|
+
{
|
|
618
|
+
"IpProtocol": "tcp",
|
|
619
|
+
"FromPort": 8000,
|
|
620
|
+
"ToPort": 9000,
|
|
621
|
+
"IpRanges": [{"CidrIp": "10.0.0.0/8"}],
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
"IpPermissionsEgress": [],
|
|
625
|
+
"Tags": [],
|
|
626
|
+
}
|
|
627
|
+
]
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
resp = client.get("/api/ec2/security-groups/sg-123456/inbound")
|
|
631
|
+
assert resp.status_code == 200
|
|
632
|
+
data = resp.json()
|
|
633
|
+
assert len(data["inboundRules"]) == 1
|
|
634
|
+
rule = data["inboundRules"][0]
|
|
635
|
+
assert rule["protocol"] == "tcp"
|
|
636
|
+
assert rule["portRange"] == "8000-9000"
|
|
637
|
+
assert rule["source"] == "10.0.0.0/8"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as L,C as z,a as O,b as Y,G as J,j as n,r as m,B as j}from"./index-
|
|
1
|
+
import{c as L,C as z,a as O,b as Y,G as J,j as n,r as m,B as j}from"./index-B-seC0pm.js";import{T as B}from"./stepfunctions-graph-B6nvU4OB.js";/**
|
|
2
2
|
* @license lucide-react v1.7.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|