diff --git a/scanners/dns-processor/.env.example b/scanners/dns-processor/.env.example index ffe8c2082c..f534d27ae3 100644 --- a/scanners/dns-processor/.env.example +++ b/scanners/dns-processor/.env.example @@ -8,3 +8,5 @@ NOTIFICATION_API_KEY= NOTIFICATION_API_URL= NOTIFICATION_ASSET_CHANGE_ALERT_EMAIL= ALERT_SUBS= + +ENABLE_MX_DIFF_ALERTS=false diff --git a/scanners/dns-processor/Dockerfile b/scanners/dns-processor/Dockerfile index 50b3d0770f..c7f28dc56a 100644 --- a/scanners/dns-processor/Dockerfile +++ b/scanners/dns-processor/Dockerfile @@ -34,6 +34,7 @@ COPY --from=python-builder /working/install/lib /usr/local/lib COPY service.py dns_processor_cli.py ./ COPY dns_processor ./dns_processor +COPY check_mx_diff ./check_mx_diff COPY notify ./notify RUN adduser -D scanner diff --git a/scanners/dns-processor/check_mx_diff/__init__.py b/scanners/dns-processor/check_mx_diff/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scanners/dns-processor/check_mx_diff/check_mx_diff.py b/scanners/dns-processor/check_mx_diff/check_mx_diff.py new file mode 100644 index 0000000000..02d54eac0c --- /dev/null +++ b/scanners/dns-processor/check_mx_diff/check_mx_diff.py @@ -0,0 +1,84 @@ +import os + +from notify.send_mx_diff_email_alerts import send_mx_diff_email_alerts + + +def check_mx_diff(processed_results, domain_id, db): + new_mx = processed_results.get("mx_records").get("hosts") + mx_record_diff = False + # fetch most recent scan of domain + last_mx_cursor = db.aql.execute( + """ + FOR v, e IN 1..1 OUTBOUND @domain_id domainsDNS + SORT v.timestamp DESC + LIMIT 1 + RETURN v + """, + bind_vars={"domain_id": domain_id}, + ) + # if no previous scan, return False as we can't compare records + if last_mx_cursor.empty(): + return False + + last_mx = last_mx_cursor.next().get("mxRecords", {}).get("hosts", []) + + # compare mx_records to most recent scan + # if different, set mx_records_diff to True + # check number of hosts + if len(new_mx) != len(last_mx): + mx_record_diff = True + else: + # check hostnames + hostnames_new = [] + hostnames_last = [] + for i in range(len(new_mx)): + hostnames_new.append(new_mx[i]["hostname"]) + hostnames_last.append(last_mx[i]["hostname"]) + + if set(hostnames_new) != set(hostnames_last): + mx_record_diff = True + + # fetch domain org, filter by verified and externally managed + domain_org_cursor = db.aql.execute( + """ + FOR v, e IN 1..1 INBOUND @domain_id claims + FILTER v.verified == true + LIMIT 1 + RETURN v + """, + bind_vars={"domain_id": domain_id}, + ) + # if no org, return early + if domain_org_cursor.empty(): + return mx_record_diff + + domain_org = domain_org_cursor.next() + + # send alerts if true + if mx_record_diff and os.getenv("ALERT_SUBS"): + current_val = [] + for host in new_mx: + current_val.append(f"{host['hostname']} {host['preference']}") + if len(current_val) == 0: + current_val = "null" + else: + current_val = ";".join(current_val) + + prev_val = [] + for host in last_mx: + prev_val.append(f"{host['hostname']} {host['preference']}") + + if len(prev_val) == 0: + prev_val = "null" + else: + prev_val = ";".join(prev_val) + + send_mx_diff_email_alerts( + domain=processed_results.get("domain"), + record_type="MX", + org=domain_org, + prev_val=prev_val, + current_val=current_val, + ) + + return mx_record_diff diff --git a/scanners/dns-processor/service.py b/scanners/dns-processor/service.py index 8f8f2e9fb2..28bc3353d6 100644 --- a/scanners/dns-processor/service.py +++ b/scanners/dns-processor/service.py @@ -21,7 +21,7 @@ from nats.errors import TimeoutError as NatsTimeoutError from dns_processor.dns_processor import process_results -from notify.send_mx_diff_email_alerts import send_mx_diff_email_alerts +from check_mx_diff.check_mx_diff import check_mx_diff load_dotenv() @@ -60,6 +60,8 @@ CNAME_MONITOR_ONLY_LIST = os.getenv("CNAME_MONITOR_ONLY_LIST", "").split(",") SERVICE_ACCOUNT_EMAIL = os.getenv("SERVICE_ACCOUNT_EMAIL") +ENABLE_MX_DIFF_ALERTS = os.getenv("ENABLE_MX_DIFF_ALERTS", "false").lower() == "true" + SCAN_THREAD_COUNT = int(os.getenv("SCAN_THREAD_COUNT", 1)) # Establish DB connection @@ -92,87 +94,6 @@ def snake_to_camel(d): } -def check_mx_diff(processed_results, domain_id): - new_mx = processed_results.get("mx_records").get("hosts") - mx_record_diff = False - # fetch most recent scan of domain - last_mx_cursor = db.aql.execute( - """ - FOR v, e IN 1..1 OUTBOUND @domain_id domainsDNS - SORT v.timestamp DESC - LIMIT 1 - RETURN v - """, - bind_vars={"domain_id": domain_id}, - ) - # if no previous scan, return False as we can't compare records - if last_mx_cursor.empty(): - return False - - last_mx = last_mx_cursor.next().get("mxRecords", {}).get("hosts", []) - - # compare mx_records to most recent scan - # if different, set mx_records_diff to True - # check number of hosts - if len(new_mx) != len(last_mx): - mx_record_diff = True - else: - # check hostnames - hostnames_new = [] - hostnames_last = [] - for i in range(len(new_mx)): - hostnames_new.append(new_mx[i]["hostname"]) - hostnames_last.append(last_mx[i]["hostname"]) - - if set(hostnames_new) != set(hostnames_last): - mx_record_diff = True - - # fetch domain org, filter by verified and externally managed - domain_org_cursor = db.aql.execute( - """ - FOR v, e IN 1..1 INBOUND @domain_id claims - FILTER v.verified == true - LIMIT 1 - RETURN v - """, - bind_vars={"domain_id": domain_id}, - ) - # if no org, return early - if domain_org_cursor.empty(): - return mx_record_diff - - domain_org = domain_org_cursor.next() - - # send alerts if true - if mx_record_diff and os.getenv("ALERT_SUBS"): - current_val = [] - for host in new_mx: - current_val.append(f"{host['hostname']} {host['preference']}") - if len(current_val) == 0: - current_val = "null" - else: - current_val = ";".join(current_val) - - prev_val = [] - for host in last_mx: - prev_val.append(f"{host['hostname']} {host['preference']}") - - if len(prev_val) == 0: - prev_val = "null" - else: - prev_val = ";".join(prev_val) - - send_mx_diff_email_alerts( - domain=processed_results.get("domain"), - record_type="MX", - org=domain_org, - prev_val=prev_val, - current_val=current_val, - ) - - return mx_record_diff - - def is_cname_target(resolve_chain, domains): # Check if any CNAME record in the resolve chain points to a domain in the provided list for rrset in resolve_chain: @@ -209,10 +130,11 @@ def process_msg(msg): processed_results = process_results(scan_results) try: - if processed_results.get("mx_records") is not None: + if ENABLE_MX_DIFF_ALERTS and processed_results.get("mx_records") is not None: mx_record_diff = check_mx_diff( processed_results=processed_results, domain_id=f"domains/{domain_key}", + db=db, ) processed_results["mx_records"].update({"diff": mx_record_diff}) except Exception as e: