Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scanners/dns-processor/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ NOTIFICATION_API_KEY=
NOTIFICATION_API_URL=
NOTIFICATION_ASSET_CHANGE_ALERT_EMAIL=
ALERT_SUBS=

ENABLE_MX_DIFF_ALERTS=false
1 change: 1 addition & 0 deletions scanners/dns-processor/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Empty file.
84 changes: 84 additions & 0 deletions scanners/dns-processor/check_mx_diff/check_mx_diff.py
Original file line number Diff line number Diff line change
@@ -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
88 changes: 5 additions & 83 deletions scanners/dns-processor/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down