|
9 | 9 | import boto3 |
10 | 10 | from botocore.exceptions import ClientError |
11 | 11 |
|
| 12 | +if "LOGLEVEL" in os.environ: |
| 13 | + logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING"), format='%(levelname)s:%(message)s') |
12 | 14 | logger = logging.getLogger(__name__) |
13 | 15 |
|
14 | 16 |
|
@@ -446,6 +448,7 @@ def analyze_vpcs(vpc_from_config, account_list, role_to_assume, region): |
446 | 448 | "subnets_not_deployed": [], |
447 | 449 | "subnets_not_associated": [], |
448 | 450 | "subnet_route_table_mismatches": [], |
| 451 | + "route_table_entries_mismatches": [] |
449 | 452 | } |
450 | 453 | vpc_details = {} |
451 | 454 |
|
@@ -492,6 +495,16 @@ def analyze_vpcs(vpc_from_config, account_list, role_to_assume, region): |
492 | 495 | drift["route_tables_not_deployed"].append( |
493 | 496 | {"RouteTable": crt['name'], "Vpc": dv}) |
494 | 497 | continue |
| 498 | + elif len(drt) > 0 : |
| 499 | + if len(drt) > 1: |
| 500 | + logger.error( |
| 501 | + f"More than one route table named {crt['name']} is deployed! LZA upgrade already executed?") |
| 502 | + |
| 503 | + # matching config and deployed route, compare the entries |
| 504 | + rteDrift = compare_route_table(crt, drt[0]) |
| 505 | + if len(rteDrift) > 0: |
| 506 | + drift["route_table_entries_mismatches"].append( |
| 507 | + {"RouteTable": crt['name'], "Vpc": dv, "Entries": rteDrift}) |
495 | 508 |
|
496 | 509 | # check if there are more subnets than in the config |
497 | 510 | d_subnets = get_vpc_subnets(client, deployed_vpcs[dv]) |
@@ -540,6 +553,81 @@ def analyze_vpcs(vpc_from_config, account_list, role_to_assume, region): |
540 | 553 |
|
541 | 554 | return {"Drift": drift, "VpcDetails": vpc_details} |
542 | 555 |
|
| 556 | +def compare_route_table(crt, drt): |
| 557 | + """ |
| 558 | + Compare entries of configured and deployed route table |
| 559 | + crt: configured route table in ASEA config |
| 560 | + drt: deployed route table in AWS VPC |
| 561 | + """ |
| 562 | + drift = [] |
| 563 | + |
| 564 | + #ignoring gateway endpoint routes (S3 and DynamoDB) and local subnet routes |
| 565 | + cRoutes = [r for r in crt.get('routes', []) if r['target'].lower() != 's3' and r['target'].lower() != 'dynamodb'] |
| 566 | + dRoutes = [r for r in drt.get('Routes', []) if 'DestinationCidrBlock' in r and r.get("GatewayId", "") != "local"] |
| 567 | + |
| 568 | + if len(cRoutes) != len(dRoutes): |
| 569 | + logger.warning(f"Different number of routes in config and deployed route table for {crt['name']}") |
| 570 | + |
| 571 | + #check if all route entries in config matches what is deployed |
| 572 | + for cr in cRoutes: |
| 573 | + if cr['target'].lower() == "pcx": |
| 574 | + logger.warning(f"Route {cr['destination']} is a VPC peering route. Skipping check") |
| 575 | + continue |
| 576 | + |
| 577 | + dr = [r for r in dRoutes if cr['destination'] == r['DestinationCidrBlock']] |
| 578 | + if len(dr) == 0: |
| 579 | + logger.warning(f"Route {cr['destination']} exists in config but not found in deployed route table") |
| 580 | + drift.append({"Route": cr['destination'], "Reason": "Not found in deployed route table"}) |
| 581 | + continue |
| 582 | + elif len(dr) == 1: |
| 583 | + dre = dr[0] |
| 584 | + if cr['target'] == "IGW": |
| 585 | + if not ("GatewayId" in dre and dre['GatewayId'].startswith("igw-")): |
| 586 | + logger.warning(f"Route {cr['destination']} not matched to IGW") |
| 587 | + drift.append({"Route": cr['destination'], "Reason": "Not matched to IGW"}) |
| 588 | + elif cr['target'] == "TGW": |
| 589 | + if not "TransitGatewayId" in dre: |
| 590 | + logger.warning(f"Route {cr['destination']} not matched to TGW") |
| 591 | + drift.append({"Route": cr['destination'], "Reason": "Not matched to TGW"}) |
| 592 | + elif cr['target'].startswith("NFW_"): |
| 593 | + if not ("GatewayId" in dre and dre['GatewayId'].startswith("vpce-")): |
| 594 | + logger.warning(f"Route {cr['destination']} not matched to NFW VPCE") |
| 595 | + drift.append({"Route": cr['destination'], "Reason": "Not matched to NFW VPCE"}) |
| 596 | + elif cr['target'].startswith("NATGW_"): |
| 597 | + if not "NatGatewayId" in dre: |
| 598 | + logger.warning(f"Route {cr['destination']} not matched to NATGW") |
| 599 | + drift.append({"Route": cr['destination'], "Reason": "Not matched to NATGW"}) |
| 600 | + elif cr['target'] == "VGW": |
| 601 | + if not ("GatewayId" in dre and dre['GatewayId'].startswith("vgw-")): |
| 602 | + logger.warning(f"Route {cr['destination']} not matched to VGW") |
| 603 | + drift.append({"Route": cr['destination'], "Reason": "Not matched to VGW"}) |
| 604 | + elif cr['target'].lower() == "firewall": |
| 605 | + if not "InstanceId" in dre: |
| 606 | + logger.warning(f"Route {cr['destination']} not matched to firewall instance") |
| 607 | + drift.append({"Route": cr['destination'], "Reason": "Not matched to firewall instance"}) |
| 608 | + else: |
| 609 | + logger.error(f"Route target {cr['target']} is not supported!") |
| 610 | + drift.append({"Route": cr['destination'], "Reason": f"Route target {cr['target']} is not supported!"}) |
| 611 | + else: |
| 612 | + #this should not be possible! |
| 613 | + logger.error(f"More than one route with destination {cr['destination']} is deployed!") |
| 614 | + drift.append({"Route": cr['destination'], "Reason": f"More than one route with destination {cr['destination']} found"}) |
| 615 | + |
| 616 | + #check if there are route entries deployed that are not in the config |
| 617 | + for dr in dRoutes: |
| 618 | + if 'VpcPeeringConnectionId' in dr: |
| 619 | + logger.warning(f"Route {dr['DestinationCidrBlock']} is a VPC peering route. Skipping check") |
| 620 | + continue |
| 621 | + |
| 622 | + cr = [r for r in cRoutes if r['destination'] == dr['DestinationCidrBlock']] |
| 623 | + if len(cr) == 0: |
| 624 | + logger.warning(f"Route {dr['DestinationCidrBlock']} exists in deployed route table but not found in config") |
| 625 | + drift.append({"Route": dr['DestinationCidrBlock'], "Reason": "Not found in config"}) |
| 626 | + |
| 627 | + return drift |
| 628 | + |
| 629 | + |
| 630 | + |
543 | 631 |
|
544 | 632 | def get_tgw_from_config(asea_config, region): |
545 | 633 | """ |
|
0 commit comments