From afbe4c3419c2d5f76182a84cec4b0bc1bc529a9e Mon Sep 17 00:00:00 2001 From: FestiveKyle Date: Fri, 6 Feb 2026 13:54:42 -0400 Subject: [PATCH] Add feature to mark claims as monitor-only for some CNAME targets --- .../scanners/dns-processor/deployment.yaml | 10 +++ scanners/dns-processor/service.py | 84 +++++++++++++++++++ services/summaries/summaries.py | 3 +- 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/k8s/apps/bases/scanners/dns-processor/deployment.yaml b/k8s/apps/bases/scanners/dns-processor/deployment.yaml index d90b6fa53..874be6a55 100644 --- a/k8s/apps/bases/scanners/dns-processor/deployment.yaml +++ b/k8s/apps/bases/scanners/dns-processor/deployment.yaml @@ -44,6 +44,16 @@ spec: key: DB_USER - name: NATS_SERVERS value: nats://nats.pubsub:4222 + - name: CNAME_MONITOR_ONLY_LIST + valueFrom: + secretKeyRef: + name: scanners + key: CNAME_MONITOR_ONLY_LIST + - name: SERVICE_ACCOUNT_EMAIL + valueFrom: + secretKeyRef: + name: scanners + key: SERVICE_ACCOUNT_EMAIL - name: NOTIFICATION_API_KEY valueFrom: secretKeyRef: diff --git a/scanners/dns-processor/service.py b/scanners/dns-processor/service.py index 6511487d7..8f8f2e9fb 100644 --- a/scanners/dns-processor/service.py +++ b/scanners/dns-processor/service.py @@ -57,6 +57,9 @@ DB_NAME = os.getenv("DB_NAME") DB_URL = os.getenv("DB_URL") +CNAME_MONITOR_ONLY_LIST = os.getenv("CNAME_MONITOR_ONLY_LIST", "").split(",") +SERVICE_ACCOUNT_EMAIL = os.getenv("SERVICE_ACCOUNT_EMAIL") + SCAN_THREAD_COUNT = int(os.getenv("SCAN_THREAD_COUNT", 1)) # Establish DB connection @@ -170,6 +173,18 @@ def check_mx_diff(processed_results, domain_id): 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: + for record in rrset: + record_parts = record.split(" ") + if len(record_parts) >= 5 and record_parts[3] == "CNAME": + record_target = record_parts[4].strip(".") + if record_target in domains: + return True + return False + + def process_msg(msg): subject = msg.subject reply = msg.reply @@ -370,6 +385,75 @@ def process_msg(msg): f"Error while updating domain after retrying for received message: {msg}: {error_str}" ) + if processed_results.get("cname_record") is not None and CNAME_MONITOR_ONLY_LIST: + try: + # Use resolve chain instead of CNAME as some domains have CNAMEs that point to other CNAMEs before reaching the final target domain + is_cname_target_in_monitor_only_list = is_cname_target( + resolve_chain=processed_results.get("resolve_chain", []), + domains=CNAME_MONITOR_ONLY_LIST, + ) + + if is_cname_target_in_monitor_only_list: + # Get current claim asset states of domain + approved_state_claims_cursor = db.aql.execute( + """ + FOR v, e IN 1..1 INBOUND @domain_id claims + FILTER v.verified == true + FILTER e.assetState == "approved" + RETURN e + """, + bind_vars={"domain_id": domain["_id"]}, + ) + if approved_state_claims_cursor.empty(): + logger.debug( + f"No approved claims for domain with CNAME in monitor-only list for received message: {msg}" + ) + else: + approved_state_claims = [claim for claim in approved_state_claims_cursor] + for claim in approved_state_claims: + try: + logger.info(f"Domain with CNAME in monitor-only list has approved claim, updating claim state to monitor-only for claim: {claim}") + claim["assetState"] = "monitor-only" + db.collection("claims").update(claim) + + insert_activity = { + "timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[ + :-3 + ] + + "Z", + "initiatedBy": { + "id": "dns-processor", + "userName": SERVICE_ACCOUNT_EMAIL, + "role": "service", + }, + "target": { + "resource": domain["domain"], + "updatedProperties": [ + { + "name": "assetState", + "oldValue": "approved", + "newValue": "monitor-only", + } + ], + "organization": {"id": claim["_from"].split("/")[1]}, + "resourceType": "domain", + }, + "action": "update", + "reason": None, + } + db.collection("auditLogs").insert(insert_activity) + + except Exception as e: + logger.error( + f"Error while processing claim with approved state for domain with CNAME in monitor-only list for received message: {msg}: {str(e)}" + ) + continue + + except Exception as e: + logger.error( + f"Error while parsing CNAME record for received message: {msg}: {str(e)}" + ) + except Exception as e: logger.error( f"Error while inserting processed results for received message: {msg}: {str(e)} \n\nFull traceback: {traceback.format_exc()}" diff --git a/services/summaries/summaries.py b/services/summaries/summaries.py index 1709acc54..910369ff4 100644 --- a/services/summaries/summaries.py +++ b/services/summaries/summaries.py @@ -34,6 +34,7 @@ def domain_has_verified_claim(domain, db): """ FOR v, e IN 1..1 INBOUND @domain_id claims FILTER v.verified == true + FILTER e.assetState == "approved" RETURN v """, bind_vars={"domain_id": domain["_id"]}, @@ -334,7 +335,7 @@ def update_org_summaries(host=DB_URL, name=DB_NAME, user=DB_USER, password=DB_PA logging.error(f"Error processing domain {domain['_id']}: {e}") continue - dmarc_phase_total = ( + dmarc_phase_total = ( dmarc_phase_assess + dmarc_phase_deploy + dmarc_phase_enforce