Skip to content

Commit 2514a9a

Browse files
add detection of modified route entries
1 parent cfba5b1 commit 2514a9a

File tree

2 files changed

+89
-0
lines changed

2 files changed

+89
-0
lines changed

reference-artifacts/Custom-Scripts/lza-upgrade/tools/network-drift-detection/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ This section details drift in subnets and their route tables. Careful inspection
7474

7575
|Key|Description|Notes and upgrade impact|
7676
|---|-----------|------------------------|
77+
|route_table_entries_mismatches|Difference in route entries between ASEA config and AWS account|Route entries may have been modified manually **the changes will be overwritten during the upgrade**. Note: the script doesn't handle all route target types, manual verification is still recommended|
7778
|route_tables_not_deployed|Route tables found in the ASEA config, but not in the AWS account|These route tables may have been manually removed and **will be re-created during the upgrade**|
7879
|route_tables_not_in_config|Route tables not found in the ASEA config, but are present in the AWS account|This is for information, these route tables won't be modified during the upgrade. See note below.|
7980
|subnet_route_table_mismatches|There is a configuration difference between the ASEA config and the current state of the route table|These route tables may have been manually modified, **the changes will be overwritten during the upgrade**|

reference-artifacts/Custom-Scripts/lza-upgrade/tools/network-drift-detection/lza-upgrade-check.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import boto3
1010
from botocore.exceptions import ClientError
1111

12+
if "LOGLEVEL" in os.environ:
13+
logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING"), format='%(levelname)s:%(message)s')
1214
logger = logging.getLogger(__name__)
1315

1416

@@ -446,6 +448,7 @@ def analyze_vpcs(vpc_from_config, account_list, role_to_assume, region):
446448
"subnets_not_deployed": [],
447449
"subnets_not_associated": [],
448450
"subnet_route_table_mismatches": [],
451+
"route_table_entries_mismatches": []
449452
}
450453
vpc_details = {}
451454

@@ -492,6 +495,16 @@ def analyze_vpcs(vpc_from_config, account_list, role_to_assume, region):
492495
drift["route_tables_not_deployed"].append(
493496
{"RouteTable": crt['name'], "Vpc": dv})
494497
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})
495508

496509
# check if there are more subnets than in the config
497510
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):
540553

541554
return {"Drift": drift, "VpcDetails": vpc_details}
542555

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+
543631

544632
def get_tgw_from_config(asea_config, region):
545633
"""

0 commit comments

Comments
 (0)