" if k == "page_token" else v) for k, v in params.items()}
+ applogger.debug(f"{consts.LOG_PREFIX} get_page: request parameters {shown}")
+ return body
+
+ def iter_pages(self, inserted_after_time: str, inserted_after_event_id: Optional[str],
+ filters: Dict[str, str]) -> Iterator[List[dict]]:
+ """Yield each page of events, oldest first, until the last page (no next_page_token).
+
+ Every request of one read keeps the same inserted_after_time and inserted_after_event_id, as the API
+ requires when page_token is used.
+ """
+ base = {
+ "organization_id": self.organization_id,
+ "page_size": consts.PAGE_SIZE,
+ "inserted_after_time": inserted_after_time,
+ }
+ if inserted_after_event_id:
+ base["inserted_after_event_id"] = inserted_after_event_id
+ base.update(filters)
+ page_token = None
+ while True:
+ params = dict(base)
+ if page_token:
+ params["page_token"] = page_token
+ result = (self.get_page(params) or {}).get("result") or {}
+ events = result.get("risk_events") or []
+ if events:
+ yield events
+ page_token = result.get("next_page_token")
+ if not page_token:
+ return
+
+ def iter_suppliers(self) -> Iterator[List[dict]]:
+ """Yield each page of suppliers of the default supply chain until the last page (no next_page_token)."""
+ base = {
+ "organization_id": self.organization_id,
+ "page_size": consts.SUPPLIERS_PAGE_SIZE,
+ "order_by": "created_at_asc",
+ }
+ return self._iter_list(base, consts.SUPPLIERS_PATH, "suppliers")
+
+ def iter_risks(self) -> Iterator[List[dict]]:
+ """Yield each page of risks of the default supply chain until the last page (no next_page_token)."""
+ base = {"organization_id": self.organization_id, "page_size": consts.RISKS_PAGE_SIZE}
+ return self._iter_list(base, consts.RISKS_PATH, "risks")
+
+ def _iter_list(self, base: Dict[str, str], path: str, key: str) -> Iterator[List[dict]]:
+ """Yield the pages of a list endpoint. key is the result field holding the items."""
+ page_token, seen = None, set()
+ while True:
+ params = dict(base)
+ if page_token:
+ params["page_token"] = page_token
+ result = (self.get_page(params, path) or {}).get("result") or {}
+ items = result.get(key) or []
+ if items:
+ yield items
+ page_token = result.get("next_page_token")
+ if not page_token:
+ return
+ if page_token in seen:
+ raise CensysException(f"Censys returned the same {key} page token twice, stopping to avoid a loop.")
+ seen.add(page_token)
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/checkpoint.py b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/checkpoint.py
new file mode 100644
index 00000000000..40461ff73cf
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/checkpoint.py
@@ -0,0 +1,62 @@
+"""Checkpoint of the last ingested risk event, stored in an Azure Storage Table."""
+import datetime
+import hashlib
+from typing import Optional, Tuple
+
+from azure.core.exceptions import ResourceNotFoundError
+from azure.data.tables import TableServiceClient, UpdateMode
+
+from .consts import CHECKPOINT_PARTITION_KEY, LOG_PREFIX
+from .exceptions import CensysException
+from .logger import applogger
+
+
+def checkpoint_key(*parts: str) -> str:
+ """Build a stable row key from the settings that define what is collected.
+
+ A different organization or filter set (change types, severity, rapid response, risk type IDs) gets its own
+ checkpoint, so changing one of them never skips events. The suppliers are not part of the key: changing the
+ supplier names continues from the current checkpoint.
+ """
+ return hashlib.sha256("|".join(parts).encode("utf-8")).hexdigest()[:32]
+
+
+class CheckpointManager:
+ """Reads and writes the (inserted_at, event_id) checkpoint."""
+
+ def __init__(self, connection_string: str, table_name: str):
+ """Create the table client, creating the table when needed."""
+ self.table_name = table_name
+ try:
+ service = TableServiceClient.from_connection_string(conn_str=connection_string)
+ self.table = service.create_table_if_not_exists(table_name)
+ except Exception as err:
+ applogger.error(f"{LOG_PREFIX} CheckpointManager.__init__: error accessing checkpoint table "
+ f"{table_name}: {err}")
+ raise CensysException(f"Error accessing checkpoint table {table_name}: {err}")
+
+ def get(self, row_key: str) -> Optional[Tuple[str, str]]:
+ """Return (inserted_at, event_id) or None when no checkpoint exists."""
+ try:
+ entity = self.table.get_entity(partition_key=CHECKPOINT_PARTITION_KEY, row_key=row_key)
+ return entity.get("InsertedAt"), entity.get("EventId")
+ except ResourceNotFoundError:
+ return None
+ except Exception as err:
+ applogger.error(f"{LOG_PREFIX} CheckpointManager.get: error reading checkpoint: {err}")
+ raise CensysException(f"Error reading checkpoint: {err}")
+
+ def set(self, row_key: str, inserted_at: str, event_id: str) -> None:
+ """Store the inserted_at and id of the last event that was ingested."""
+ entity = {
+ "PartitionKey": CHECKPOINT_PARTITION_KEY,
+ "RowKey": row_key,
+ "InsertedAt": inserted_at,
+ "EventId": event_id,
+ "UpdatedAt": datetime.datetime.now(datetime.timezone.utc).isoformat(),
+ }
+ try:
+ self.table.upsert_entity(mode=UpdateMode.REPLACE, entity=entity)
+ except Exception as err:
+ applogger.error(f"{LOG_PREFIX} CheckpointManager.set: error saving checkpoint: {err}")
+ raise CensysException(f"Error saving checkpoint: {err}")
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/consts.py b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/consts.py
new file mode 100644
index 00000000000..16e99565fe7
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/consts.py
@@ -0,0 +1,57 @@
+"""Constants and configuration read from the Function App settings."""
+import os
+
+API_BASE_URL = "https://api.platform.censys.io"
+RISK_EVENTS_PATH = "/v1/supplychains/default/risk-events"
+SUPPLIERS_PATH = "/v1/supplychains/default/suppliers"
+SUPPLIERS_PAGE_SIZE = 100
+RISKS_PATH = "/v1/supplychains/default/risks"
+RISKS_PAGE_SIZE = 100
+CONNECTOR_VERSION = "1.0.0"
+USER_AGENT_PRODUCT = "CensysSRMSSentinel"
+
+# Names in SupplierNames and RiskNames are separated by an asterisk, so a name can contain a comma. At most
+# MAX_SUPPLIER_IDS ids are sent in one request: the supplier_ids parameter is part of the URL, and the API rejected
+# URLs of about 16000 characters (400 ids) while it accepted about 8000 characters (200 ids).
+SUPPLIER_NAME_SEPARATOR = "*"
+MAX_SUPPLIER_IDS = 200
+MAX_SUPPLIER_NAMES_LENGTH = 500
+MAX_RISK_NAMES_LENGTH = 500
+MAX_RISK_TYPE_IDS = 100
+
+PAGE_SIZE = 1000
+REQUEST_TIMEOUT = 60
+MAX_RETRIES = 5
+DEFAULT_START_TIME = "1970-01-01T00:00:00Z"
+RETENTION_DAYS = 29
+# No Censys API call starts after MAX_RUN_SECONDS, and no call may run past HARD_LIMIT_SECONDS, so the run always
+# ends before the 10 minute function timeout. Progress is checkpointed after every page.
+MAX_RUN_SECONDS = 540
+HARD_LIMIT_SECONDS = 580
+
+API_TOKEN = os.environ.get("CensysApiToken")
+ORGANIZATION_ID = os.environ.get("OrganizationId")
+INITIAL_START_TIME = os.environ.get("InitialStartTime", "")
+SUPPLIER_NAMES = os.environ.get("SupplierNames", "")
+RISK_NAMES = os.environ.get("RiskNames", "")
+CHANGE_TYPES = os.environ.get("ChangeTypes", "")
+SEVERITY = os.environ.get("Severity", "")
+RAPID_RESPONSE = os.environ.get("RapidResponse", "")
+
+ALLOWED_CHANGE_TYPES = ("opened", "resolved", "reopened")
+ALLOWED_SEVERITIES = ("LOW", "MEDIUM", "HIGH", "CRITICAL")
+
+CONN_STRING = os.environ.get("AzureWebJobsStorage")
+CHECKPOINT_TABLE_NAME = os.environ.get("CheckpointTableName", "CensysSRRiskEventsCheckpoint")
+CHECKPOINT_PARTITION_KEY = "CensysSRRiskEvents"
+SUPPLIERS_TABLE_NAME = os.environ.get("SuppliersTableName", "CensysSRSuppliers")
+RISKS_TABLE_NAME = os.environ.get("RisksTableName", "CensysSRRisks")
+
+DATA_COLLECTION_ENDPOINT = os.environ.get("AZURE_DATA_COLLECTION_ENDPOINT")
+DATA_COLLECTION_RULE_ID = os.environ.get("AZURE_DATA_COLLECTION_RULE_ID")
+STREAM_NAME = os.environ.get("StreamName", "Custom-CensysRiskEvents_CL")
+SCOPE = os.environ.get("SCOPE", "https://monitor.azure.com/.default")
+
+# Top-level event fields declared in the data collection rule stream
+EVENT_FIELDS = ("id", "inserted_at", "occurred_at", "change_type", "risk_instance")
+LOG_PREFIX = "CensysSR"
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/exceptions.py b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/exceptions.py
new file mode 100644
index 00000000000..6df0bf21010
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/exceptions.py
@@ -0,0 +1,17 @@
+"""Exceptions raised by the Censys risk events connector."""
+
+
+class CensysException(Exception):
+ """Generic connector error."""
+
+
+class CensysAuthError(CensysException):
+ """The Censys API rejected the credentials or the organization."""
+
+
+class CensysCursorExpired(CensysException):
+ """The checkpoint is older than the Censys event retention window (HTTP 410)."""
+
+
+class CensysTimeBudgetReached(CensysException):
+ """The run used its time budget. Everything ingested so far is already checkpointed."""
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/logger.py b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/logger.py
new file mode 100644
index 00000000000..c96fe151acc
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/logger.py
@@ -0,0 +1,15 @@
+"""Logger of the connector. The log level comes from the LogLevel setting."""
+import logging
+import os
+import sys
+
+log_level = (os.environ.get("LogLevel") or "INFO").upper()
+
+applogger = logging.getLogger("azure")
+if log_level in ("DEBUG", "INFO", "WARNING", "ERROR"):
+ applogger.setLevel(getattr(logging, log_level))
+else:
+ applogger.setLevel(logging.INFO)
+if not any(isinstance(h, logging.StreamHandler) and getattr(h, "stream", None) is sys.stdout
+ for h in applogger.handlers):
+ applogger.addHandler(logging.StreamHandler(stream=sys.stdout))
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/sentinel.py b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/sentinel.py
new file mode 100644
index 00000000000..b82c70a186d
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/sentinel.py
@@ -0,0 +1,44 @@
+"""Send events to Microsoft Sentinel through the Logs Ingestion API and the data collection rule."""
+from azure.core.exceptions import ClientAuthenticationError, HttpResponseError
+from azure.identity import AzureAuthorityHosts, DefaultAzureCredential
+from azure.monitor.ingestion import LogsIngestionClient
+
+from . import consts
+from .exceptions import CensysException
+from .logger import applogger
+
+
+class SentinelIngestor:
+ """Uploads risk events to the data collection rule stream."""
+
+ def __init__(self, endpoint: str, rule_id: str, stream_name: str, scope: str):
+ """Create the ingestion client.
+
+ DefaultAzureCredential uses the Microsoft Entra app set in the AZURE_CLIENT_ID, AZURE_CLIENT_SECRET and
+ AZURE_TENANT_ID settings.
+ """
+ self.rule_id = rule_id
+ self.stream_name = stream_name
+ if ".us" in scope:
+ credential = DefaultAzureCredential(authority=AzureAuthorityHosts.AZURE_GOVERNMENT)
+ else:
+ credential = DefaultAzureCredential()
+ self.client = LogsIngestionClient(endpoint, credential=credential, credential_scopes=[scope])
+
+ def upload(self, events: list) -> None:
+ """Upload one page of events.
+
+ Raises an error when any part of the upload fails, so the checkpoint does not move.
+ """
+ logs = [{field: event.get(field) for field in consts.EVENT_FIELDS} for event in events]
+ try:
+ self.client.upload(rule_id=self.rule_id, stream_name=self.stream_name, logs=logs)
+ except ClientAuthenticationError as err:
+ applogger.error(f"{consts.LOG_PREFIX} upload: authentication error while uploading to Sentinel: {err}")
+ raise CensysException(f"Authentication error while uploading to Sentinel: {err}")
+ except HttpResponseError as err:
+ applogger.error(f"{consts.LOG_PREFIX} upload: HTTP error while uploading to Sentinel: {err}")
+ raise CensysException(f"HTTP error while uploading to Sentinel: {err}")
+ except Exception as err:
+ applogger.error(f"{consts.LOG_PREFIX} upload: unexpected error while uploading to Sentinel: {err}")
+ raise CensysException(f"Unexpected error while uploading to Sentinel: {err}")
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/suppliers.py b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/suppliers.py
new file mode 100644
index 00000000000..950d87b9d94
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/suppliers.py
@@ -0,0 +1,113 @@
+"""Supplier and risk names with their ids, kept in Azure Storage Tables so that names can be used as an input."""
+from typing import Dict, Iterable, List, Tuple
+
+from azure.core.exceptions import ResourceNotFoundError
+from azure.data.tables import TableServiceClient, UpdateMode
+
+from . import consts
+from .exceptions import CensysException
+from .logger import applogger
+
+_BATCH = 100 # entities per Table transaction, all in the same partition
+
+
+def normalize(name: str) -> str:
+ """Compare names without caring about case or extra spaces. Used at run time only, nothing normalized is stored."""
+ return " ".join((name or "").split()).casefold()
+
+
+def parse_names(raw: str, placeholder: str = "suppliernames") -> List[str]:
+ """Split a names setting (SupplierNames or RiskNames) on the separator, trim, drop empty entries and duplicates."""
+ names, seen = [], set()
+ for part in (raw or "").replace("\n", consts.SUPPLIER_NAME_SEPARATOR).split(consts.SUPPLIER_NAME_SEPARATOR):
+ name = " ".join(part.split())
+ key = normalize(name)
+ if key and key != placeholder and key not in seen:
+ seen.add(key)
+ names.append(name)
+ return names
+
+
+def resolve(names: Iterable[str], suppliers: Iterable[Tuple[str, str]]) -> Tuple[List[str], List[str]]:
+ """Return (ids, names that matched nothing). suppliers is a list of (id, display name), suppliers or risks.
+
+ A name can match several entries, and all their ids are returned. The ids keep the order of the names.
+ """
+ by_name: Dict[str, List[str]] = {}
+ for supplier_id, display_name in suppliers:
+ by_name.setdefault(normalize(display_name), []).append(supplier_id)
+ ids, missing = [], []
+ for name in names:
+ found = by_name.get(normalize(name))
+ if not found:
+ missing.append(name)
+ continue
+ for supplier_id in sorted(found):
+ if supplier_id not in ids:
+ ids.append(supplier_id)
+ return ids, missing
+
+
+class SupplierStore:
+ """Table with one row per supplier: partition = organization id, row = supplier id, DisplayName as returned.
+
+ RiskStore uses the same layout for risks, whose name field is called name.
+ """
+
+ name_field = "display_name"
+
+ def __init__(self, connection_string: str, table_name: str, organization_id: str):
+ """Create the table client, creating the table when needed."""
+ self.organization_id = organization_id
+ try:
+ service = TableServiceClient.from_connection_string(conn_str=connection_string)
+ self.table = service.create_table_if_not_exists(table_name)
+ except Exception as err:
+ applogger.error(f"{consts.LOG_PREFIX} SupplierStore.__init__: error accessing suppliers table "
+ f"{table_name}: {err}")
+ raise CensysException(f"Error accessing suppliers table {table_name}: {err}")
+
+ def load(self) -> List[Tuple[str, str]]:
+ """Return (supplier id, display name) of every stored supplier of the organization."""
+ try:
+ rows = self.table.query_entities(f"PartitionKey eq '{self.organization_id}'")
+ return [(row["RowKey"], row.get("DisplayName") or "") for row in rows]
+ except ResourceNotFoundError:
+ return []
+ except Exception as err:
+ applogger.error(f"{consts.LOG_PREFIX} SupplierStore.load: error reading suppliers table: {err}")
+ raise CensysException(f"Error reading suppliers table: {err}")
+
+ def sync(self, pages: Iterable[List[dict]]) -> Tuple[int, int]:
+ """Store every supplier of the pages, then remove the rows of suppliers that no longer exist in Censys.
+
+ Returns (suppliers stored, rows removed). Rows are removed only after all pages were read and written,
+ so a failed run never removes anything.
+ """
+ latest: Dict[str, str] = {}
+ for page in pages:
+ for supplier in page:
+ if supplier.get("id"):
+ latest[supplier["id"]] = supplier.get(self.name_field) or ""
+ try:
+ self._write([("upsert", {"PartitionKey": self.organization_id, "RowKey": sid, "DisplayName": name},
+ {"mode": UpdateMode.REPLACE}) for sid, name in latest.items()])
+ stale = [sid for sid, _ in self.load() if sid not in latest]
+ self._write([("delete", {"PartitionKey": self.organization_id, "RowKey": sid}) for sid in stale])
+ except CensysException:
+ raise
+ except Exception as err:
+ applogger.error(f"{consts.LOG_PREFIX} SupplierStore.sync: error writing suppliers table: {err}")
+ raise CensysException(f"Error writing suppliers table: {err}")
+ return len(latest), len(stale)
+
+ def _write(self, operations: list) -> None:
+ """Send the operations to the table in transactions of at most 100 entities."""
+ for start in range(0, len(operations), _BATCH):
+ self.table.submit_transaction(operations[start:start + _BATCH])
+
+
+class RiskStore(SupplierStore):
+ """Table with one row per risk: partition = organization id, row = risk id, DisplayName = risk name."""
+
+ name_field = "name"
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/validation.py b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/validation.py
new file mode 100644
index 00000000000..06547f71976
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/SharedCode/validation.py
@@ -0,0 +1,186 @@
+"""Validation of the Function App settings.
+
+Every problem is logged with the name of the setting, then the run stops with one error.
+"""
+import datetime
+import re
+from dataclasses import dataclass, field
+from typing import Dict, List, Optional, Tuple
+
+from . import consts
+from .exceptions import CensysException
+from .logger import applogger
+from .suppliers import parse_names
+
+_UUID = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
+_RFC3339 = re.compile(r"^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?([Zz]|[+-]\d{2}:\d{2})$")
+_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
+_START_TIME_EXAMPLE = "2026-10-01T00:00:00Z"
+
+
+@dataclass
+class Settings:
+ """The validated and cleaned settings of the main function."""
+
+ api_token: str
+ organization_id: str
+ storage_connection: str
+ data_collection_endpoint: str
+ data_collection_rule_id: str
+ start_time: str
+ supplier_names: List[str] = field(default_factory=list)
+ risk_names: List[str] = field(default_factory=list)
+ change_types: List[str] = field(default_factory=list)
+ severity: List[str] = field(default_factory=list)
+ rapid_response: Optional[str] = None
+
+ def filters(self, supplier_ids: Optional[List[str]] = None,
+ risk_type_ids: Optional[List[str]] = None) -> Dict[str, str]:
+ """Return the query parameters of the filters. A filter without a value is never included."""
+ filters: Dict[str, str] = {}
+ for name, values in (("supplier_ids", supplier_ids), ("risk_type_ids", risk_type_ids),
+ ("change_types", self.change_types), ("severity", self.severity)):
+ if values:
+ filters[name] = ",".join(values)
+ if self.rapid_response:
+ filters["rapid_response"] = self.rapid_response
+ return filters
+
+
+class _Problems:
+ """The settings that failed the validation. Each problem is logged when it is added."""
+
+ def __init__(self):
+ """Start with no problems."""
+ self.settings: List[str] = []
+
+ def add(self, setting: str, message: str) -> None:
+ """Log the problem with the name of the setting and remember the setting."""
+ applogger.error(f"{consts.LOG_PREFIX} validate_settings: setting '{setting}' {message}")
+ self.settings.append(setting)
+
+ def raise_if_any(self) -> None:
+ """Stop the run with one error that lists all the settings with a problem."""
+ if self.settings:
+ raise CensysException(f"Invalid settings: {', '.join(self.settings)}. "
+ "See the previous error logs for the details.")
+
+
+def _value(raw: Optional[str], name: str) -> str:
+ """Trimmed value. The name of the setting itself (a placeholder left in by mistake) counts as empty."""
+ value = (raw or "").strip()
+ return "" if value.lower() == name.lower() else value
+
+
+def _items(raw: str, separator: str = ",") -> List[str]:
+ """Split a list setting on the separator, trim each item, and drop empty items and duplicates."""
+ items = []
+ for item in raw.replace("\n", separator).split(separator):
+ item = item.strip()
+ if item and item not in items:
+ items.append(item)
+ return items
+
+
+def parse_start_time(raw: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
+ """Return (start time in UTC as yyyy-MM-ddTHH:mm:ssZ, error). Empty means all events Censys still retains."""
+ value = _value(raw, "InitialStartTime")
+ if not value:
+ return consts.DEFAULT_START_TIME, None
+ match = _RFC3339.match(value)
+ if not match:
+ return None, (f"must be a UTC time with date and time, for example {_START_TIME_EXAMPLE}, "
+ f"but is '{value}'.")
+ year, month, day, hour, minute, second = (int(match.group(i)) for i in range(1, 7))
+ micro = int((match.group(7) or "0")[:6].ljust(6, "0"))
+ zone = match.group(8)
+ try:
+ offset = datetime.timedelta(0)
+ if zone not in ("Z", "z"):
+ hours, minutes = int(zone[1:3]), int(zone[4:6])
+ offset = datetime.timedelta(hours=hours, minutes=minutes) * (1 if zone[0] == "+" else -1)
+ moment = datetime.datetime(year, month, day, hour, minute, second, micro, tzinfo=datetime.timezone(offset))
+ except ValueError:
+ return None, f"is not a real date and time: '{value}'. Use the form {_START_TIME_EXAMPLE}."
+ moment = moment.astimezone(datetime.timezone.utc)
+ if moment > datetime.datetime.now(datetime.timezone.utc):
+ return None, f"must not be in the future: '{value}'."
+ return moment.strftime(_TIME_FORMAT), None
+
+
+def _choice_list(raw: str, name: str, allowed: Tuple[str, ...], upper: bool, problems: _Problems) -> List[str]:
+ """Return the values of a list setting that only accepts the allowed values, and report the other values."""
+ values = []
+ for item in _items(_value(raw, name)):
+ item = item.upper() if upper else item.lower()
+ if item not in values:
+ values.append(item)
+ bad = [v for v in values if v not in allowed]
+ if bad:
+ problems.add(name, f"has unsupported values {bad}. Allowed values: {', '.join(allowed)}.")
+ return values
+
+
+def load_common(problems: Optional[_Problems] = None) -> Tuple[str, str, str]:
+ """Validate and return (API token, organization id, storage connection string), used by both functions."""
+ own = problems is None
+ problems = problems or _Problems()
+ token = (consts.API_TOKEN or "").strip()
+ org = (consts.ORGANIZATION_ID or "").strip()
+ storage = (consts.CONN_STRING or "").strip()
+ if not token:
+ problems.add("CensysApiToken", "is not set. Enter your Censys personal access token.")
+ if not org:
+ problems.add("OrganizationId", "is not set. Enter the UUID of your Censys organization.")
+ elif not _UUID.match(org):
+ problems.add("OrganizationId",
+ f"must be a UUID, for example 00000000-0000-0000-0000-000000000000, but is '{org}'.")
+ if not storage:
+ problems.add("AzureWebJobsStorage", "is not set.")
+ if own:
+ problems.raise_if_any()
+ return token, org, storage
+
+
+def load_settings() -> Settings:
+ """Validate every setting of the main function and return the cleaned values.
+
+ Raises one CensysException that lists all the settings with a problem.
+ """
+ problems = _Problems()
+ token, org, storage = load_common(problems)
+ endpoint = (consts.DATA_COLLECTION_ENDPOINT or "").strip()
+ rule = (consts.DATA_COLLECTION_RULE_ID or "").strip()
+ if not endpoint:
+ problems.add("AZURE_DATA_COLLECTION_ENDPOINT", "is not set.")
+ if not rule:
+ problems.add("AZURE_DATA_COLLECTION_RULE_ID", "is not set.")
+
+ start_time, error = parse_start_time(consts.INITIAL_START_TIME)
+ if error:
+ problems.add("InitialStartTime", error)
+
+ change_types = _choice_list(consts.CHANGE_TYPES, "ChangeTypes", consts.ALLOWED_CHANGE_TYPES, False, problems)
+ severity = _choice_list(consts.SEVERITY, "Severity", consts.ALLOWED_SEVERITIES, True, problems)
+ rapid = _value(consts.RAPID_RESPONSE, "RapidResponse").lower()
+ if rapid and rapid not in ("true", "false"):
+ problems.add("RapidResponse", f"must be true or false, but is '{rapid}'. Leave it empty for both.")
+ rapid = ""
+
+ raw_risks = (consts.RISK_NAMES or "").strip()
+ if len(raw_risks) > consts.MAX_RISK_NAMES_LENGTH:
+ problems.add("RiskNames",
+ f"has {len(raw_risks)} characters, at most {consts.MAX_RISK_NAMES_LENGTH} are allowed.")
+ risk_names = parse_names(raw_risks, "risknames")
+
+ raw_names = (consts.SUPPLIER_NAMES or "").strip()
+ if len(raw_names) > consts.MAX_SUPPLIER_NAMES_LENGTH:
+ problems.add("SupplierNames",
+ f"has {len(raw_names)} characters, at most {consts.MAX_SUPPLIER_NAMES_LENGTH} are allowed.")
+ supplier_names = parse_names(raw_names)
+
+ problems.raise_if_any()
+ return Settings(api_token=token, organization_id=org, storage_connection=storage,
+ data_collection_endpoint=endpoint, data_collection_rule_id=rule, start_time=start_time,
+ supplier_names=supplier_names, risk_names=risk_names, change_types=change_types,
+ severity=severity, rapid_response=rapid or None)
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/azuredeploy_CensysSR_RiskEvents_FunctionApp.json b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/azuredeploy_CensysSR_RiskEvents_FunctionApp.json
new file mode 100644
index 00000000000..540e8eb2b23
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/azuredeploy_CensysSR_RiskEvents_FunctionApp.json
@@ -0,0 +1,650 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "Censys SR Function Name": {
+ "type": "string",
+ "defaultValue": "CensysSR",
+ "metadata": {
+ "description": "Name prefix of the Function App to create (up to 10 characters, letters and digits)."
+ },
+ "minLength": 1,
+ "maxLength": 10
+ },
+ "Censys Api Key": {
+ "type": "securestring",
+ "metadata": {
+ "description": "Censys personal access token. Starter, Search and Core plans need the API Access role."
+ }
+ },
+ "Censys Organization Id": {
+ "type": "string",
+ "minLength": 36,
+ "maxLength": 36,
+ "metadata": {
+ "description": "UUID of the Censys organization to collect risk events for, for example 00000000-0000-0000-0000-000000000000."
+ }
+ },
+ "Censys Start Date Time": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. UTC time to start collecting from, in the form 2026-10-01T00:00:00Z. It must not be in the future. Leave empty for full history (all events Censys still retains)."
+ }
+ },
+ "Censys Supplier Names": {
+ "type": "string",
+ "defaultValue": "",
+ "maxLength": 500,
+ "metadata": {
+ "description": "Optional. Names of the suppliers to collect risk events for, separated by an asterisk (*). For example: OpenAI*Harvey*Acme, Inc. Names are not case-sensitive and can be picked from the Censys Supplier Risk UI. Names that are not found among your suppliers are skipped with a warning. Leave empty to collect all suppliers. A change applies from the next run and continues from the current position, so avoid changing it often. Maximum 500 characters."
+ }
+ },
+ "Censys Risk Names": {
+ "type": "string",
+ "defaultValue": "",
+ "maxLength": 500,
+ "metadata": {
+ "description": "Optional. Names of the risks to collect risk events for, separated by an asterisk (*). For example: Log4Shell*Exposed Redis. Names are not case-sensitive and must match the risk name shown in the Censys Supplier Risk UI. Names that are not found among the risks of your supply chain are skipped with a warning. Leave empty to collect all risks. A change applies from the next run and continues from the current position, so avoid changing it often. Maximum 500 characters."
+ }
+ },
+ "Censys Change Types": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. Comma-separated change types: opened, resolved, reopened. Other values stop the run with an error. Leave empty for all."
+ }
+ },
+ "Censys Severity": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. Comma-separated severities: CRITICAL, HIGH, MEDIUM, LOW. Other values stop the run with an error. Leave empty for all."
+ }
+ },
+ "Censys Rapid Response": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional. true for rapid response findings only, false to exclude them. Leave empty for both."
+ },
+ "allowedValues": [
+ "",
+ "true",
+ "false"
+ ]
+ },
+ "Azure_Client_Id": {
+ "type": "string",
+ "metadata": {
+ "description": "Enter the Azure client ID of the Microsoft Entra app that ingests the data. It is the app you created for this connector."
+ }
+ },
+ "Azure_Client_Secret": {
+ "type": "securestring",
+ "metadata": {
+ "description": "Enter the client secret that you created for that Microsoft Entra app."
+ }
+ },
+ "Azure_Tenant_Id": {
+ "type": "string",
+ "metadata": {
+ "description": "Enter the Azure tenant ID of your Microsoft Entra ID."
+ }
+ },
+ "Azure_Entra_Object_Id": {
+ "type": "string",
+ "metadata": {
+ "description": "Enter the object ID of your Microsoft Entra app (Enterprise application). It gets the Monitoring Metrics Publisher role on the data collection rule."
+ }
+ },
+ "WorkspaceName": {
+ "type": "string",
+ "metadata": {
+ "description": "Name of the Log Analytics workspace of Microsoft Sentinel. Deploy this template in the resource group of that workspace. The template creates the table CensysRiskEvents_CL, a data collection endpoint and a data collection rule in it."
+ }
+ },
+ "Location": {
+ "type": "string",
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Region of the data collection endpoint and rule. It must be the region of the workspace."
+ }
+ },
+ "Censys Risk Events Table Name": {
+ "type": "string",
+ "defaultValue": "CensysRiskEvents_CL",
+ "minLength": 4,
+ "maxLength": 52,
+ "metadata": {
+ "description": "Name of the table in the workspace that the connector writes the risk events to. The suffix _CL is added, or corrected, when it is missing or in another case. Maximum 52 characters. Warning: the default is CensysRiskEvents_CL, and the parser CensysRiskEvents, the workbook, the analytic rule and the connector status check of this solution read that table. If you use another name, update these resources to read the new table, otherwise they show no data."
+ }
+ },
+ "Censys Risk Events Collection Schedule": {
+ "type": "string",
+ "defaultValue": "0 */10 * * * *",
+ "metadata": {
+ "description": "Quartz (NCRONTAB) cron expression for how often the function runs, for example 0 */10 * * * *. Warning: keep the frequency at least 10 minutes for a better experience. Default is every 10 minutes."
+ }
+ },
+ "Censys Supplier and Risk List Refresh Schedule": {
+ "type": "string",
+ "defaultValue": "0 */30 * * * *",
+ "metadata": {
+ "description": "Quartz (NCRONTAB) cron expression for how often the function that refreshes the lists of supplier names and risk names with their ids runs, for example 0 */30 * * * *. It also runs once when the app starts. Warning: keep the frequency at least 10 minutes for a better experience. Default is every 30 minutes."
+ }
+ },
+ "AppInsightsWorkspaceResourceID": {
+ "type": "string",
+ "metadata": {
+ "description": "Resource ID of the Log Analytics workspace used by Application Insights. Use 'Log Analytics Workspace, Properties' blade, Resource ID."
+ }
+ },
+ "Censys SR Function Log Level": {
+ "type": "string",
+ "defaultValue": "INFO",
+ "metadata": {
+ "description": "Log level of the functions: DEBUG, INFO or ERROR."
+ },
+ "allowedValues": [
+ "DEBUG",
+ "INFO",
+ "ERROR"
+ ]
+ }
+ },
+ "variables": {
+ "FunctionName": "[concat(toLower(parameters('Censys SR Function Name')), uniqueString(resourceGroup().id))]",
+ "deploymentStorageContainerName": "[concat('app-package-', toLower(parameters('Censys SR Function Name')))]",
+ "storageBlobDataOwnerRoleId": "b7e6dc6d-f1e8-4753-8033-0f276bb0955b",
+ "monitoringMetricsPublisherRoleId": "3913510d-42f4-4e42-8a64-420c390055eb",
+ "scope": "[concat(replace(environment().portal, 'portal', 'monitor'), '/.default')]",
+ "suffix": "[uniqueString(resourceGroup().id)]",
+ "dceName": "[concat('CensysSRRiskEventsDCE', variables('suffix'))]",
+ "dcrName": "[concat('CensysSRRiskEventsDCR', variables('suffix'))]",
+ "dcrId": "[resourceId('Microsoft.Insights/dataCollectionRules', variables('dcrName'))]",
+ "dceId": "[resourceId('Microsoft.Insights/dataCollectionEndpoints', variables('dceName'))]",
+ "tableName": "[if(endsWith(toLower(parameters('Censys Risk Events Table Name')), '_cl'), concat(substring(parameters('Censys Risk Events Table Name'), 0, sub(length(parameters('Censys Risk Events Table Name')), 3)), '_CL'), concat(parameters('Censys Risk Events Table Name'), '_CL'))]",
+ "streamName": "[concat('Custom-', variables('tableName'))]",
+ "workspaceResId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('WorkspaceName'))]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/tables",
+ "apiVersion": "2022-10-01",
+ "name": "[concat(parameters('WorkspaceName'), '/', variables('tableName'))]",
+ "properties": {
+ "plan": "Analytics",
+ "schema": {
+ "name": "[variables('tableName')]",
+ "columns": [
+ {
+ "name": "TimeGenerated",
+ "type": "datetime"
+ },
+ {
+ "name": "EventId",
+ "type": "string"
+ },
+ {
+ "name": "InsertedAt",
+ "type": "datetime"
+ },
+ {
+ "name": "OccurredAt",
+ "type": "datetime"
+ },
+ {
+ "name": "ChangeType",
+ "type": "string"
+ },
+ {
+ "name": "RiskInstanceId",
+ "type": "string"
+ },
+ {
+ "name": "SupplyChainId",
+ "type": "string"
+ },
+ {
+ "name": "AssetType",
+ "type": "string"
+ },
+ {
+ "name": "RiskTypeId",
+ "type": "string"
+ },
+ {
+ "name": "CveId",
+ "type": "string"
+ },
+ {
+ "name": "RiskCategory",
+ "type": "string"
+ },
+ {
+ "name": "RiskName",
+ "type": "string"
+ },
+ {
+ "name": "Description",
+ "type": "string"
+ },
+ {
+ "name": "Remediations",
+ "type": "dynamic"
+ },
+ {
+ "name": "Severity",
+ "type": "string"
+ },
+ {
+ "name": "Score",
+ "type": "real"
+ },
+ {
+ "name": "Confidence",
+ "type": "real"
+ },
+ {
+ "name": "Status",
+ "type": "string"
+ },
+ {
+ "name": "ResolveTime",
+ "type": "datetime"
+ },
+ {
+ "name": "ResolveReason",
+ "type": "string"
+ },
+ {
+ "name": "SupplierId",
+ "type": "string"
+ },
+ {
+ "name": "SupplierDisplayName",
+ "type": "string"
+ },
+ {
+ "name": "Suppliers",
+ "type": "dynamic"
+ },
+ {
+ "name": "AssetIp",
+ "type": "string"
+ },
+ {
+ "name": "AssetHostname",
+ "type": "string"
+ },
+ {
+ "name": "AssetPort",
+ "type": "int"
+ },
+ {
+ "name": "ServiceTransportProtocol",
+ "type": "string"
+ },
+ {
+ "name": "ServicePort",
+ "type": "int"
+ },
+ {
+ "name": "ServiceProtocol",
+ "type": "string"
+ },
+ {
+ "name": "Tags",
+ "type": "dynamic"
+ },
+ {
+ "name": "RapidResponse",
+ "type": "bool"
+ },
+ {
+ "name": "Evidence",
+ "type": "string"
+ },
+ {
+ "name": "FirstDetectedAt",
+ "type": "datetime"
+ },
+ {
+ "name": "LastDetectedAt",
+ "type": "datetime"
+ },
+ {
+ "name": "CreatedAt",
+ "type": "datetime"
+ },
+ {
+ "name": "PlatformUrl",
+ "type": "string"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Insights/dataCollectionEndpoints",
+ "apiVersion": "2022-06-01",
+ "name": "[variables('dceName')]",
+ "location": "[parameters('Location')]",
+ "kind": "Linux",
+ "properties": {
+ "description": "Data collection endpoint of the Censys SR function connector",
+ "networkAcls": {
+ "publicNetworkAccess": "Enabled"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Insights/dataCollectionRules",
+ "apiVersion": "2023-03-11",
+ "name": "[variables('dcrName')]",
+ "location": "[parameters('Location')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.OperationalInsights/workspaces/tables', parameters('WorkspaceName'), variables('tableName'))]",
+ "[variables('dceId')]"
+ ],
+ "properties": {
+ "dataCollectionEndpointId": "[variables('dceId')]",
+ "streamDeclarations": {
+ "[variables('streamName')]": {
+ "columns": [
+ {
+ "name": "id",
+ "type": "string"
+ },
+ {
+ "name": "inserted_at",
+ "type": "datetime"
+ },
+ {
+ "name": "occurred_at",
+ "type": "datetime"
+ },
+ {
+ "name": "change_type",
+ "type": "string"
+ },
+ {
+ "name": "risk_instance",
+ "type": "dynamic"
+ }
+ ]
+ }
+ },
+ "destinations": {
+ "logAnalytics": [
+ {
+ "workspaceResourceId": "[variables('workspaceResId')]",
+ "name": "clv2ws1"
+ }
+ ]
+ },
+ "dataFlows": [
+ {
+ "streams": [
+ "[variables('streamName')]"
+ ],
+ "destinations": [
+ "clv2ws1"
+ ],
+ "transformKql": "source | extend ri = todynamic(risk_instance) | extend TimeGenerated = todatetime(occurred_at), EventId = tostring(id), InsertedAt = todatetime(inserted_at), OccurredAt = todatetime(occurred_at), ChangeType = tostring(change_type), RiskInstanceId = tostring(ri.id), SupplyChainId = tostring(ri.supply_chain_id), AssetType = tostring(ri.asset_type), RiskTypeId = tostring(ri.risk_type_id), CveId = iff(tostring(ri.risk_type_id) matches regex @'^CVE-[0-9]{4}-[0-9]+$', tostring(ri.risk_type_id), ''), RiskCategory = tostring(ri.risk_category), RiskName = tostring(ri.risk_name), Description = tostring(ri.description), Remediations = todynamic(ri.remediations), Severity = tostring(ri.severity), Score = toreal(ri.score), Confidence = toreal(ri.confidence), Status = tostring(ri.status), ResolveTime = todatetime(ri.resolve_time), ResolveReason = tostring(ri.resolve_reason), SupplierId = tostring(ri.suppliers[0].id), SupplierDisplayName = tostring(ri.suppliers[0].display_name), Suppliers = todynamic(ri.suppliers), AssetIp = tostring(ri.host.ip), AssetHostname = tostring(ri.web.hostname), AssetPort = toint(ri.web.port), ServiceTransportProtocol = tostring(ri.service.transport_protocol), ServicePort = toint(ri.service.port), ServiceProtocol = tostring(ri.service.protocol), Tags = todynamic(ri.tags), RapidResponse = tobool(ri.rapid_response), Evidence = tostring(ri.evidence), FirstDetectedAt = todatetime(ri.first_detected_at), LastDetectedAt = todatetime(ri.last_detected_at), CreatedAt = todatetime(ri.created_at), PlatformUrl = tostring(ri.platform_url) | project TimeGenerated, EventId, InsertedAt, OccurredAt, ChangeType, RiskInstanceId, SupplyChainId, AssetType, RiskTypeId, CveId, RiskCategory, RiskName, Description, Remediations, Severity, Score, Confidence, Status, ResolveTime, ResolveReason, SupplierId, SupplierDisplayName, Suppliers, AssetIp, AssetHostname, AssetPort, ServiceTransportProtocol, ServicePort, ServiceProtocol, Tags, RapidResponse, Evidence, FirstDetectedAt, LastDetectedAt, CreatedAt, PlatformUrl",
+ "outputStream": "[variables('streamName')]"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.Insights/components",
+ "apiVersion": "2020-02-02",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "web",
+ "properties": {
+ "Application_Type": "web",
+ "ApplicationId": "[variables('FunctionName')]",
+ "WorkspaceResourceId": "[parameters('AppInsightsWorkspaceResourceID')]"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts",
+ "apiVersion": "2023-01-01",
+ "name": "[tolower(variables('FunctionName'))]",
+ "location": "[resourceGroup().location]",
+ "sku": {
+ "name": "Standard_LRS",
+ "tier": "Standard"
+ },
+ "kind": "StorageV2",
+ "properties": {
+ "networkAcls": {
+ "bypass": "AzureServices",
+ "virtualNetworkRules": [],
+ "ipRules": [],
+ "defaultAction": "Allow"
+ },
+ "supportsHttpsTrafficOnly": true,
+ "minimumTlsVersion": "TLS1_2",
+ "encryption": {
+ "services": {
+ "file": {
+ "keyType": "Account",
+ "enabled": true
+ },
+ "blob": {
+ "keyType": "Account",
+ "enabled": true
+ }
+ },
+ "keySource": "Microsoft.Storage"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices",
+ "apiVersion": "2023-01-01",
+ "name": "[concat(tolower(variables('FunctionName')), '/default')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]"
+ ],
+ "properties": {
+ "cors": {
+ "corsRules": []
+ },
+ "deleteRetentionPolicy": {
+ "enabled": false
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2023-01-01",
+ "name": "[concat(tolower(variables('FunctionName')), '/default/', variables('deploymentStorageContainerName'))]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', tolower(variables('FunctionName')), 'default')]"
+ ],
+ "properties": {
+ "publicAccess": "None"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2023-01-01",
+ "name": "[concat(tolower(variables('FunctionName')), '/default/', 'azure-webjobs-hosts')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', tolower(variables('FunctionName')), 'default')]"
+ ],
+ "properties": {
+ "publicAccess": "None"
+ }
+ },
+ {
+ "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
+ "apiVersion": "2023-01-01",
+ "name": "[concat(tolower(variables('FunctionName')), '/default/', 'azure-webjobs-secrets')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices', tolower(variables('FunctionName')), 'default')]"
+ ],
+ "properties": {
+ "publicAccess": "None"
+ }
+ },
+ {
+ "type": "Microsoft.Web/serverfarms",
+ "apiVersion": "2023-12-01",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "functionapp",
+ "sku": {
+ "tier": "FlexConsumption",
+ "name": "FC1"
+ },
+ "properties": {
+ "reserved": true
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2023-12-01",
+ "name": "[variables('FunctionName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "functionapp,linux",
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/serverfarms', variables('FunctionName'))]",
+ "[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]",
+ "[resourceId('Microsoft.Storage/storageAccounts/blobServices/containers', tolower(variables('FunctionName')), 'default', variables('deploymentStorageContainerName'))]",
+ "[resourceId('Microsoft.Insights/components', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "name": "[variables('FunctionName')]",
+ "httpsOnly": true,
+ "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('FunctionName'))]",
+ "functionAppConfig": {
+ "deployment": {
+ "storage": {
+ "type": "blobContainer",
+ "value": "[concat(reference(resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))).primaryEndpoints.blob, variables('deploymentStorageContainerName'))]",
+ "authentication": {
+ "type": "SystemAssignedIdentity"
+ }
+ }
+ },
+ "scaleAndConcurrency": {
+ "maximumInstanceCount": 40,
+ "instanceMemoryMB": 2048
+ },
+ "runtime": {
+ "name": "python",
+ "version": "3.14"
+ }
+ }
+ },
+ "resources": [
+ {
+ "apiVersion": "2023-12-01",
+ "type": "config",
+ "name": "appsettings",
+ "dependsOn": [
+ "[concat('Microsoft.Web/sites/', variables('FunctionName'))]",
+ "[variables('dcrId')]",
+ "[variables('dceId')]"
+ ],
+ "properties": {
+ "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.Insights/components', variables('FunctionName')), '2015-05-01').InstrumentationKey]",
+ "APPLICATIONINSIGHTS_CONNECTION_STRING": "[reference(resourceId('Microsoft.Insights/components', variables('FunctionName')), '2015-05-01').ConnectionString]",
+ "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', tolower(variables('FunctionName')), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName'))), '2023-01-01').keys[0].value, ';EndpointSuffix=', environment().suffixes.storage)]",
+ "CensysApiToken": "[parameters('Censys Api Key')]",
+ "OrganizationId": "[parameters('Censys Organization Id')]",
+ "InitialStartTime": "[parameters('Censys Start Date Time')]",
+ "SupplierNames": "[parameters('Censys Supplier Names')]",
+ "RiskNames": "[parameters('Censys Risk Names')]",
+ "ChangeTypes": "[parameters('Censys Change Types')]",
+ "Severity": "[parameters('Censys Severity')]",
+ "RapidResponse": "[parameters('Censys Rapid Response')]",
+ "Schedule": "[parameters('Censys Risk Events Collection Schedule')]",
+ "SuppliersAndRisksSchedule": "[parameters('Censys Supplier and Risk List Refresh Schedule')]",
+ "LogLevel": "[parameters('Censys SR Function Log Level')]",
+ "StreamName": "[variables('streamName')]",
+ "SCOPE": "[variables('scope')]",
+ "AZURE_DATA_COLLECTION_ENDPOINT": "[reference(variables('dceId'), '2022-06-01').logsIngestion.endpoint]",
+ "AZURE_DATA_COLLECTION_RULE_ID": "[reference(variables('dcrId'), '2023-03-11').immutableId]",
+ "AZURE_CLIENT_ID": "[parameters('Azure_Client_Id')]",
+ "AZURE_CLIENT_SECRET": "[parameters('Azure_Client_Secret')]",
+ "AZURE_TENANT_ID": "[parameters('Azure_Tenant_Id')]"
+ }
+ }
+ ]
+ },
+ {
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName'))), variables('storageBlobDataOwnerRoleId'), variables('FunctionName'))]",
+ "scope": "[concat('Microsoft.Storage/storageAccounts', '/', tolower(variables('FunctionName')))]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName')))]",
+ "[resourceId('Microsoft.Web/sites', variables('FunctionName'))]"
+ ],
+ "properties": {
+ "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('storageBlobDataOwnerRoleId'))]",
+ "principalType": "ServicePrincipal",
+ "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('FunctionName')), '2023-12-01', 'Full').identity.principalId]"
+ }
+ },
+ {
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2022-04-01",
+ "name": "[guid(variables('dcrId'), variables('monitoringMetricsPublisherRoleId'), parameters('Azure_Entra_Object_Id'))]",
+ "scope": "[variables('dcrId')]",
+ "dependsOn": [
+ "[variables('dcrId')]"
+ ],
+ "properties": {
+ "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('monitoringMetricsPublisherRoleId'))]",
+ "principalType": "ServicePrincipal",
+ "principalId": "[parameters('Azure_Entra_Object_Id')]"
+ }
+ },
+ {
+ "type": "Microsoft.Resources/deploymentScripts",
+ "apiVersion": "2020-10-01",
+ "name": "WaitSection",
+ "location": "[resourceGroup().location]",
+ "kind": "AzurePowerShell",
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/sites', variables('FunctionName'))]",
+ "[guid(resourceId('Microsoft.Storage/storageAccounts', tolower(variables('FunctionName'))), variables('storageBlobDataOwnerRoleId'), variables('FunctionName'))]"
+ ],
+ "properties": {
+ "azPowerShellVersion": "7.0",
+ "scriptContent": "start-sleep -Seconds 30",
+ "cleanupPreference": "Always",
+ "retentionInterval": "PT1H"
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites/extensions",
+ "apiVersion": "2022-09-01",
+ "name": "[format('{0}/{1}', variables('FunctionName'), 'onedeploy')]",
+ "dependsOn": [
+ "WaitSection"
+ ],
+ "properties": {
+ "packageUri": "https://aka.ms/sentinel-CensysSR100-functionapp",
+ "remoteBuild": true
+ }
+ }
+ ],
+ "outputs": {
+ "FunctionAppName": {
+ "type": "string",
+ "value": "[variables('FunctionName')]"
+ }
+ }
+}
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/host.json b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/host.json
new file mode 100644
index 00000000000..3b13d3477a1
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/host.json
@@ -0,0 +1,22 @@
+{
+ "version": "2.0",
+ "logging": {
+ "logLevel": {
+ "default": "Information",
+ "Host.Results": "Information",
+ "Function": "Information",
+ "Host.Aggregator": "Information"
+ },
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ }
+ }
+ },
+ "functionTimeout": "00:10:00",
+ "extensionBundle": {
+ "id": "Microsoft.Azure.Functions.ExtensionBundle",
+ "version": "[4.*, 5.0.0)"
+ }
+}
diff --git a/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/requirements.txt b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/requirements.txt
new file mode 100644
index 00000000000..24845b7a8a8
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data Connectors/CensysRiskEventsFunctionConnector/requirements.txt
@@ -0,0 +1,9 @@
+# DO NOT include azure-functions-worker in this file
+# The Python Worker is managed by Azure Functions platform
+# Manually managing azure-functions-worker may cause unexpected issues
+
+azure-functions
+requests
+azure-identity
+azure-monitor-ingestion
+azure-data-tables
diff --git a/Solutions/Censys Supplier Risk/Data/Solution_Censys.json b/Solutions/Censys Supplier Risk/Data/Solution_Censys.json
new file mode 100644
index 00000000000..8a3dfebe9dc
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Data/Solution_Censys.json
@@ -0,0 +1,24 @@
+{
+ "Name": "Censys Supplier Risk",
+ "Author": "Censys - support@censys.com",
+ "Logo": "
",
+ "Description": "Bring Censys Supplier Risk into Microsoft Sentinel. The solution collects supply chain risk events (risks opened, reopened and resolved for your suppliers) from the Censys platform, and provides a parser, a workbook and an analytic rule that raises incidents for new and reopened supplier risks and keeps resolved events with the incident of the same risk.",
+ "Data Connectors": [
+ "Data Connectors/CensysRiskEventsFunctionConnector/CensysSR_RiskEvents_FunctionApp.json"
+ ],
+ "Parsers": [
+ "Parsers/CensysRiskEvents.yaml",
+ "Parsers/CensysRisks.yaml"
+ ],
+ "Workbooks": [
+ "Workbooks/CensysSupplierRisk.json"
+ ],
+ "Analytic Rules": [
+ "Analytic Rules/CensysRiskStateChange.yaml"
+ ],
+ "BasePath": "C:\\GitHub\\Azure-Sentinel\\Solutions\\Censys Supplier Risk",
+ "Version": "3.0.0",
+ "Metadata": "SolutionMetadata.json",
+ "TemplateSpec": true,
+ "Is1PConnector": false
+}
diff --git a/Solutions/Censys Supplier Risk/Package/3.0.0.zip b/Solutions/Censys Supplier Risk/Package/3.0.0.zip
new file mode 100644
index 00000000000..663945f25ac
Binary files /dev/null and b/Solutions/Censys Supplier Risk/Package/3.0.0.zip differ
diff --git a/Solutions/Censys Supplier Risk/Package/createUiDefinition.json b/Solutions/Censys Supplier Risk/Package/createUiDefinition.json
new file mode 100644
index 00000000000..31144cf1a14
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Package/createUiDefinition.json
@@ -0,0 +1,176 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#",
+ "handler": "Microsoft.Azure.CreateUIDef",
+ "version": "0.1.2-preview",
+ "parameters": {
+ "config": {
+ "isWizard": false,
+ "basics": {
+ "description": "
\n\n**Note:** Please refer to the following before installing the solution: \n\n• Review the solution [Release Notes](https://github.com/Azure/Azure-Sentinel/tree/master/Solutions/Censys%20Supplier%20Risk/ReleaseNotes.md)\n\n • There may be [known issues](https://aka.ms/sentinelsolutionsknownissues) pertaining to this Solution, please refer to them before installing.\n\nBring Censys Supplier Risk into Microsoft Sentinel. The solution collects supply chain risk events (risks opened, reopened and resolved for your suppliers) from the Censys platform, and provides a parser, a workbook and an analytic rule that raises incidents for new and reopened supplier risks and keeps resolved events with the incident of the same risk.\n\n**Data Connectors:** 1, **Parsers:** 2, **Workbooks:** 1, **Analytic Rules:** 1\n\n[Learn more about Microsoft Sentinel](https://aka.ms/azuresentinel) | [Learn more about Solutions](https://aka.ms/azuresentinelsolutionsdoc)",
+ "subscription": {
+ "resourceProviders": [
+ "Microsoft.OperationsManagement/solutions",
+ "Microsoft.OperationalInsights/workspaces/providers/alertRules",
+ "Microsoft.Insights/workbooks",
+ "Microsoft.Logic/workflows"
+ ]
+ },
+ "location": {
+ "metadata": {
+ "hidden": "Hiding location, we get it from the log analytics workspace"
+ },
+ "visible": false
+ },
+ "resourceGroup": {
+ "allowExisting": true
+ }
+ }
+ },
+ "basics": [
+ {
+ "name": "getLAWorkspace",
+ "type": "Microsoft.Solutions.ArmApiControl",
+ "toolTip": "This filters by workspaces that exist in the Resource Group selected",
+ "condition": "[greater(length(resourceGroup().name),0)]",
+ "request": {
+ "method": "GET",
+ "path": "[concat(subscription().id,'/providers/Microsoft.OperationalInsights/workspaces?api-version=2020-08-01')]"
+ }
+ },
+ {
+ "name": "workspace",
+ "type": "Microsoft.Common.DropDown",
+ "label": "Workspace",
+ "placeholder": "Select a workspace",
+ "toolTip": "This dropdown will list only workspace that exists in the Resource Group selected",
+ "constraints": {
+ "allowedValues": "[map(filter(basics('getLAWorkspace').value, (filter) => contains(toLower(filter.id), toLower(resourceGroup().name))), (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]",
+ "required": true
+ },
+ "visible": true
+ }
+ ],
+ "steps": [
+ {
+ "name": "dataconnectors",
+ "label": "Data Connectors",
+ "bladeTitle": "Data Connectors",
+ "elements": [
+ {
+ "name": "dataconnectors1-text",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "text": "This Solution installs the data connector for Censys Supplier Risk. You can get Censys Supplier Risk custom log data in your Microsoft Sentinel workspace. After installing the solution, configure and enable this data connector by following guidance in Manage solution view."
+ }
+ },
+ {
+ "name": "dataconnectors-parser-text",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "text": "The Solution installs a parser that transforms the ingested data into Microsoft Sentinel normalized format. The normalized format enables better correlation of different types of data from different data sources to drive end-to-end outcomes seamlessly in security monitoring, hunting, incident investigation and response scenarios in Microsoft Sentinel."
+ }
+ },
+ {
+ "name": "dataconnectors-link1",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "link": {
+ "label": "Learn more about connecting data sources",
+ "uri": "https://docs.microsoft.com/azure/sentinel/connect-data-sources"
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "workbooks",
+ "label": "Workbooks",
+ "subLabel": {
+ "preValidation": "Configure the workbooks",
+ "postValidation": "Done"
+ },
+ "bladeTitle": "Workbooks",
+ "elements": [
+ {
+ "name": "workbooks-text",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "text": "This solution installs workbook(s) to help you gain insights into the telemetry collected in Microsoft Sentinel. After installing the solution, start using the workbook in Manage solution view."
+ }
+ },
+ {
+ "name": "workbooks-link",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "link": {
+ "label": "Learn more",
+ "uri": "https://docs.microsoft.com/azure/sentinel/tutorial-monitor-your-data"
+ }
+ }
+ },
+ {
+ "name": "workbook1",
+ "type": "Microsoft.Common.Section",
+ "label": "Censys Supplier Risk",
+ "elements": [
+ {
+ "name": "workbook1-text",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "text": "Gain insight into supplier risk collected from Censys Supplier Risk: open risks by severity, risk events by category, opened and closed risks over time, top risks by open risk count, supplier risk ranking and rapid response events, with details one selection away."
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "analytics",
+ "label": "Analytics",
+ "subLabel": {
+ "preValidation": "Configure the analytics",
+ "postValidation": "Done"
+ },
+ "bladeTitle": "Analytics",
+ "elements": [
+ {
+ "name": "analytics-text",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "text": "This solution installs the following analytic rule templates. After installing the solution, create and enable analytic rules in Manage solution view."
+ }
+ },
+ {
+ "name": "analytics-link",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "link": {
+ "label": "Learn more",
+ "uri": "https://docs.microsoft.com/azure/sentinel/tutorial-detect-threats-custom?WT.mc_id=Portal-Microsoft_Azure_CreateUIDef"
+ }
+ }
+ },
+ {
+ "name": "analytic1",
+ "type": "Microsoft.Common.Section",
+ "label": "Censys SR - Risk State Change",
+ "elements": [
+ {
+ "name": "analytic1-text",
+ "type": "Microsoft.Common.TextBlock",
+ "options": {
+ "text": "Creates one incident per supplier risk instance that is opened, or reopened, with CRITICAL or HIGH severity. Alerts are grouped by RiskInstanceId, so further events of the same risk instance within 24 hours are added to the same incident while it is open.\nAlert severity follows the Censys severity: CRITICAL and HIGH map to High, MEDIUM to Medium and LOW to Low. Resolved events follow the same mapping, and any other severity is Informational.\nBy default the rule alerts only on new CRITICAL and HIGH risks. The query holds commented lines to follow the whole life of a risk instead (reopened events of any severity and resolved events), see the comments in the query."
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "outputs": {
+ "workspace-location": "[first(map(filter(basics('getLAWorkspace').value, (filter) => and(contains(toLower(filter.id), toLower(resourceGroup().name)),equals(filter.name,basics('workspace')))), (item) => item.location))]",
+ "location": "[location()]",
+ "workspace": "[basics('workspace')]"
+ }
+ }
+}
diff --git a/Solutions/Censys Supplier Risk/Package/mainTemplate.json b/Solutions/Censys Supplier Risk/Package/mainTemplate.json
new file mode 100644
index 00000000000..f24fefa6115
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Package/mainTemplate.json
@@ -0,0 +1,1026 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "metadata": {
+ "author": "Censys - support@censys.com",
+ "comments": "Solution template for Censys Supplier Risk"
+ },
+ "parameters": {
+ "location": {
+ "type": "string",
+ "minLength": 1,
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace"
+ }
+ },
+ "workspace-location": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]"
+ }
+ },
+ "workspace": {
+ "defaultValue": "",
+ "type": "string",
+ "metadata": {
+ "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup"
+ }
+ },
+ "workbook1-name": {
+ "type": "string",
+ "defaultValue": "Censys Supplier Risk",
+ "minLength": 1,
+ "metadata": {
+ "description": "Name for the workbook"
+ }
+ }
+ },
+ "variables": {
+ "email": "support@censys.com",
+ "_email": "[variables('email')]",
+ "_solutionName": "Censys Supplier Risk",
+ "_solutionVersion": "3.0.0",
+ "solutionId": "censys.censys_supplier_risk",
+ "_solutionId": "[variables('solutionId')]",
+ "uiConfigId1": "CensysSRRiskEventsFunction",
+ "_uiConfigId1": "[variables('uiConfigId1')]",
+ "dataConnectorContentId1": "CensysSRRiskEventsFunction",
+ "_dataConnectorContentId1": "[variables('dataConnectorContentId1')]",
+ "dataConnectorId1": "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/dataConnectors', variables('_dataConnectorContentId1'))]",
+ "_dataConnectorId1": "[variables('dataConnectorId1')]",
+ "dataConnectorTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-dc-',uniquestring(variables('_dataConnectorContentId1'))))]",
+ "dataConnectorVersion1": "1.0.0",
+ "_dataConnectorcontentProductId1": "[concat(take(variables('_solutionId'),50),'-','dc','-', uniqueString(concat(variables('_solutionId'),'-','DataConnector','-',variables('_dataConnectorContentId1'),'-', variables('dataConnectorVersion1'))))]",
+ "parserObject1": {
+ "_parserName1": "[concat(parameters('workspace'),'/','CensysRiskEvents')]",
+ "_parserId1": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('workspace'), 'CensysRiskEvents')]",
+ "parserTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-pr-',uniquestring('CensysRiskEvents-Parser')))]",
+ "parserVersion1": "1.0.0",
+ "parserContentId1": "CensysRiskEvents-Parser"
+ },
+ "parserObject2": {
+ "_parserName2": "[concat(parameters('workspace'),'/','CensysRisks')]",
+ "_parserId2": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('workspace'), 'CensysRisks')]",
+ "parserTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-pr-',uniquestring('CensysRisks-Parser')))]",
+ "parserVersion2": "1.0.0",
+ "parserContentId2": "CensysRisks-Parser"
+ },
+ "workbookVersion1": "1.0.0",
+ "workbookContentId1": "CensysSupplierRiskWorkbook",
+ "workbookId1": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId1'))]",
+ "workbookTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId1'))))]",
+ "_workbookContentId1": "[variables('workbookContentId1')]",
+ "workspaceResourceId": "[resourceId('microsoft.OperationalInsights/Workspaces', parameters('workspace'))]",
+ "_workbookcontentProductId1": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId1'),'-', variables('workbookVersion1'))))]",
+ "analyticRuleObject1": {
+ "analyticRuleVersion1": "1.0.0",
+ "_analyticRulecontentId1": "8a208922-5ef7-4721-ad8b-7f69e2163d79",
+ "analyticRuleId1": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '8a208922-5ef7-4721-ad8b-7f69e2163d79')]",
+ "analyticRuleTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('8a208922-5ef7-4721-ad8b-7f69e2163d79')))]",
+ "_analyticRulecontentProductId1": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','8a208922-5ef7-4721-ad8b-7f69e2163d79','-', '1.0.0')))]"
+ },
+ "_solutioncontentProductId": "[concat(take(variables('_solutionId'),50),'-','sl','-', uniqueString(concat(variables('_solutionId'),'-','Solution','-',variables('_solutionId'),'-', variables('_solutionVersion'))))]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('dataConnectorTemplateSpecName1')]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "Censys Supplier Risk data connector with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('dataConnectorVersion1')]",
+ "parameters": {},
+ "variables": {},
+ "resources": [
+ {
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',variables('_dataConnectorContentId1'))]",
+ "apiVersion": "2021-03-01-preview",
+ "type": "Microsoft.OperationalInsights/workspaces/providers/dataConnectors",
+ "location": "[parameters('workspace-location')]",
+ "kind": "GenericUI",
+ "properties": {
+ "connectorUiConfig": {
+ "id": "[variables('_uiConfigId1')]",
+ "title": "Censys Supplier Risk (using Azure Functions)",
+ "publisher": "Censys",
+ "descriptionMarkdown": "The Censys Supplier Risk data connector brings the Censys risk-events change feed (`/v1/supplychains/default/risk-events`) into Microsoft Sentinel using Azure Functions and the [Logs Ingestion API](https://learn.microsoft.com/azure/azure-monitor/logs/logs-ingestion-api-overview). The function runs on a schedule and remembers the `inserted_at` and `id` of the last event it ingested, so each run continues where the previous one stopped. Events are written to the table `CensysRiskEvents_CL` through a data collection endpoint and a data collection rule that the deployment template creates. Suppliers and risks can be selected by name; the connector keeps the supplier names and risk names with their ids in storage tables.",
+ "graphQueries": [
+ {
+ "metricName": "Total Censys risk events received",
+ "legend": "CensysRiskEvents_CL",
+ "baseQuery": "CensysRiskEvents_CL"
+ }
+ ],
+ "sampleQueries": [
+ {
+ "description": "All Censys risk events received, newest first.",
+ "query": "CensysRiskEvents_CL\n| sort by TimeGenerated desc"
+ },
+ {
+ "description": "Newly opened or reopened risks with critical or high severity.",
+ "query": "CensysRiskEvents_CL\n| where ChangeType in ('opened', 'reopened')\n| where Severity in ('CRITICAL', 'HIGH')\n| project TimeGenerated, Severity, RiskName, SupplierDisplayName, AssetIp, AssetHostname, PlatformUrl\n| order by TimeGenerated desc"
+ },
+ {
+ "description": "Rapid response findings (actively exploited).",
+ "query": "CensysRiskEvents_CL\n| where RapidResponse == true and ChangeType in ('opened', 'reopened')\n| project TimeGenerated, Severity, CveId, RiskName, SupplierDisplayName, AssetIp, Evidence, PlatformUrl"
+ },
+ {
+ "description": "Currently open risks per supplier (latest event per risk).",
+ "query": "CensysRiskEvents_CL\n| summarize arg_max(InsertedAt, *) by RiskInstanceId\n| where Status == 'open'\n| summarize OpenRisks = count() by SupplierDisplayName, Severity\n| order by OpenRisks desc"
+ },
+ {
+ "description": "Risk events mapped to known CVEs.",
+ "query": "CensysRiskEvents_CL\n| where isnotempty(CveId)\n| summarize AffectedAssets = dcount(RiskInstanceId) by CveId, Severity\n| order by AffectedAssets desc"
+ }
+ ],
+ "dataTypes": [
+ {
+ "name": "CensysRiskEvents_CL",
+ "lastDataReceivedQuery": "CensysRiskEvents_CL\n | summarize Time = max(TimeGenerated)\n | where isnotempty(Time)"
+ }
+ ],
+ "connectivityCriterias": [
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "CensysRiskEvents_CL\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ }
+ ],
+ "availability": {
+ "status": 1,
+ "isPreview": false
+ },
+ "permissions": {
+ "resourceProvider": [
+ {
+ "provider": "Microsoft.OperationalInsights/workspaces",
+ "permissionsDisplayText": "read and write permissions on the workspace are required.",
+ "providerDisplayName": "Workspace",
+ "scope": "Workspace",
+ "requiredPermissions": {
+ "write": true,
+ "read": true,
+ "delete": true
+ }
+ }
+ ],
+ "customs": [
+ {
+ "name": "Microsoft.Web/sites permissions",
+ "description": "Read and write permissions to Azure Functions to create a Function App is required. [See the documentation to learn more about Azure Functions](https://docs.microsoft.com/azure/azure-functions/)."
+ },
+ {
+ "name": "REST API Credentials/permissions",
+ "description": "A Censys personal access token and the Censys Organization ID are required. Users on Starter, Search, and Core plans need the API Access role. [Create a token](https://accounts.censys.io/settings/personal-access-tokens)."
+ },
+ {
+ "name": "Microsoft Entra application",
+ "description": "An app registration with a client secret is required. Permission to create role assignments on the data collection rule (Owner or User Access Administrator) is required to give the application the Monitoring Metrics Publisher role."
+ }
+ ]
+ },
+ "instructionSteps": [
+ {
+ "description": ">**NOTE:** This connector uses Azure Functions to connect to the Censys API to pull risk events into Microsoft Sentinel using the [Logs Ingestion API](https://learn.microsoft.com/azure/azure-monitor/logs/logs-ingestion-api-overview). The deployment template creates the table CensysRiskEvents_CL, a data collection endpoint and a data collection rule in the workspace. A second function, CensysSRSuppliersAndRisks, keeps the lists of supplier names and risk names, with their ids, in storage tables of the Function App (nothing is sent to Microsoft Sentinel), so supplier names and risk names can be used as an input. This might result in additional data ingestion costs. Check the [Azure Functions pricing page](https://azure.microsoft.com/pricing/details/functions/) for details."
+ },
+ {
+ "description": ">**(Optional Step)** Securely store the Censys API token and the client secret in Azure Key Vault. Azure Key Vault provides a secure mechanism to store and retrieve key values. [Follow these instructions](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) to use Azure Key Vault with an Azure Function App."
+ },
+ {
+ "description": "**STEP 1 - Steps to Create/Get a Censys Personal Access Token and Organization ID**\n\n Follow these instructions to get the values needed by this connector:\n 1. Sign in to Censys and open [Personal access tokens](https://accounts.censys.io/settings/personal-access-tokens).\n 2. Create a personal access token. Users on Starter, Search, and Core plans need the **API Access** role.\n 3. Copy the token. It is shown only once.\n 4. Copy the **Organization ID** (UUID) of the Censys organization you want to collect risk events for."
+ },
+ {
+ "description": "**STEP 2 - Get the name of the Log Analytics workspace**\n\n The deployment template creates the table, the data collection endpoint and the data collection rule in the workspace of Microsoft Sentinel, so deploy it in the resource group of that workspace. You need the **name** of the workspace (workspace Overview page)."
+ },
+ {
+ "description": "**STEP 3 - App Registration steps for the Application in Microsoft Entra ID**\n\n This integration requires an App registration in the Azure portal. Follow the steps in this section to create a new application in Microsoft Entra ID:\n 1. Sign in to the [Azure portal](https://portal.azure.com/).\n 2. Search for and select **Microsoft Entra ID**.\n 3. Under **Manage**, select **App registrations > New registration**.\n 4. Enter a display **Name** for your application.\n 5. Select **Register** to complete the initial app registration.\n 6. When registration finishes, the Azure portal displays the app registration's Overview pane. You see the **Application (client) ID** and **Tenant ID**. The client ID and Tenant ID are required as configuration parameters for the execution of the Censys SR data connector. \n\n> **Reference link:** [https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app](https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app)"
+ },
+ {
+ "description": "**STEP 4 - Add a client secret for application in Microsoft Entra ID**\n\n Sometimes called an application password, a client secret is a string value required for the execution of the Censys SR data connector. Follow the steps in this section to create a new Client Secret:\n 1. In the Azure portal, in **App registrations**, select your application.\n 2. Select **Certificates & secrets > Client secrets > New client secret**.\n 3. Add a description for your client secret.\n 4. Select an expiration for the secret or specify a custom lifetime. Limit is 24 months.\n 5. Select **Add**. \n 6. *Record the secret's value for use in your client application code. This secret value is never displayed again after you leave this page.* The secret value is required as configuration parameter for the execution of the Censys SR data connector. \n\n> **Reference link:** [https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app#add-a-client-secret](https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app#add-a-client-secret)"
+ },
+ {
+ "description": "**STEP 5 - Get Object ID of your application in Microsoft Entra ID**\n\n After creating your app registration, follow the steps in this section to get Object ID:\n 1. Go to **Microsoft Entra ID**.\n 2. Select **Enterprise applications** from the left menu.\n 3. Find your newly created application in the list (you can search by the name you provided).\n 4. Click on the application.\n 5. On the overview page, copy the **Object ID**. This is the **Azure_Entra_Object_Id** needed for your ARM template role assignment. The template gives this application the **Monitoring Metrics Publisher** role on the data collection rule, so no other role needs to be assigned."
+ },
+ {
+ "description": "**STEP 6 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Function**\n\n>**IMPORTANT:** Before deploying the Censys SR data connector, have the Censys API token, the organization ID, the workspace name and the Azure credentials (client ID, client secret, tenant ID, object ID) ready."
+ },
+ {
+ "description": "Use this method for automated deployment of the Censys SR connector.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[](https://aka.ms/sentinel-CensysSRRiskEventsFunction-azuredeploy)\n2. Select the preferred **Subscription** and **Resource Group**. Use the resource group of the Log Analytics workspace of Microsoft Sentinel. \n3. Enter the below information : \n\n\t a. **Censys SR Function Name** - Name prefix of the Azure Function App to be created, up to 10 characters. Default is CensysSR.\n\n\t b. **Censys Api Key** - Enter your Censys personal access token.\n\n\t c. **Censys Organization Id** - Enter the UUID of the Censys organization to collect risk events for, for example 00000000-0000-0000-0000-000000000000.\n\n\t d. **Censys Start Date Time** - Optional. UTC time to start collecting from, in the form 2026-10-01T00:00:00Z. It must not be in the future. Leave empty for full history (all events Censys still retains).\n\n\t e. **Censys Supplier Names** - Optional. Names of the suppliers to collect risk events for, separated by an asterisk (*). For example: OpenAI*Harvey*Acme, Inc. Names are not case-sensitive and can be picked from the Censys Supplier Risk UI. Names that are not found among your suppliers are skipped with a warning. Leave empty to collect all suppliers. A change applies from the next run and continues from the current position, so avoid changing it often. Maximum 500 characters.\n\n\t f. **Censys Risk Names** - Optional. Names of the risks to collect risk events for, separated by an asterisk (*). For example: Log4Shell*Exposed Redis. Names are not case-sensitive and must match the risk name shown in the Censys Supplier Risk UI. Names that are not found among the risks of your supply chain are skipped with a warning. Leave empty to collect all risks. A change applies from the next run and continues from the current position, so avoid changing it often. Maximum 500 characters.\n\n\t g. **Censys Change Types** - Optional. Comma-separated change types: opened, resolved, reopened. Other values stop the run with an error. Leave empty for all.\n\n\t h. **Censys Severity** - Optional. Comma-separated severities: CRITICAL, HIGH, MEDIUM, LOW. Other values stop the run with an error. Leave empty for all.\n\n\t i. **Censys Rapid Response** - Optional. Select true for rapid response findings only, false to exclude them. Leave empty for both.\n\n\t j. **Azure_Client_Id** - Enter the Azure client ID of the app registration you created.\n\n\t k. **Azure_Client_Secret** - Enter the client secret you created for the app registration.\n\n\t l. **Azure_Tenant_Id** - Enter the Azure tenant ID of your Microsoft Entra ID.\n\n\t m. **Azure_Entra_Object_Id** - Enter the object ID of your Microsoft Entra app (Enterprise application).\n\n\t n. **WorkspaceName** - Name of the Log Analytics workspace of Microsoft Sentinel. The deployment creates the table CensysRiskEvents_CL, a data collection endpoint and a data collection rule in it.\n\n\t o. **Location** - Region of the data collection endpoint and rule. It must be the region of the workspace. Default is the region of the resource group.\n\n\t p. **Censys Risk Events Table Name** - Name of the table in the workspace that the risk events are written to. Default is CensysRiskEvents_CL (the suffix _CL is added, or corrected, when it is missing or in another case). Maximum 52 characters. Warning: the parser, the workbook, the analytic rule and the connector status check of this solution read CensysRiskEvents_CL. If you use another name, update these resources to read the new table.\n\n\t q. **Censys Risk Events Collection Schedule** - Please enter a valid Quartz cron-expression, for example 0 */10 * * * *. **Warning:** keep the frequency at least 10 minutes for a better experience. Default is 0 */10 * * * * (every 10 minutes).\n\n\t r. **Censys Supplier and Risk List Refresh Schedule** - Quartz cron-expression for how often the function that refreshes the lists of supplier names and risk names with their ids runs, for example 0 */30 * * * *. It also runs once when the app starts. **Warning:** keep the frequency at least 10 minutes for a better experience. Default is every 30 minutes.\n\n\t s. **AppInsightsWorkspaceResourceID** - Use 'Log Analytic Workspace > Properties' blade having 'Resource ID' property value. This is a fully qualified resource ID which is in format '/subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.OperationalInsights/workspaces/{workspace-name}'.\n\n\t t. **Censys SR Function Log Level** - Select log level or log severity value from DEBUG, INFO, ERROR. By default it is set to INFO.\n\n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy.",
+ "title": "Option 1 - Azure Resource Manager (ARM) Template"
+ },
+ {
+ "description": "Use the following step-by-step instructions to deploy the Censys SR data connector manually with Azure Functions (Deployment via Visual Studio Code).",
+ "title": "Option 2 - Manual Deployment of Azure Functions"
+ },
+ {
+ "description": "**1. Deploy a Function App**\n\n> **NOTE:** You will need to [prepare VS code](https://docs.microsoft.com/azure/azure-functions/functions-create-first-function-python#prerequisites) for Azure function development.\n\n1. Download the [Azure Function App](https://aka.ms/sentinel-CensysSR100-functionapp) file. Extract archive to your local development computer.\n2. Start VS Code. Choose File in the main menu and select Open Folder.\n3. Select the top level folder from extracted files.\n4. Choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose the **Deploy to function app** button.\nIf you aren't already signed in, choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose **Sign in to Azure**\nIf you're already signed in, go to the next step.\n5. Provide the following information at the prompts:\n\n\ta. **Select folder:** Choose a folder from your workspace or browse to one that contains your function app.\n\n\tb. **Select Subscription:** Choose the subscription to use.\n\n\tc. Select **Create new Function App in Azure** (Don't choose the Advanced option)\n\n\td. **Enter a globally unique name for the function app:** Type a name that is valid in a URL path. The name you type is validated to make sure that it's unique in Azure Functions. (e.g. CensysSRXXXXX).\n\n\te. **Select a runtime:** Choose Python 3.14 (the packages in the zip are built for it).\n\n\tf. Select a location for new resources. For better performance and lower costs choose the same [region](https://azure.microsoft.com/regions/) where Microsoft Sentinel is located.\n\n6. Deployment will begin. A notification is displayed after your function app is created and the deployment package is applied.\n7. Go to Azure Portal for the Function App configuration.\n8. In the Function App, enable the system-assigned managed identity if you want to use it for the function app storage.\n9. The manual method needs the table CensysRiskEvents_CL, a data collection endpoint and a data collection rule. Deploy the template of Option 1 once to create them, or create them yourself.\n10. Give the Microsoft Entra application the **Monitoring Metrics Publisher** role on the data collection rule: open the rule, select **Access control (IAM) > Add role assignment**, choose the role and select your application."
+ },
+ {
+ "description": "**2. Configure the Function App**\n\n1. In the Function App, select the Function App Name and select **Configuration**.\n2. In the **Application settings** tab, select **+ New application setting**.\n3. Add each of the following application settings individually, with their respective values (case-sensitive): \n\n\t a. **CensysApiToken** - Enter your Censys personal access token.\n\n\t b. **OrganizationId** - Enter the UUID of the Censys organization, for example 00000000-0000-0000-0000-000000000000.\n\n\t c. **InitialStartTime** - Optional. UTC start time in the form 2026-10-01T00:00:00Z. It must not be in the future. Leave empty for all events Censys still retains.\n\n\t d. **SupplierNames** - Optional. Supplier names separated by an asterisk (*), for example OpenAI*Harvey*Acme, Inc. Pick the names from the Censys Supplier Risk UI. Maximum 500 characters.\n\n\t e. **RiskNames** - Optional. Risk names separated by an asterisk (*), for example Log4Shell*Exposed Redis. Pick the names from the Censys Supplier Risk UI. Maximum 500 characters.\n\n\t f. **ChangeTypes** - Optional. Comma-separated: opened, resolved, reopened. Other values stop the run with an error.\n\n\t g. **Severity** - Optional. Comma-separated: CRITICAL, HIGH, MEDIUM, LOW. Other values stop the run with an error.\n\n\t h. **RapidResponse** - Optional. true or false.\n\n\t i. **AZURE_CLIENT_ID** - Azure Client Id of your Microsoft Entra app.\n\n\t j. **AZURE_CLIENT_SECRET** - Client secret of your Microsoft Entra app.\n\n\t k. **AZURE_TENANT_ID** - Azure Tenant Id of your Microsoft Entra ID.\n\n\t l. **AZURE_DATA_COLLECTION_ENDPOINT** - Logs ingestion URL of the data collection endpoint created for this connector (Monitor > Data Collection Endpoints > the endpoint > Logs Ingestion).\n\n\t m. **AZURE_DATA_COLLECTION_RULE_ID** - Immutable ID of the data collection rule created for this connector (Monitor > Data Collection Rules > the rule > JSON View > immutableId).\n\n\t n. **StreamName** - Input stream of that data collection rule, Custom-CensysRiskEvents_CL.\n\n\t o. **SCOPE** - https://monitor.azure.com/.default\n\n\t p. **Schedule** - Quartz cron-expression, for example 0 */10 * * * *. **Warning:** keep the frequency at least 10 minutes for a better experience. Default is 0 */10 * * * *.\n\n\t q. **SuppliersAndRisksSchedule** - Quartz cron-expression of the function that refreshes the supplier names and risk names with their ids. **Warning:** keep the frequency at least 10 minutes for a better experience. Default is 0 */30 * * * *.\n\n\t r. **LogLevel** - DEBUG, INFO or ERROR. Default is INFO.\n\n4. Once all application settings have been entered, click **Save**."
+ }
+ ]
+ }
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('DataConnector-', last(split(variables('_dataConnectorId1'),'/'))))]",
+ "properties": {
+ "parentId": "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/dataConnectors', variables('_dataConnectorContentId1'))]",
+ "contentId": "[variables('_dataConnectorContentId1')]",
+ "kind": "DataConnector",
+ "version": "[variables('dataConnectorVersion1')]",
+ "source": {
+ "kind": "Solution",
+ "name": "Censys Supplier Risk",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ }
+ }
+ }
+ ]
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('_dataConnectorContentId1')]",
+ "contentKind": "DataConnector",
+ "displayName": "Censys Supplier Risk (using Azure Functions)",
+ "contentProductId": "[variables('_dataConnectorcontentProductId1')]",
+ "id": "[variables('_dataConnectorcontentProductId1')]",
+ "version": "[variables('dataConnectorVersion1')]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('DataConnector-', last(split(variables('_dataConnectorId1'),'/'))))]",
+ "dependsOn": [
+ "[variables('_dataConnectorId1')]"
+ ],
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "parentId": "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/dataConnectors', variables('_dataConnectorContentId1'))]",
+ "contentId": "[variables('_dataConnectorContentId1')]",
+ "kind": "DataConnector",
+ "version": "[variables('dataConnectorVersion1')]",
+ "source": {
+ "kind": "Solution",
+ "name": "Censys Supplier Risk",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ }
+ }
+ },
+ {
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',variables('_dataConnectorContentId1'))]",
+ "apiVersion": "2021-03-01-preview",
+ "type": "Microsoft.OperationalInsights/workspaces/providers/dataConnectors",
+ "location": "[parameters('workspace-location')]",
+ "kind": "GenericUI",
+ "properties": {
+ "connectorUiConfig": {
+ "title": "Censys Supplier Risk (using Azure Functions)",
+ "publisher": "Censys",
+ "descriptionMarkdown": "The Censys Supplier Risk data connector brings the Censys risk-events change feed (`/v1/supplychains/default/risk-events`) into Microsoft Sentinel using Azure Functions and the [Logs Ingestion API](https://learn.microsoft.com/azure/azure-monitor/logs/logs-ingestion-api-overview). The function runs on a schedule and remembers the `inserted_at` and `id` of the last event it ingested, so each run continues where the previous one stopped. Events are written to the table `CensysRiskEvents_CL` through a data collection endpoint and a data collection rule that the deployment template creates. Suppliers and risks can be selected by name; the connector keeps the supplier names and risk names with their ids in storage tables.",
+ "graphQueries": [
+ {
+ "metricName": "Total Censys risk events received",
+ "legend": "CensysRiskEvents_CL",
+ "baseQuery": "CensysRiskEvents_CL"
+ }
+ ],
+ "dataTypes": [
+ {
+ "name": "CensysRiskEvents_CL",
+ "lastDataReceivedQuery": "CensysRiskEvents_CL\n | summarize Time = max(TimeGenerated)\n | where isnotempty(Time)"
+ }
+ ],
+ "connectivityCriterias": [
+ {
+ "type": "IsConnectedQuery",
+ "value": [
+ "CensysRiskEvents_CL\n | summarize LastLogReceived = max(TimeGenerated)\n | project IsConnected = LastLogReceived > ago(30d)"
+ ]
+ }
+ ],
+ "sampleQueries": [
+ {
+ "description": "All Censys risk events received, newest first.",
+ "query": "CensysRiskEvents_CL\n| sort by TimeGenerated desc"
+ },
+ {
+ "description": "Newly opened or reopened risks with critical or high severity.",
+ "query": "CensysRiskEvents_CL\n| where ChangeType in ('opened', 'reopened')\n| where Severity in ('CRITICAL', 'HIGH')\n| project TimeGenerated, Severity, RiskName, SupplierDisplayName, AssetIp, AssetHostname, PlatformUrl\n| order by TimeGenerated desc"
+ },
+ {
+ "description": "Rapid response findings (actively exploited).",
+ "query": "CensysRiskEvents_CL\n| where RapidResponse == true and ChangeType in ('opened', 'reopened')\n| project TimeGenerated, Severity, CveId, RiskName, SupplierDisplayName, AssetIp, Evidence, PlatformUrl"
+ },
+ {
+ "description": "Currently open risks per supplier (latest event per risk).",
+ "query": "CensysRiskEvents_CL\n| summarize arg_max(InsertedAt, *) by RiskInstanceId\n| where Status == 'open'\n| summarize OpenRisks = count() by SupplierDisplayName, Severity\n| order by OpenRisks desc"
+ },
+ {
+ "description": "Risk events mapped to known CVEs.",
+ "query": "CensysRiskEvents_CL\n| where isnotempty(CveId)\n| summarize AffectedAssets = dcount(RiskInstanceId) by CveId, Severity\n| order by AffectedAssets desc"
+ }
+ ],
+ "availability": {
+ "status": 1,
+ "isPreview": false
+ },
+ "permissions": {
+ "resourceProvider": [
+ {
+ "provider": "Microsoft.OperationalInsights/workspaces",
+ "permissionsDisplayText": "read and write permissions on the workspace are required.",
+ "providerDisplayName": "Workspace",
+ "scope": "Workspace",
+ "requiredPermissions": {
+ "write": true,
+ "read": true,
+ "delete": true
+ }
+ }
+ ],
+ "customs": [
+ {
+ "name": "Microsoft.Web/sites permissions",
+ "description": "Read and write permissions to Azure Functions to create a Function App is required. [See the documentation to learn more about Azure Functions](https://docs.microsoft.com/azure/azure-functions/)."
+ },
+ {
+ "name": "REST API Credentials/permissions",
+ "description": "A Censys personal access token and the Censys Organization ID are required. Users on Starter, Search, and Core plans need the API Access role. [Create a token](https://accounts.censys.io/settings/personal-access-tokens)."
+ },
+ {
+ "name": "Microsoft Entra application",
+ "description": "An app registration with a client secret is required. Permission to create role assignments on the data collection rule (Owner or User Access Administrator) is required to give the application the Monitoring Metrics Publisher role."
+ }
+ ]
+ },
+ "instructionSteps": [
+ {
+ "description": ">**NOTE:** This connector uses Azure Functions to connect to the Censys API to pull risk events into Microsoft Sentinel using the [Logs Ingestion API](https://learn.microsoft.com/azure/azure-monitor/logs/logs-ingestion-api-overview). The deployment template creates the table CensysRiskEvents_CL, a data collection endpoint and a data collection rule in the workspace. A second function, CensysSRSuppliersAndRisks, keeps the lists of supplier names and risk names, with their ids, in storage tables of the Function App (nothing is sent to Microsoft Sentinel), so supplier names and risk names can be used as an input. This might result in additional data ingestion costs. Check the [Azure Functions pricing page](https://azure.microsoft.com/pricing/details/functions/) for details."
+ },
+ {
+ "description": ">**(Optional Step)** Securely store the Censys API token and the client secret in Azure Key Vault. Azure Key Vault provides a secure mechanism to store and retrieve key values. [Follow these instructions](https://docs.microsoft.com/azure/app-service/app-service-key-vault-references) to use Azure Key Vault with an Azure Function App."
+ },
+ {
+ "description": "**STEP 1 - Steps to Create/Get a Censys Personal Access Token and Organization ID**\n\n Follow these instructions to get the values needed by this connector:\n 1. Sign in to Censys and open [Personal access tokens](https://accounts.censys.io/settings/personal-access-tokens).\n 2. Create a personal access token. Users on Starter, Search, and Core plans need the **API Access** role.\n 3. Copy the token. It is shown only once.\n 4. Copy the **Organization ID** (UUID) of the Censys organization you want to collect risk events for."
+ },
+ {
+ "description": "**STEP 2 - Get the name of the Log Analytics workspace**\n\n The deployment template creates the table, the data collection endpoint and the data collection rule in the workspace of Microsoft Sentinel, so deploy it in the resource group of that workspace. You need the **name** of the workspace (workspace Overview page)."
+ },
+ {
+ "description": "**STEP 3 - App Registration steps for the Application in Microsoft Entra ID**\n\n This integration requires an App registration in the Azure portal. Follow the steps in this section to create a new application in Microsoft Entra ID:\n 1. Sign in to the [Azure portal](https://portal.azure.com/).\n 2. Search for and select **Microsoft Entra ID**.\n 3. Under **Manage**, select **App registrations > New registration**.\n 4. Enter a display **Name** for your application.\n 5. Select **Register** to complete the initial app registration.\n 6. When registration finishes, the Azure portal displays the app registration's Overview pane. You see the **Application (client) ID** and **Tenant ID**. The client ID and Tenant ID are required as configuration parameters for the execution of the Censys SR data connector. \n\n> **Reference link:** [https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app](https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app)"
+ },
+ {
+ "description": "**STEP 4 - Add a client secret for application in Microsoft Entra ID**\n\n Sometimes called an application password, a client secret is a string value required for the execution of the Censys SR data connector. Follow the steps in this section to create a new Client Secret:\n 1. In the Azure portal, in **App registrations**, select your application.\n 2. Select **Certificates & secrets > Client secrets > New client secret**.\n 3. Add a description for your client secret.\n 4. Select an expiration for the secret or specify a custom lifetime. Limit is 24 months.\n 5. Select **Add**. \n 6. *Record the secret's value for use in your client application code. This secret value is never displayed again after you leave this page.* The secret value is required as configuration parameter for the execution of the Censys SR data connector. \n\n> **Reference link:** [https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app#add-a-client-secret](https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app#add-a-client-secret)"
+ },
+ {
+ "description": "**STEP 5 - Get Object ID of your application in Microsoft Entra ID**\n\n After creating your app registration, follow the steps in this section to get Object ID:\n 1. Go to **Microsoft Entra ID**.\n 2. Select **Enterprise applications** from the left menu.\n 3. Find your newly created application in the list (you can search by the name you provided).\n 4. Click on the application.\n 5. On the overview page, copy the **Object ID**. This is the **Azure_Entra_Object_Id** needed for your ARM template role assignment. The template gives this application the **Monitoring Metrics Publisher** role on the data collection rule, so no other role needs to be assigned."
+ },
+ {
+ "description": "**STEP 6 - Choose ONE from the following two deployment options to deploy the connector and the associated Azure Function**\n\n>**IMPORTANT:** Before deploying the Censys SR data connector, have the Censys API token, the organization ID, the workspace name and the Azure credentials (client ID, client secret, tenant ID, object ID) ready."
+ },
+ {
+ "description": "Use this method for automated deployment of the Censys SR connector.\n\n1. Click the **Deploy to Azure** button below. \n\n\t[](https://aka.ms/sentinel-CensysSRRiskEventsFunction-azuredeploy)\n2. Select the preferred **Subscription** and **Resource Group**. Use the resource group of the Log Analytics workspace of Microsoft Sentinel. \n3. Enter the below information : \n\n\t a. **Censys SR Function Name** - Name prefix of the Azure Function App to be created, up to 10 characters. Default is CensysSR.\n\n\t b. **Censys Api Key** - Enter your Censys personal access token.\n\n\t c. **Censys Organization Id** - Enter the UUID of the Censys organization to collect risk events for, for example 00000000-0000-0000-0000-000000000000.\n\n\t d. **Censys Start Date Time** - Optional. UTC time to start collecting from, in the form 2026-10-01T00:00:00Z. It must not be in the future. Leave empty for full history (all events Censys still retains).\n\n\t e. **Censys Supplier Names** - Optional. Names of the suppliers to collect risk events for, separated by an asterisk (*). For example: OpenAI*Harvey*Acme, Inc. Names are not case-sensitive and can be picked from the Censys Supplier Risk UI. Names that are not found among your suppliers are skipped with a warning. Leave empty to collect all suppliers. A change applies from the next run and continues from the current position, so avoid changing it often. Maximum 500 characters.\n\n\t f. **Censys Risk Names** - Optional. Names of the risks to collect risk events for, separated by an asterisk (*). For example: Log4Shell*Exposed Redis. Names are not case-sensitive and must match the risk name shown in the Censys Supplier Risk UI. Names that are not found among the risks of your supply chain are skipped with a warning. Leave empty to collect all risks. A change applies from the next run and continues from the current position, so avoid changing it often. Maximum 500 characters.\n\n\t g. **Censys Change Types** - Optional. Comma-separated change types: opened, resolved, reopened. Other values stop the run with an error. Leave empty for all.\n\n\t h. **Censys Severity** - Optional. Comma-separated severities: CRITICAL, HIGH, MEDIUM, LOW. Other values stop the run with an error. Leave empty for all.\n\n\t i. **Censys Rapid Response** - Optional. Select true for rapid response findings only, false to exclude them. Leave empty for both.\n\n\t j. **Azure_Client_Id** - Enter the Azure client ID of the app registration you created.\n\n\t k. **Azure_Client_Secret** - Enter the client secret you created for the app registration.\n\n\t l. **Azure_Tenant_Id** - Enter the Azure tenant ID of your Microsoft Entra ID.\n\n\t m. **Azure_Entra_Object_Id** - Enter the object ID of your Microsoft Entra app (Enterprise application).\n\n\t n. **WorkspaceName** - Name of the Log Analytics workspace of Microsoft Sentinel. The deployment creates the table CensysRiskEvents_CL, a data collection endpoint and a data collection rule in it.\n\n\t o. **Location** - Region of the data collection endpoint and rule. It must be the region of the workspace. Default is the region of the resource group.\n\n\t p. **Censys Risk Events Table Name** - Name of the table in the workspace that the risk events are written to. Default is CensysRiskEvents_CL (the suffix _CL is added, or corrected, when it is missing or in another case). Maximum 52 characters. Warning: the parser, the workbook, the analytic rule and the connector status check of this solution read CensysRiskEvents_CL. If you use another name, update these resources to read the new table.\n\n\t q. **Censys Risk Events Collection Schedule** - Please enter a valid Quartz cron-expression, for example 0 */10 * * * *. **Warning:** keep the frequency at least 10 minutes for a better experience. Default is 0 */10 * * * * (every 10 minutes).\n\n\t r. **Censys Supplier and Risk List Refresh Schedule** - Quartz cron-expression for how often the function that refreshes the lists of supplier names and risk names with their ids runs, for example 0 */30 * * * *. It also runs once when the app starts. **Warning:** keep the frequency at least 10 minutes for a better experience. Default is every 30 minutes.\n\n\t s. **AppInsightsWorkspaceResourceID** - Use 'Log Analytic Workspace > Properties' blade having 'Resource ID' property value. This is a fully qualified resource ID which is in format '/subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.OperationalInsights/workspaces/{workspace-name}'.\n\n\t t. **Censys SR Function Log Level** - Select log level or log severity value from DEBUG, INFO, ERROR. By default it is set to INFO.\n\n4. Mark the checkbox labeled **I agree to the terms and conditions stated above**. \n5. Click **Purchase** to deploy.",
+ "title": "Option 1 - Azure Resource Manager (ARM) Template"
+ },
+ {
+ "description": "Use the following step-by-step instructions to deploy the Censys SR data connector manually with Azure Functions (Deployment via Visual Studio Code).",
+ "title": "Option 2 - Manual Deployment of Azure Functions"
+ },
+ {
+ "description": "**1. Deploy a Function App**\n\n> **NOTE:** You will need to [prepare VS code](https://docs.microsoft.com/azure/azure-functions/functions-create-first-function-python#prerequisites) for Azure function development.\n\n1. Download the [Azure Function App](https://aka.ms/sentinel-CensysSR100-functionapp) file. Extract archive to your local development computer.\n2. Start VS Code. Choose File in the main menu and select Open Folder.\n3. Select the top level folder from extracted files.\n4. Choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose the **Deploy to function app** button.\nIf you aren't already signed in, choose the Azure icon in the Activity bar, then in the **Azure: Functions** area, choose **Sign in to Azure**\nIf you're already signed in, go to the next step.\n5. Provide the following information at the prompts:\n\n\ta. **Select folder:** Choose a folder from your workspace or browse to one that contains your function app.\n\n\tb. **Select Subscription:** Choose the subscription to use.\n\n\tc. Select **Create new Function App in Azure** (Don't choose the Advanced option)\n\n\td. **Enter a globally unique name for the function app:** Type a name that is valid in a URL path. The name you type is validated to make sure that it's unique in Azure Functions. (e.g. CensysSRXXXXX).\n\n\te. **Select a runtime:** Choose Python 3.14 (the packages in the zip are built for it).\n\n\tf. Select a location for new resources. For better performance and lower costs choose the same [region](https://azure.microsoft.com/regions/) where Microsoft Sentinel is located.\n\n6. Deployment will begin. A notification is displayed after your function app is created and the deployment package is applied.\n7. Go to Azure Portal for the Function App configuration.\n8. In the Function App, enable the system-assigned managed identity if you want to use it for the function app storage.\n9. The manual method needs the table CensysRiskEvents_CL, a data collection endpoint and a data collection rule. Deploy the template of Option 1 once to create them, or create them yourself.\n10. Give the Microsoft Entra application the **Monitoring Metrics Publisher** role on the data collection rule: open the rule, select **Access control (IAM) > Add role assignment**, choose the role and select your application."
+ },
+ {
+ "description": "**2. Configure the Function App**\n\n1. In the Function App, select the Function App Name and select **Configuration**.\n2. In the **Application settings** tab, select **+ New application setting**.\n3. Add each of the following application settings individually, with their respective values (case-sensitive): \n\n\t a. **CensysApiToken** - Enter your Censys personal access token.\n\n\t b. **OrganizationId** - Enter the UUID of the Censys organization, for example 00000000-0000-0000-0000-000000000000.\n\n\t c. **InitialStartTime** - Optional. UTC start time in the form 2026-10-01T00:00:00Z. It must not be in the future. Leave empty for all events Censys still retains.\n\n\t d. **SupplierNames** - Optional. Supplier names separated by an asterisk (*), for example OpenAI*Harvey*Acme, Inc. Pick the names from the Censys Supplier Risk UI. Maximum 500 characters.\n\n\t e. **RiskNames** - Optional. Risk names separated by an asterisk (*), for example Log4Shell*Exposed Redis. Pick the names from the Censys Supplier Risk UI. Maximum 500 characters.\n\n\t f. **ChangeTypes** - Optional. Comma-separated: opened, resolved, reopened. Other values stop the run with an error.\n\n\t g. **Severity** - Optional. Comma-separated: CRITICAL, HIGH, MEDIUM, LOW. Other values stop the run with an error.\n\n\t h. **RapidResponse** - Optional. true or false.\n\n\t i. **AZURE_CLIENT_ID** - Azure Client Id of your Microsoft Entra app.\n\n\t j. **AZURE_CLIENT_SECRET** - Client secret of your Microsoft Entra app.\n\n\t k. **AZURE_TENANT_ID** - Azure Tenant Id of your Microsoft Entra ID.\n\n\t l. **AZURE_DATA_COLLECTION_ENDPOINT** - Logs ingestion URL of the data collection endpoint created for this connector (Monitor > Data Collection Endpoints > the endpoint > Logs Ingestion).\n\n\t m. **AZURE_DATA_COLLECTION_RULE_ID** - Immutable ID of the data collection rule created for this connector (Monitor > Data Collection Rules > the rule > JSON View > immutableId).\n\n\t n. **StreamName** - Input stream of that data collection rule, Custom-CensysRiskEvents_CL.\n\n\t o. **SCOPE** - https://monitor.azure.com/.default\n\n\t p. **Schedule** - Quartz cron-expression, for example 0 */10 * * * *. **Warning:** keep the frequency at least 10 minutes for a better experience. Default is 0 */10 * * * *.\n\n\t q. **SuppliersAndRisksSchedule** - Quartz cron-expression of the function that refreshes the supplier names and risk names with their ids. **Warning:** keep the frequency at least 10 minutes for a better experience. Default is 0 */30 * * * *.\n\n\t r. **LogLevel** - DEBUG, INFO or ERROR. Default is INFO.\n\n4. Once all application settings have been entered, click **Save**."
+ }
+ ],
+ "id": "[variables('_uiConfigId1')]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('parserObject1').parserTemplateSpecName1]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "CensysRiskEvents Data Parser with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('parserObject1').parserVersion1]",
+ "parameters": {},
+ "variables": {},
+ "resources": [
+ {
+ "name": "[variables('parserObject1')._parserName1]",
+ "apiVersion": "2025-07-01",
+ "type": "Microsoft.OperationalInsights/workspaces/savedSearches",
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "eTag": "*",
+ "displayName": "Parser for CensysRiskEvents",
+ "category": "Microsoft Sentinel Parser",
+ "functionAlias": "CensysRiskEvents",
+ "query": "let CensysRiskEvents_view = view () {\n // The empty datatable keeps the parser working (returning no rows) when the table does not exist yet\n union isfuzzy=true\n (datatable(\n TimeGenerated:datetime,\n EventId:string,\n InsertedAt:datetime,\n OccurredAt:datetime,\n ChangeType:string,\n RiskInstanceId:string,\n SupplyChainId:string,\n AssetType:string,\n RiskTypeId:string,\n CveId:string,\n RiskCategory:string,\n RiskName:string,\n Description:string,\n Remediations:dynamic,\n Severity:string,\n Score:real,\n Confidence:real,\n Status:string,\n ResolveTime:datetime,\n ResolveReason:string,\n SupplierId:string,\n SupplierDisplayName:string,\n Suppliers:dynamic,\n AssetIp:string,\n AssetHostname:string,\n AssetPort:int,\n ServiceTransportProtocol:string,\n ServicePort:int,\n ServiceProtocol:string,\n Tags:dynamic,\n RapidResponse:bool,\n Evidence:string,\n FirstDetectedAt:datetime,\n LastDetectedAt:datetime,\n CreatedAt:datetime,\n PlatformUrl:string,\n IngestedAt:datetime\n )[]),\n (CensysRiskEvents_CL | extend IngestedAt = ingestion_time())\n // Treat every field as optional: a column that is missing from the table becomes empty instead of failing the query\n | extend\n TimeGenerated = column_ifexists('TimeGenerated', datetime(null)),\n EventId = column_ifexists('EventId', ''),\n InsertedAt = column_ifexists('InsertedAt', datetime(null)),\n OccurredAt = column_ifexists('OccurredAt', datetime(null)),\n ChangeType = column_ifexists('ChangeType', ''),\n RiskInstanceId = column_ifexists('RiskInstanceId', ''),\n SupplyChainId = column_ifexists('SupplyChainId', ''),\n AssetType = column_ifexists('AssetType', ''),\n RiskTypeId = column_ifexists('RiskTypeId', ''),\n CveId = column_ifexists('CveId', ''),\n RiskCategory = column_ifexists('RiskCategory', ''),\n RiskName = column_ifexists('RiskName', ''),\n Description = column_ifexists('Description', ''),\n Remediations = column_ifexists('Remediations', dynamic(null)),\n Severity = column_ifexists('Severity', ''),\n Score = column_ifexists('Score', real(null)),\n Confidence = column_ifexists('Confidence', real(null)),\n Status = column_ifexists('Status', ''),\n ResolveTime = column_ifexists('ResolveTime', datetime(null)),\n ResolveReason = column_ifexists('ResolveReason', ''),\n SupplierId = column_ifexists('SupplierId', ''),\n SupplierDisplayName = column_ifexists('SupplierDisplayName', ''),\n Suppliers = column_ifexists('Suppliers', dynamic(null)),\n AssetIp = column_ifexists('AssetIp', ''),\n AssetHostname = column_ifexists('AssetHostname', ''),\n AssetPort = column_ifexists('AssetPort', int(null)),\n ServiceTransportProtocol = column_ifexists('ServiceTransportProtocol', ''),\n ServicePort = column_ifexists('ServicePort', int(null)),\n ServiceProtocol = column_ifexists('ServiceProtocol', ''),\n Tags = column_ifexists('Tags', dynamic(null)),\n RapidResponse = column_ifexists('RapidResponse', bool(null)),\n Evidence = column_ifexists('Evidence', ''),\n FirstDetectedAt = column_ifexists('FirstDetectedAt', datetime(null)),\n LastDetectedAt = column_ifexists('LastDetectedAt', datetime(null)),\n CreatedAt = column_ifexists('CreatedAt', datetime(null)),\n PlatformUrl = column_ifexists('PlatformUrl', '')\n // Delivery is at-least-once, so keep one row per event: the first copy that was ingested\n | summarize arg_min(IngestedAt, *) by EventId\n | extend\n EventVendor = \"Censys\",\n EventProduct = \"Censys Supplier Risk\",\n ChangeType = tolower(ChangeType),\n Status = tolower(Status),\n Severity = toupper(Severity),\n SupplierId = coalesce(tostring(SupplierId), tostring(Suppliers[0].id)),\n SupplierDisplayName = coalesce(tostring(SupplierDisplayName), tostring(Suppliers[0].display_name)),\n AssetName = iff(isnotempty(AssetHostname), AssetHostname, AssetIp),\n IsOpen = tolower(Status) == \"open\",\n SeverityRank = case(\n toupper(Severity) == \"CRITICAL\", 4,\n toupper(Severity) == \"HIGH\", 3,\n toupper(Severity) == \"MEDIUM\", 2,\n toupper(Severity) == \"LOW\", 1,\n 0)\n | project\n TimeGenerated,\n IngestedAt,\n EventId,\n InsertedAt,\n OccurredAt,\n ChangeType,\n RiskInstanceId,\n SupplyChainId,\n RiskTypeId,\n CveId,\n RiskCategory,\n RiskName,\n Description,\n Remediations,\n Severity,\n SeverityRank,\n Score,\n Confidence,\n Status,\n IsOpen,\n ResolveTime,\n ResolveReason,\n SupplierId,\n SupplierDisplayName,\n Suppliers,\n AssetType,\n AssetName,\n AssetIp,\n AssetHostname,\n AssetPort,\n ServiceTransportProtocol,\n ServicePort,\n ServiceProtocol,\n Tags,\n RapidResponse,\n Evidence,\n FirstDetectedAt,\n LastDetectedAt,\n CreatedAt,\n PlatformUrl,\n EventVendor,\n EventProduct\n};\nCensysRiskEvents_view\n",
+ "functionParameters": "",
+ "version": 2,
+ "tags": [
+ {
+ "name": "description",
+ "value": ""
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Parser-', last(split(variables('parserObject1')._parserId1,'/'))))]",
+ "dependsOn": [
+ "[variables('parserObject1')._parserId1]"
+ ],
+ "properties": {
+ "parentId": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('workspace'), 'CensysRiskEvents')]",
+ "contentId": "[variables('parserObject1').parserContentId1]",
+ "kind": "Parser",
+ "version": "[variables('parserObject1').parserVersion1]",
+ "source": {
+ "name": "Censys Supplier Risk",
+ "kind": "Solution",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ }
+ }
+ }
+ ]
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('parserObject1').parserContentId1]",
+ "contentKind": "Parser",
+ "displayName": "Parser for CensysRiskEvents",
+ "contentProductId": "[concat(take(variables('_solutionId'),50),'-','pr','-', uniqueString(concat(variables('_solutionId'),'-','Parser','-',variables('parserObject1').parserContentId1,'-', '1.0.0')))]",
+ "id": "[concat(take(variables('_solutionId'),50),'-','pr','-', uniqueString(concat(variables('_solutionId'),'-','Parser','-',variables('parserObject1').parserContentId1,'-', '1.0.0')))]",
+ "version": "[variables('parserObject1').parserVersion1]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/savedSearches",
+ "apiVersion": "2025-07-01",
+ "name": "[variables('parserObject1')._parserName1]",
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "eTag": "*",
+ "displayName": "Parser for CensysRiskEvents",
+ "category": "Microsoft Sentinel Parser",
+ "functionAlias": "CensysRiskEvents",
+ "query": "let CensysRiskEvents_view = view () {\n // The empty datatable keeps the parser working (returning no rows) when the table does not exist yet\n union isfuzzy=true\n (datatable(\n TimeGenerated:datetime,\n EventId:string,\n InsertedAt:datetime,\n OccurredAt:datetime,\n ChangeType:string,\n RiskInstanceId:string,\n SupplyChainId:string,\n AssetType:string,\n RiskTypeId:string,\n CveId:string,\n RiskCategory:string,\n RiskName:string,\n Description:string,\n Remediations:dynamic,\n Severity:string,\n Score:real,\n Confidence:real,\n Status:string,\n ResolveTime:datetime,\n ResolveReason:string,\n SupplierId:string,\n SupplierDisplayName:string,\n Suppliers:dynamic,\n AssetIp:string,\n AssetHostname:string,\n AssetPort:int,\n ServiceTransportProtocol:string,\n ServicePort:int,\n ServiceProtocol:string,\n Tags:dynamic,\n RapidResponse:bool,\n Evidence:string,\n FirstDetectedAt:datetime,\n LastDetectedAt:datetime,\n CreatedAt:datetime,\n PlatformUrl:string,\n IngestedAt:datetime\n )[]),\n (CensysRiskEvents_CL | extend IngestedAt = ingestion_time())\n // Treat every field as optional: a column that is missing from the table becomes empty instead of failing the query\n | extend\n TimeGenerated = column_ifexists('TimeGenerated', datetime(null)),\n EventId = column_ifexists('EventId', ''),\n InsertedAt = column_ifexists('InsertedAt', datetime(null)),\n OccurredAt = column_ifexists('OccurredAt', datetime(null)),\n ChangeType = column_ifexists('ChangeType', ''),\n RiskInstanceId = column_ifexists('RiskInstanceId', ''),\n SupplyChainId = column_ifexists('SupplyChainId', ''),\n AssetType = column_ifexists('AssetType', ''),\n RiskTypeId = column_ifexists('RiskTypeId', ''),\n CveId = column_ifexists('CveId', ''),\n RiskCategory = column_ifexists('RiskCategory', ''),\n RiskName = column_ifexists('RiskName', ''),\n Description = column_ifexists('Description', ''),\n Remediations = column_ifexists('Remediations', dynamic(null)),\n Severity = column_ifexists('Severity', ''),\n Score = column_ifexists('Score', real(null)),\n Confidence = column_ifexists('Confidence', real(null)),\n Status = column_ifexists('Status', ''),\n ResolveTime = column_ifexists('ResolveTime', datetime(null)),\n ResolveReason = column_ifexists('ResolveReason', ''),\n SupplierId = column_ifexists('SupplierId', ''),\n SupplierDisplayName = column_ifexists('SupplierDisplayName', ''),\n Suppliers = column_ifexists('Suppliers', dynamic(null)),\n AssetIp = column_ifexists('AssetIp', ''),\n AssetHostname = column_ifexists('AssetHostname', ''),\n AssetPort = column_ifexists('AssetPort', int(null)),\n ServiceTransportProtocol = column_ifexists('ServiceTransportProtocol', ''),\n ServicePort = column_ifexists('ServicePort', int(null)),\n ServiceProtocol = column_ifexists('ServiceProtocol', ''),\n Tags = column_ifexists('Tags', dynamic(null)),\n RapidResponse = column_ifexists('RapidResponse', bool(null)),\n Evidence = column_ifexists('Evidence', ''),\n FirstDetectedAt = column_ifexists('FirstDetectedAt', datetime(null)),\n LastDetectedAt = column_ifexists('LastDetectedAt', datetime(null)),\n CreatedAt = column_ifexists('CreatedAt', datetime(null)),\n PlatformUrl = column_ifexists('PlatformUrl', '')\n // Delivery is at-least-once, so keep one row per event: the first copy that was ingested\n | summarize arg_min(IngestedAt, *) by EventId\n | extend\n EventVendor = \"Censys\",\n EventProduct = \"Censys Supplier Risk\",\n ChangeType = tolower(ChangeType),\n Status = tolower(Status),\n Severity = toupper(Severity),\n SupplierId = coalesce(tostring(SupplierId), tostring(Suppliers[0].id)),\n SupplierDisplayName = coalesce(tostring(SupplierDisplayName), tostring(Suppliers[0].display_name)),\n AssetName = iff(isnotempty(AssetHostname), AssetHostname, AssetIp),\n IsOpen = tolower(Status) == \"open\",\n SeverityRank = case(\n toupper(Severity) == \"CRITICAL\", 4,\n toupper(Severity) == \"HIGH\", 3,\n toupper(Severity) == \"MEDIUM\", 2,\n toupper(Severity) == \"LOW\", 1,\n 0)\n | project\n TimeGenerated,\n IngestedAt,\n EventId,\n InsertedAt,\n OccurredAt,\n ChangeType,\n RiskInstanceId,\n SupplyChainId,\n RiskTypeId,\n CveId,\n RiskCategory,\n RiskName,\n Description,\n Remediations,\n Severity,\n SeverityRank,\n Score,\n Confidence,\n Status,\n IsOpen,\n ResolveTime,\n ResolveReason,\n SupplierId,\n SupplierDisplayName,\n Suppliers,\n AssetType,\n AssetName,\n AssetIp,\n AssetHostname,\n AssetPort,\n ServiceTransportProtocol,\n ServicePort,\n ServiceProtocol,\n Tags,\n RapidResponse,\n Evidence,\n FirstDetectedAt,\n LastDetectedAt,\n CreatedAt,\n PlatformUrl,\n EventVendor,\n EventProduct\n};\nCensysRiskEvents_view\n",
+ "functionParameters": "",
+ "version": 2,
+ "tags": [
+ {
+ "name": "description",
+ "value": ""
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "location": "[parameters('workspace-location')]",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Parser-', last(split(variables('parserObject1')._parserId1,'/'))))]",
+ "dependsOn": [
+ "[variables('parserObject1')._parserId1]"
+ ],
+ "properties": {
+ "parentId": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('workspace'), 'CensysRiskEvents')]",
+ "contentId": "[variables('parserObject1').parserContentId1]",
+ "kind": "Parser",
+ "version": "[variables('parserObject1').parserVersion1]",
+ "source": {
+ "kind": "Solution",
+ "name": "Censys Supplier Risk",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('parserObject2').parserTemplateSpecName2]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "CensysRisks Data Parser with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('parserObject2').parserVersion2]",
+ "parameters": {},
+ "variables": {},
+ "resources": [
+ {
+ "name": "[variables('parserObject2')._parserName2]",
+ "apiVersion": "2025-07-01",
+ "type": "Microsoft.OperationalInsights/workspaces/savedSearches",
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "eTag": "*",
+ "displayName": "Parser for Censys risks",
+ "category": "Microsoft Sentinel Parser",
+ "functionAlias": "CensysRisks",
+ "query": "// Current state of every supplier risk: the latest event of each risk instance, whether the risk is open or closed\nlet CensysRisks_view = view () {\n CensysRiskEvents\n | summarize arg_max(InsertedAt, *) by RiskInstanceId\n};\nCensysRisks_view\n",
+ "functionParameters": "",
+ "version": 2,
+ "tags": [
+ {
+ "name": "description",
+ "value": ""
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Parser-', last(split(variables('parserObject2')._parserId2,'/'))))]",
+ "dependsOn": [
+ "[variables('parserObject2')._parserId2]"
+ ],
+ "properties": {
+ "parentId": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('workspace'), 'CensysRisks')]",
+ "contentId": "[variables('parserObject2').parserContentId2]",
+ "kind": "Parser",
+ "version": "[variables('parserObject2').parserVersion2]",
+ "source": {
+ "name": "Censys Supplier Risk",
+ "kind": "Solution",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ }
+ }
+ }
+ ]
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('parserObject2').parserContentId2]",
+ "contentKind": "Parser",
+ "displayName": "Parser for Censys risks",
+ "contentProductId": "[concat(take(variables('_solutionId'),50),'-','pr','-', uniqueString(concat(variables('_solutionId'),'-','Parser','-',variables('parserObject2').parserContentId2,'-', '1.0.0')))]",
+ "id": "[concat(take(variables('_solutionId'),50),'-','pr','-', uniqueString(concat(variables('_solutionId'),'-','Parser','-',variables('parserObject2').parserContentId2,'-', '1.0.0')))]",
+ "version": "[variables('parserObject2').parserVersion2]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/savedSearches",
+ "apiVersion": "2025-07-01",
+ "name": "[variables('parserObject2')._parserName2]",
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "eTag": "*",
+ "displayName": "Parser for Censys risks",
+ "category": "Microsoft Sentinel Parser",
+ "functionAlias": "CensysRisks",
+ "query": "// Current state of every supplier risk: the latest event of each risk instance, whether the risk is open or closed\nlet CensysRisks_view = view () {\n CensysRiskEvents\n | summarize arg_max(InsertedAt, *) by RiskInstanceId\n};\nCensysRisks_view\n",
+ "functionParameters": "",
+ "version": 2,
+ "tags": [
+ {
+ "name": "description",
+ "value": ""
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "location": "[parameters('workspace-location')]",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Parser-', last(split(variables('parserObject2')._parserId2,'/'))))]",
+ "dependsOn": [
+ "[variables('parserObject2')._parserId2]"
+ ],
+ "properties": {
+ "parentId": "[resourceId('Microsoft.OperationalInsights/workspaces/savedSearches', parameters('workspace'), 'CensysRisks')]",
+ "contentId": "[variables('parserObject2').parserContentId2]",
+ "kind": "Parser",
+ "version": "[variables('parserObject2').parserVersion2]",
+ "source": {
+ "kind": "Solution",
+ "name": "Censys Supplier Risk",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('workbookTemplateSpecName1')]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "CensysSupplierRisk Workbook with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('workbookVersion1')]",
+ "parameters": {},
+ "variables": {},
+ "resources": [
+ {
+ "type": "Microsoft.Insights/workbooks",
+ "name": "[variables('workbookContentId1')]",
+ "location": "[parameters('workspace-location')]",
+ "kind": "shared",
+ "apiVersion": "2021-08-01",
+ "metadata": {
+ "description": "Gain insight into supplier risk collected from Censys Supplier Risk: open risks by severity, risk events by category, opened and closed risks over time, top risks by open risk count, supplier risk ranking and rapid response events, with details one selection away."
+ },
+ "properties": {
+ "displayName": "[parameters('workbook1-name')]",
+ "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Censys Supplier Risk Overview\\nOverview dashboard for Censys Supplier Risk risk events. All panels count risk events in the selected **Time Range**, one per event id, and every filter applies to every panel. Open counts use the status that each event carries, so a risk opened earlier than the Time Range with no event inside it will not appear.\"},\"name\":\"0a3c227f-21b3-55b9-b3d4-6c6d84922765\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"3980b3ca-3e47-5259-ae54-e9cf79f7aa97\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"value\":{\"durationMs\":604800000},\"typeSettings\":{\"selectableValues\":[{\"durationMs\":86400000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2592000000}],\"allowCustom\":true}},{\"id\":\"531ff72e-136a-5683-8495-98755dec1185\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Severity\",\"label\":\"Severity\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"timeContext\":{\"durationMs\":7776000000},\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"defaultValue\":\"value::all\",\"value\":[\"value::all\"],\"jsonData\":\"[{\\\"value\\\": \\\"LOW\\\", \\\"label\\\": \\\"Low\\\"}, {\\\"value\\\": \\\"MEDIUM\\\", \\\"label\\\": \\\"Medium\\\"}, {\\\"value\\\": \\\"HIGH\\\", \\\"label\\\": \\\"High\\\"}, {\\\"value\\\": \\\"CRITICAL\\\", \\\"label\\\": \\\"Critical\\\"}]\"},{\"id\":\"408ed010-0624-5802-be49-0b16ce3b0b55\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Supplier\",\"label\":\"Supplier\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"CensysRiskEvents\\n| where isnotempty(SupplierDisplayName)\\n| summarize by SupplierDisplayName\\n| project value = SupplierDisplayName, label = SupplierDisplayName\\n| order by label asc\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"timeContext\":{\"durationMs\":7776000000},\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"defaultValue\":\"value::all\",\"value\":[\"value::all\"]},{\"id\":\"981f1274-a745-5685-9c0a-938042ba7b06\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"RiskCategory\",\"label\":\"Risk Category\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"timeContext\":{\"durationMs\":7776000000},\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"defaultValue\":\"value::all\",\"value\":[\"value::all\"],\"jsonData\":\"[{\\\"value\\\": \\\"VULNERABILITY\\\", \\\"label\\\": \\\"Vulnerability\\\"}, {\\\"value\\\": \\\"MISCONFIGURATION\\\", \\\"label\\\": \\\"Misconfiguration\\\"}, {\\\"value\\\": \\\"EXPOSURE\\\", \\\"label\\\": \\\"Exposure\\\"}, {\\\"value\\\": \\\"THREAT\\\", \\\"label\\\": \\\"Threat\\\"}]\"},{\"id\":\"2a6c8d3e-7b1f-5f0e-9a44-3c2d9e8b1a77\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Status\",\"label\":\"Status\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"timeContext\":{\"durationMs\":7776000000},\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"defaultValue\":\"value::all\",\"value\":[\"value::all\"],\"jsonData\":\"[{\\\"value\\\": \\\"open\\\", \\\"label\\\": \\\"Open\\\"}, {\\\"value\\\": \\\"closed\\\", \\\"label\\\": \\\"Closed\\\"}]\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"d242da61-ec90-533c-946e-20debc8b18b2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"b0aac801-3580-5807-8df7-7a5e49f3c503\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"P1Severity\",\"type\":1,\"isRequired\":false,\"isHiddenWhenLocked\":true,\"value\":\"None\"},{\"id\":\"9952c2a7-5e8b-5f2f-a19d-dc9d340ca4fe\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"P2Date\",\"type\":1,\"isRequired\":false,\"isHiddenWhenLocked\":true,\"value\":\"None\"},{\"id\":\"c97c3004-6c39-55d2-8b8a-a3c41ef751f5\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"P2Status\",\"type\":1,\"isRequired\":false,\"isHiddenWhenLocked\":true,\"value\":\"None\"},{\"id\":\"c070c984-7e2c-568c-8d8f-b6ca8ffaffd8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"P3Category\",\"type\":1,\"isRequired\":false,\"isHiddenWhenLocked\":true,\"value\":\"None\"},{\"id\":\"b5d774c8-0c6d-5ffa-8e74-193a9f8dcd6e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"P4Risk\",\"type\":1,\"isRequired\":false,\"isHiddenWhenLocked\":true,\"value\":\"None\"},{\"id\":\"6179b664-6feb-5562-9189-6351e12c6713\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"P5Supplier\",\"type\":1,\"isRequired\":false,\"isHiddenWhenLocked\":true,\"value\":\"None\"},{\"id\":\"c728699c-76e3-5dd5-b847-ed58b40037b7\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"P6Event\",\"type\":1,\"isRequired\":false,\"isHiddenWhenLocked\":true,\"value\":\"None\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"383c127f-e50f-5a35-8def-1a9f9987899b\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where Status == \\\"open\\\"\\n| summarize [\\\"count\\\"] = count() by severity = Severity\\n| order by severity asc\",\"size\":3,\"title\":\"Open Risk Count by Severity\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"piechart\",\"exportFieldName\":\"series\",\"exportParameterName\":\"P1Severity\",\"exportDefaultValue\":\"None\",\"chartSettings\":{\"group\":\"severity\",\"seriesLabelSettings\":[{\"seriesName\":\"CRITICAL\",\"color\":\"redBright\"},{\"seriesName\":\"HIGH\",\"color\":\"orange\"},{\"seriesName\":\"MEDIUM\",\"color\":\"yellow\"},{\"seriesName\":\"LOW\",\"color\":\"blue\"},{\"seriesName\":\"UNSPECIFIED\",\"color\":\"gray\"}],\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"decimal\",\"useGrouping\":true,\"maximumSignificantDigits\":15}}}}},\"name\":\"1b6bdd62-e666-568e-ad83-e6466e7d0772\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| summarize [\\\"count\\\"] = count() by category = RiskCategory\\n| order by category asc\",\"size\":3,\"title\":\"Risk Events by Category\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"piechart\",\"chartSettings\":{\"group\":\"category\",\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"decimal\",\"useGrouping\":true,\"maximumSignificantDigits\":15}}}},\"exportFieldName\":\"series\",\"exportParameterName\":\"P3Category\",\"exportDefaultValue\":\"None\"},\"name\":\"ab26b647-f261-532b-937b-fc06058cd3f2\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"💡 _Click on a slice in the above 'Open Risk Count by Severity' chart to view more details_\\n\\n📝 _Counts open risk events by severity, one per event id._\"},\"name\":\"3408c688-c583-5070-b687-a6b6c057b141\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"💡 _Click on a slice in the above 'Risk Events by Category' chart to view more details_\\n\\n📝 _Counts risk events by category, one per event id._\"},\"name\":\"a91116af-f6ff-5304-8e46-14afb20ca027\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where Status == \\\"open\\\"\\n| where Severity == '{P1Severity}'\\n| order by InsertedAt desc\\n| project Time = InsertedAt, id = EventId, [\\\"Risk Name\\\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\\\"Platform URL\\\"] = PlatformUrl\\n| take 10000\",\"size\":0,\"title\":\"Events - {P1Severity}\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Time\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"id\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}},{\"columnMatch\":\"Risk Name\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"27%\"}},{\"columnMatch\":\"Severity\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"8%\"}},{\"columnMatch\":\"Supplier\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"Platform URL\",\"formatter\":7,\"formatOptions\":{\"linkTarget\":\"Url\",\"linkLabel\":\"Open in Censys\",\"customColumnWidthSetting\":\"15%\"}}],\"filter\":true,\"rowLimit\":10000}},\"name\":\"b4afc41f-40bd-50d8-a9f6-66f8d2b30133\",\"customWidth\":\"100\",\"conditionalVisibility\":{\"parameterName\":\"P1Severity\",\"comparison\":\"isNotEqualTo\",\"value\":\"None\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where RiskCategory == '{P3Category}'\\n| order by InsertedAt desc\\n| project Time = InsertedAt, id = EventId, [\\\"Risk Name\\\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\\\"Platform URL\\\"] = PlatformUrl\\n| take 10000\",\"size\":0,\"title\":\"Events - {P3Category}\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Time\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"id\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}},{\"columnMatch\":\"Risk Name\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"27%\"}},{\"columnMatch\":\"Severity\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"8%\"}},{\"columnMatch\":\"Supplier\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"Platform URL\",\"formatter\":7,\"formatOptions\":{\"linkTarget\":\"Url\",\"linkLabel\":\"Open in Censys\",\"customColumnWidthSetting\":\"15%\"}}],\"filter\":true,\"rowLimit\":10000}},\"name\":\"70e7cbe8-0bc7-571e-81e4-7d792f321c2b\",\"customWidth\":\"100\",\"conditionalVisibility\":{\"parameterName\":\"P3Category\",\"comparison\":\"isNotEqualTo\",\"value\":\"None\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let TotalHours = (todatetime({TimeRange:end}) - todatetime({TimeRange:start})) / 1h;\\n// Time span picked from the Time Range (at most about 100 points): 30 minutes, 1 hour or 1 day\\nlet Span = iff(TotalHours <= 50, 30m, iff(TotalHours <= 100, 1h, 1d));\\nlet Buckets = range Bucket from bin(todatetime({TimeRange:start}), Span) to todatetime({TimeRange:end}) step Span | extend k = 1;\\nlet Statuses = datatable(Series: string)[\\\"Opened\\\", \\\"Closed\\\"] | extend k = 1;\\nlet Counts = CensysRiskEvents\\n | where OccurredAt {TimeRange}\\n | where ('*' in ({Status}) or Status in ({Status}))\\n | where ('*' in ({Severity}) or Severity in ({Severity}))\\n | where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n | where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n | extend Series = case(Status == \\\"open\\\", \\\"Opened\\\", Status == \\\"closed\\\", \\\"Closed\\\", Status)\\n | summarize Total = count() by Bucket = bin(OccurredAt, Span), Series;\\nBuckets\\n| join kind=inner Statuses on k\\n| join kind=leftouter Counts on Bucket, Series\\n| project Time = iff(Span >= 1d, format_datetime(Bucket, 'yyyy-MM-dd'), format_datetime(Bucket, 'yyyy-MM-dd HH:mm')), Series, [\\\"Risk Count\\\"] = coalesce(Total, 0)\\n| order by Time asc, Series asc\",\"size\":0,\"title\":\"Opened vs. Closed Risks\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"timechart\",\"chartSettings\":{\"xAxis\":\"Time\",\"group\":\"Series\",\"xSettings\":{\"label\":\"Time\"},\"ySettings\":{\"label\":\"Risk Count\"}},\"exportedParameters\":[{\"fieldName\":\"x\",\"parameterName\":\"P2Date\",\"parameterType\":1,\"defaultValue\":\"None\"},{\"fieldName\":\"series\",\"parameterName\":\"P2Status\",\"parameterType\":1,\"defaultValue\":\"None\"}]},\"name\":\"4668c9cb-64a5-581d-b461-d8854d0f2409\",\"customWidth\":\"100\"},{\"type\":1,\"content\":{\"json\":\"💡 _Click on a data point in the above 'Opened vs. Closed Risks' chart to view more details_\\n\\n📝 _Counts risk events per day by their status (Opened or Closed), by when they occurred._\"},\"name\":\"c2beca44-a16d-5fa8-821b-d7aea664ec9f\",\"customWidth\":\"100\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let TotalHours = (todatetime({TimeRange:end}) - todatetime({TimeRange:start})) / 1h;\\n// Time span picked from the Time Range (at most about 100 points): 30 minutes, 1 hour or 1 day\\nlet Span = iff(TotalHours <= 50, 30m, iff(TotalHours <= 100, 1h, 1d));\\nCensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where Status == iff('{P2Status}' == \\\"Opened\\\", \\\"open\\\", \\\"closed\\\")\\n| where OccurredAt >= todatetime(substring('{P2Date}', 0, 16)) and OccurredAt < todatetime(substring('{P2Date}', 0, 16)) + Span\\n| order by InsertedAt desc\\n| project Time = InsertedAt, id = EventId, [\\\"Risk Name\\\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\\\"Platform URL\\\"] = PlatformUrl\\n| take 10000\",\"size\":0,\"title\":\"Events - {P2Status} on {P2Date}\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Time\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"id\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}},{\"columnMatch\":\"Risk Name\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"27%\"}},{\"columnMatch\":\"Severity\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"8%\"}},{\"columnMatch\":\"Supplier\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"Platform URL\",\"formatter\":7,\"formatOptions\":{\"linkTarget\":\"Url\",\"linkLabel\":\"Open in Censys\",\"customColumnWidthSetting\":\"15%\"}}],\"filter\":true,\"rowLimit\":10000}},\"name\":\"50857b22-679b-5891-bd85-96262bd29d80\",\"customWidth\":\"100\",\"conditionalVisibilities\":[{\"parameterName\":\"P2Date\",\"comparison\":\"isNotEqualTo\",\"value\":\"None\"},{\"parameterName\":\"P2Status\",\"comparison\":\"isNotEqualTo\",\"value\":\"None\"}]},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where Status == \\\"open\\\" and isnotempty(RiskName)\\n| summarize OpenRisks = count() by [\\\"Risk Name\\\"] = RiskName\\n| order by OpenRisks desc\\n| take 10\",\"size\":0,\"title\":\"Top 10 Risks by Open Risk Count\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Risk Name\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"80%\"}},{\"columnMatch\":\"OpenRisks\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}}],\"filter\":true,\"rowLimit\":10000},\"exportFieldName\":\"Risk Name\",\"exportParameterName\":\"P4Risk\",\"exportDefaultValue\":\"None\"},\"name\":\"4c44d1fa-a2d9-55b0-98e5-8030350c5f9d\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where Status == \\\"open\\\"\\n| summarize OpenRisks = count() by Supplier = SupplierDisplayName\\n| order by OpenRisks desc\",\"size\":0,\"title\":\"Supplier Risk Ranking\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Supplier\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"80%\"}},{\"columnMatch\":\"OpenRisks\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}}],\"filter\":true,\"rowLimit\":10000},\"exportFieldName\":\"Supplier\",\"exportParameterName\":\"P5Supplier\",\"exportDefaultValue\":\"None\"},\"name\":\"a5821846-0945-540c-9c97-849cad7afa8b\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"💡 _Click on a row in the above 'Top 10 Risks by Open Risk Count' grid to view more details_\\n\\n📝 _The 10 risk names with the most open risk events._\"},\"name\":\"cdf92177-e783-5d83-97d1-37d122085500\",\"customWidth\":\"50\"},{\"type\":1,\"content\":{\"json\":\"💡 _Click on a row in the above 'Supplier Risk Ranking' grid to view more details_\\n\\n📝 _Suppliers ranked by their number of open risk events._\"},\"name\":\"4e9590ae-6386-5f24-9ca0-f3a096994918\",\"customWidth\":\"50\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where Status == \\\"open\\\"\\n| where RiskName == '{P4Risk}'\\n| order by InsertedAt desc\\n| project Time = InsertedAt, id = EventId, [\\\"Risk Name\\\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\\\"Platform URL\\\"] = PlatformUrl\\n| take 10000\",\"size\":0,\"title\":\"Events - {P4Risk}\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Time\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"id\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}},{\"columnMatch\":\"Risk Name\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"27%\"}},{\"columnMatch\":\"Severity\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"8%\"}},{\"columnMatch\":\"Supplier\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"Platform URL\",\"formatter\":7,\"formatOptions\":{\"linkTarget\":\"Url\",\"linkLabel\":\"Open in Censys\",\"customColumnWidthSetting\":\"15%\"}}],\"filter\":true,\"rowLimit\":10000}},\"name\":\"22004c2d-0bf3-5e3d-9870-78807252bd52\",\"customWidth\":\"100\",\"conditionalVisibility\":{\"parameterName\":\"P4Risk\",\"comparison\":\"isNotEqualTo\",\"value\":\"None\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where Status == \\\"open\\\"\\n| where SupplierDisplayName == '{P5Supplier}'\\n| order by InsertedAt desc\\n| project Time = InsertedAt, id = EventId, [\\\"Risk Name\\\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\\\"Platform URL\\\"] = PlatformUrl\\n| take 10000\",\"size\":0,\"title\":\"Events - {P5Supplier}\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Time\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"id\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}},{\"columnMatch\":\"Risk Name\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"27%\"}},{\"columnMatch\":\"Severity\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"8%\"}},{\"columnMatch\":\"Supplier\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"Platform URL\",\"formatter\":7,\"formatOptions\":{\"linkTarget\":\"Url\",\"linkLabel\":\"Open in Censys\",\"customColumnWidthSetting\":\"15%\"}}],\"filter\":true,\"rowLimit\":10000}},\"name\":\"67930e57-64b3-52a9-b227-bebac1f40c88\",\"customWidth\":\"100\",\"conditionalVisibility\":{\"parameterName\":\"P5Supplier\",\"comparison\":\"isNotEqualTo\",\"value\":\"None\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where RapidResponse == true\\n| order by InsertedAt desc\\n| project Time = InsertedAt, id = EventId, [\\\"Risk Name\\\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\\\"Platform URL\\\"] = PlatformUrl\\n| take 10000\",\"size\":0,\"title\":\"Rapid Response Events\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Time\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"id\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}},{\"columnMatch\":\"Risk Name\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"27%\"}},{\"columnMatch\":\"Severity\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"8%\"}},{\"columnMatch\":\"Supplier\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"15%\"}},{\"columnMatch\":\"Platform URL\",\"formatter\":7,\"formatOptions\":{\"linkTarget\":\"Url\",\"linkLabel\":\"Open in Censys\",\"customColumnWidthSetting\":\"15%\"}}],\"filter\":true,\"rowLimit\":10000},\"exportFieldName\":\"id\",\"exportParameterName\":\"P6Event\",\"exportDefaultValue\":\"None\"},\"name\":\"d29db818-909d-5958-8ef4-89e50ee80542\",\"customWidth\":\"100\"},{\"type\":1,\"content\":{\"json\":\"💡 _Click on a row in the above 'Rapid Response Events' grid to view more details_\\n\\n📝 _Risk events flagged for rapid response (actively exploited), newest first._\"},\"name\":\"59f191f6-06f8-5ad8-b72b-8394af1f9a2a\",\"customWidth\":\"100\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"CensysRiskEvents\\n| where OccurredAt {TimeRange}\\n| where ('*' in ({Status}) or Status in ({Status}))\\n| where ('*' in ({Severity}) or Severity in ({Severity}))\\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\\n| where EventId == '{P6Event}'\\n| project Time = InsertedAt, id = EventId, [\\\"Change Type\\\"] = ChangeType, Status, [\\\"Risk Name\\\"] = RiskName, Severity, Supplier = SupplierDisplayName, Asset = AssetName, [\\\"Service Port\\\"] = ServicePort, [\\\"Resolve Reason\\\"] = ResolveReason, [\\\"Platform URL\\\"] = PlatformUrl\\n| take 10000\",\"size\":0,\"title\":\"Event - {P6Event}\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"showRefreshButton\":true,\"showAnalytics\":true,\"showExportToExcel\":true,\"noDataMessage\":\"No data found for the selected filters.\",\"timeContext\":{\"durationMs\":7776000000},\"visualization\":\"table\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Time\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"12%\"}},{\"columnMatch\":\"id\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"16%\"}},{\"columnMatch\":\"Change Type\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"8%\"}},{\"columnMatch\":\"Status\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"7%\"}},{\"columnMatch\":\"Risk Name\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"20%\"}},{\"columnMatch\":\"Severity\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"7%\"}},{\"columnMatch\":\"Supplier\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"11%\"}},{\"columnMatch\":\"Asset\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"10%\"}},{\"columnMatch\":\"Service Port\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"4%\"}},{\"columnMatch\":\"Resolve Reason\",\"formatter\":0,\"formatOptions\":{\"customColumnWidthSetting\":\"5%\"}},{\"columnMatch\":\"Platform URL\",\"formatter\":7,\"formatOptions\":{\"linkTarget\":\"Url\",\"linkLabel\":\"Open in Censys\",\"customColumnWidthSetting\":\"10%\"}}],\"filter\":true,\"rowLimit\":10000}},\"name\":\"996c3d06-11cb-52d7-86a5-2a6444795041\",\"customWidth\":\"100\",\"conditionalVisibility\":{\"parameterName\":\"P6Event\",\"comparison\":\"isNotEqualTo\",\"value\":\"None\"}}],\"fromTemplateId\":\"sentinel-CensysSupplierRisk\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\n",
+ "version": "1.0",
+ "sourceId": "[variables('workspaceResourceId')]",
+ "category": "sentinel"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId1'),'/'))))]",
+ "properties": {
+ "description": "@{workbookKey=CensysSupplierRiskWorkbook; logoFileName=Censys.svg; description=Gain insight into supplier risk collected from Censys Supplier Risk: open risks by severity, risk events by category, opened and closed risks over time, top risks by open risk count, supplier risk ranking and rapid response events, with details one selection away.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.0; title=Censys Supplier Risk; templateRelativePath=CensysSupplierRisk.json; subtitle=; provider=Censys}.description",
+ "parentId": "[variables('workbookId1')]",
+ "contentId": "[variables('_workbookContentId1')]",
+ "kind": "Workbook",
+ "version": "[variables('workbookVersion1')]",
+ "source": {
+ "kind": "Solution",
+ "name": "Censys Supplier Risk",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ },
+ "dependencies": {
+ "operator": "AND",
+ "criteria": [
+ {
+ "contentId": "CensysRiskEvents_CL",
+ "kind": "DataType"
+ },
+ {
+ "contentId": "CensysSRRiskEventsFunction",
+ "kind": "DataConnector"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('_workbookContentId1')]",
+ "contentKind": "Workbook",
+ "displayName": "[parameters('workbook1-name')]",
+ "contentProductId": "[variables('_workbookcontentProductId1')]",
+ "id": "[variables('_workbookcontentProductId1')]",
+ "version": "[variables('workbookVersion1')]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates",
+ "apiVersion": "2023-04-01-preview",
+ "name": "[variables('analyticRuleObject1').analyticRuleTemplateSpecName1]",
+ "location": "[parameters('workspace-location')]",
+ "dependsOn": [
+ "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]"
+ ],
+ "properties": {
+ "description": "CensysRiskStateChange_AnalyticalRules Analytics Rule with template version 3.0.0",
+ "mainTemplate": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "[variables('analyticRuleObject1').analyticRuleVersion1]",
+ "parameters": {},
+ "variables": {},
+ "resources": [
+ {
+ "type": "Microsoft.SecurityInsights/AlertRuleTemplates",
+ "name": "[variables('analyticRuleObject1')._analyticRulecontentId1]",
+ "apiVersion": "2023-02-01-preview",
+ "kind": "Scheduled",
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "description": "Creates one incident per supplier risk instance that is opened, or reopened, with CRITICAL or HIGH severity. Alerts are grouped by RiskInstanceId, so further events of the same risk instance within 24 hours are added to the same incident while it is open.\nAlert severity follows the Censys severity: CRITICAL and HIGH map to High, MEDIUM to Medium and LOW to Low. Resolved events follow the same mapping, and any other severity is Informational.\nBy default the rule alerts only on new CRITICAL and HIGH risks. The query holds commented lines to follow the whole life of a risk instead (reopened events of any severity and resolved events), see the comments in the query.",
+ "displayName": "Censys SR - Risk State Change",
+ "enabled": false,
+ "query": "// New events are selected by the time they reached Sentinel (IngestedAt), not by the Censys insert time,\n// so a delay between Censys and the connector can never make an event fall between two runs.\n// Each run covers 60 minutes that end 5 minutes ago, and consecutive runs are contiguous, so no event is alerted twice.\n//\n// OPTIONAL - follow the whole life of a risk instance (reopened events of any severity, and resolved events of risks that\n// were alerted before). To use it, uncomment the two \"let\" lines below, comment out the line \"Default\" in the query, uncomment\n// the line \"Full lifecycle\", and in the rule settings set the query period to 7 days and the incident grouping look-back to 7 days.\n// let Alertable = materialize(CensysRiskEvents | where ChangeType == \"reopened\" or (ChangeType == \"opened\" and Severity in (\"CRITICAL\", \"HIGH\")));\n// let TrackedRisks = Alertable | distinct RiskInstanceId;\nCensysRiskEvents\n| where IngestedAt between (ago(65m) .. ago(5m))\n// Default: opened or reopened risks with CRITICAL or HIGH severity\n| where ChangeType in (\"opened\", \"reopened\") and Severity in (\"CRITICAL\", \"HIGH\")\n// Full lifecycle (needs the optional \"let\" lines above):\n// | where (ChangeType == \"reopened\" or (ChangeType == \"opened\" and Severity in (\"CRITICAL\", \"HIGH\"))) or (ChangeType == \"resolved\" and RiskInstanceId in (TrackedRisks))\n//\n// Optional filters: remove the leading // of a line to use it. Each line narrows the alerts further, and several lines can be combined.\n// Do not remove or change the IngestedAt window above, it keeps every event alerted exactly once.\n// | where Confidence >= 0.8 // only findings with a confidence of 80% or more\n// | where Severity == \"CRITICAL\" // only CRITICAL risks (Censys severities: CRITICAL, HIGH, MEDIUM, LOW)\n// | where ChangeType == \"opened\" // only newly opened risks, skip reopened risks\n// | where RapidResponse == true // only actively exploited (rapid response) risks\n// | where RiskCategory == \"VULNERABILITY\" // VULNERABILITY, MISCONFIGURATION, EXPOSURE or THREAT\n// | where SupplierDisplayName in (\"OpenAI\", \"Harvey\") // only these suppliers\n// | where CveId != \"\" // only risks that have a CVE id\n// | where RiskName has \"Log4Shell\" // only risks whose name contains this text\n// | where AssetType == \"host\" // asset type: host or web\n| extend SentinelSeverity = case(\n Severity in (\"CRITICAL\", \"HIGH\"), \"High\",\n Severity == \"MEDIUM\", \"Medium\",\n Severity == \"LOW\", \"Low\",\n \"Informational\")\n| project TimeGenerated, IngestedAt, EventId, InsertedAt, OccurredAt, ChangeType, RiskInstanceId, Severity, SentinelSeverity,\n RiskCategory, RiskName, Description, CveId, RapidResponse, SupplierDisplayName,\n AssetName, AssetHostname, AssetIp, ServicePort, ServiceProtocol, Evidence, ResolveTime, ResolveReason, PlatformUrl\n",
+ "queryFrequency": "PT1H",
+ "queryPeriod": "P1D",
+ "severity": "Medium",
+ "suppressionDuration": "PT1H",
+ "suppressionEnabled": false,
+ "triggerOperator": "GreaterThan",
+ "triggerThreshold": 0,
+ "status": "Available",
+ "requiredDataConnectors": [
+ {
+ "connectorId": "CensysSRRiskEventsFunction",
+ "dataTypes": [
+ "CensysRiskEvents_CL"
+ ]
+ }
+ ],
+ "tactics": [
+ "InitialAccess"
+ ],
+ "techniques": [
+ "T1195"
+ ],
+ "entityMappings": [
+ {
+ "entityType": "Host",
+ "fieldMappings": [
+ {
+ "columnName": "AssetHostname",
+ "identifier": "HostName"
+ }
+ ]
+ },
+ {
+ "entityType": "IP",
+ "fieldMappings": [
+ {
+ "columnName": "AssetIp",
+ "identifier": "Address"
+ }
+ ]
+ },
+ {
+ "entityType": "URL",
+ "fieldMappings": [
+ {
+ "columnName": "PlatformUrl",
+ "identifier": "Url"
+ }
+ ]
+ }
+ ],
+ "eventGroupingSettings": {
+ "aggregationKind": "AlertPerResult"
+ },
+ "customDetails": {
+ "CensysSeverity": "Severity",
+ "RapidResponse": "RapidResponse",
+ "RiskInstanceId": "[if(true(), 'RiskInstanceId', 'RiskInstanceId')]",
+ "Evidence": "Evidence",
+ "RiskCategory": "RiskCategory",
+ "Supplier": "SupplierDisplayName",
+ "ResolveTime": "ResolveTime",
+ "CveId": "[if(true(), 'CveId', 'CveId')]",
+ "ChangeType": "ChangeType",
+ "ResolveReason": "ResolveReason"
+ },
+ "alertDetailsOverride": {
+ "alertDynamicProperties": [
+ {
+ "alertProperty": "AlertLink",
+ "value": "PlatformUrl"
+ }
+ ],
+ "alertSeverityColumnName": "SentinelSeverity",
+ "alertDisplayNameFormat": "Censys: {{ChangeType}} risk - {{RiskName}} ({{SupplierDisplayName}})",
+ "alertDescriptionFormat": "{{Description}} Asset: {{AssetName}}. Open in Censys: {{PlatformUrl}}"
+ },
+ "incidentConfiguration": {
+ "groupingConfiguration": {
+ "enabled": true,
+ "matchingMethod": "Selected",
+ "lookbackDuration": "P1D",
+ "groupByCustomDetails": [
+ "RiskInstanceId"
+ ],
+ "reopenClosedIncident": false
+ },
+ "createIncident": true
+ }
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/metadata",
+ "apiVersion": "2022-01-01-preview",
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject1').analyticRuleId1,'/'))))]",
+ "properties": {
+ "description": "Censys Supplier Risk Analytics Rule 1",
+ "parentId": "[variables('analyticRuleObject1').analyticRuleId1]",
+ "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]",
+ "kind": "AnalyticsRule",
+ "version": "[variables('analyticRuleObject1').analyticRuleVersion1]",
+ "source": {
+ "kind": "Solution",
+ "name": "Censys Supplier Risk",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ }
+ }
+ }
+ ]
+ },
+ "packageKind": "Solution",
+ "packageVersion": "[variables('_solutionVersion')]",
+ "packageName": "[variables('_solutionName')]",
+ "packageId": "[variables('_solutionId')]",
+ "contentSchemaVersion": "3.0.0",
+ "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]",
+ "contentKind": "AnalyticsRule",
+ "displayName": "Censys SR - Risk State Change",
+ "contentProductId": "[variables('analyticRuleObject1')._analyticRulecontentProductId1]",
+ "id": "[variables('analyticRuleObject1')._analyticRulecontentProductId1]",
+ "version": "[variables('analyticRuleObject1').analyticRuleVersion1]"
+ }
+ },
+ {
+ "type": "Microsoft.OperationalInsights/workspaces/providers/contentPackages",
+ "apiVersion": "2023-04-01-preview",
+ "location": "[parameters('workspace-location')]",
+ "properties": {
+ "version": "3.0.0",
+ "kind": "Solution",
+ "contentSchemaVersion": "3.0.0",
+ "displayName": "Censys Supplier Risk",
+ "publisherDisplayName": "Censys Support",
+ "descriptionHtml": "Note: Please refer to the following before installing the solution:
\n• Review the solution Release Notes
\n• There may be known issues pertaining to this Solution, please refer to them before installing.
\nBring Censys Supplier Risk into Microsoft Sentinel. The solution collects supply chain risk events (risks opened, reopened and resolved for your suppliers) from the Censys platform, and provides a parser, a workbook and an analytic rule that raises incidents for new and reopened supplier risks and keeps resolved events with the incident of the same risk.
\nData Connectors: 1, Parsers: 2, Workbooks: 1, Analytic Rules: 1
\nLearn more about Microsoft Sentinel | Learn more about Solutions
\n",
+ "contentKind": "Solution",
+ "contentProductId": "[variables('_solutioncontentProductId')]",
+ "id": "[variables('_solutioncontentProductId')]",
+ "icon": "
",
+ "contentId": "[variables('_solutionId')]",
+ "parentId": "[variables('_solutionId')]",
+ "source": {
+ "kind": "Solution",
+ "name": "Censys Supplier Risk",
+ "sourceId": "[variables('_solutionId')]"
+ },
+ "author": {
+ "name": "Censys",
+ "email": "[variables('_email')]"
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ },
+ "dependencies": {
+ "operator": "AND",
+ "criteria": [
+ {
+ "kind": "DataConnector",
+ "contentId": "[variables('_dataConnectorContentId1')]",
+ "version": "[variables('dataConnectorVersion1')]"
+ },
+ {
+ "kind": "Parser",
+ "contentId": "[variables('parserObject1').parserContentId1]",
+ "version": "[variables('parserObject1').parserVersion1]"
+ },
+ {
+ "kind": "Parser",
+ "contentId": "[variables('parserObject2').parserContentId2]",
+ "version": "[variables('parserObject2').parserVersion2]"
+ },
+ {
+ "kind": "Workbook",
+ "contentId": "[variables('_workbookContentId1')]",
+ "version": "[variables('workbookVersion1')]"
+ },
+ {
+ "kind": "AnalyticsRule",
+ "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]",
+ "version": "[variables('analyticRuleObject1').analyticRuleVersion1]"
+ }
+ ]
+ },
+ "firstPublishDate": "2026-10-29",
+ "lastPublishDate": "2026-10-29",
+ "providers": [
+ "Censys"
+ ],
+ "categories": {
+ "domains": [
+ "Security - Network",
+ "Security - Cloud Security"
+ ]
+ }
+ },
+ "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/', variables('_solutionId'))]"
+ }
+ ],
+ "outputs": {}
+}
diff --git a/Solutions/Censys Supplier Risk/Package/testParameters.json b/Solutions/Censys Supplier Risk/Package/testParameters.json
new file mode 100644
index 00000000000..855e4aba240
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Package/testParameters.json
@@ -0,0 +1,32 @@
+{
+ "location": {
+ "type": "string",
+ "minLength": 1,
+ "defaultValue": "[resourceGroup().location]",
+ "metadata": {
+ "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace"
+ }
+ },
+ "workspace-location": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]"
+ }
+ },
+ "workspace": {
+ "defaultValue": "",
+ "type": "string",
+ "metadata": {
+ "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup"
+ }
+ },
+ "workbook1-name": {
+ "type": "string",
+ "defaultValue": "Censys Supplier Risk",
+ "minLength": 1,
+ "metadata": {
+ "description": "Name for the workbook"
+ }
+ }
+}
diff --git a/Solutions/Censys Supplier Risk/Parsers/CensysRiskEvents.yaml b/Solutions/Censys Supplier Risk/Parsers/CensysRiskEvents.yaml
new file mode 100644
index 00000000000..7bee4a5733a
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Parsers/CensysRiskEvents.yaml
@@ -0,0 +1,153 @@
+id: 5d2a8c1e-7b34-4f6a-9e0d-3c81a2b4f917
+Function:
+ Title: Parser for CensysRiskEvents
+ Version: "1.0.0"
+ LastUpdated: "2026-10-15"
+Category: Microsoft Sentinel Parser
+FunctionName: CensysRiskEvents
+FunctionAlias: CensysRiskEvents
+FunctionQuery: |
+ let CensysRiskEvents_view = view () {
+ // The empty datatable keeps the parser working (returning no rows) when the table does not exist yet
+ union isfuzzy=true
+ (datatable(
+ TimeGenerated:datetime,
+ EventId:string,
+ InsertedAt:datetime,
+ OccurredAt:datetime,
+ ChangeType:string,
+ RiskInstanceId:string,
+ SupplyChainId:string,
+ AssetType:string,
+ RiskTypeId:string,
+ CveId:string,
+ RiskCategory:string,
+ RiskName:string,
+ Description:string,
+ Remediations:dynamic,
+ Severity:string,
+ Score:real,
+ Confidence:real,
+ Status:string,
+ ResolveTime:datetime,
+ ResolveReason:string,
+ SupplierId:string,
+ SupplierDisplayName:string,
+ Suppliers:dynamic,
+ AssetIp:string,
+ AssetHostname:string,
+ AssetPort:int,
+ ServiceTransportProtocol:string,
+ ServicePort:int,
+ ServiceProtocol:string,
+ Tags:dynamic,
+ RapidResponse:bool,
+ Evidence:string,
+ FirstDetectedAt:datetime,
+ LastDetectedAt:datetime,
+ CreatedAt:datetime,
+ PlatformUrl:string,
+ IngestedAt:datetime
+ )[]),
+ (CensysRiskEvents_CL | extend IngestedAt = ingestion_time())
+ // Treat every field as optional: a column that is missing from the table becomes empty instead of failing the query
+ | extend
+ TimeGenerated = column_ifexists('TimeGenerated', datetime(null)),
+ EventId = column_ifexists('EventId', ''),
+ InsertedAt = column_ifexists('InsertedAt', datetime(null)),
+ OccurredAt = column_ifexists('OccurredAt', datetime(null)),
+ ChangeType = column_ifexists('ChangeType', ''),
+ RiskInstanceId = column_ifexists('RiskInstanceId', ''),
+ SupplyChainId = column_ifexists('SupplyChainId', ''),
+ AssetType = column_ifexists('AssetType', ''),
+ RiskTypeId = column_ifexists('RiskTypeId', ''),
+ CveId = column_ifexists('CveId', ''),
+ RiskCategory = column_ifexists('RiskCategory', ''),
+ RiskName = column_ifexists('RiskName', ''),
+ Description = column_ifexists('Description', ''),
+ Remediations = column_ifexists('Remediations', dynamic(null)),
+ Severity = column_ifexists('Severity', ''),
+ Score = column_ifexists('Score', real(null)),
+ Confidence = column_ifexists('Confidence', real(null)),
+ Status = column_ifexists('Status', ''),
+ ResolveTime = column_ifexists('ResolveTime', datetime(null)),
+ ResolveReason = column_ifexists('ResolveReason', ''),
+ SupplierId = column_ifexists('SupplierId', ''),
+ SupplierDisplayName = column_ifexists('SupplierDisplayName', ''),
+ Suppliers = column_ifexists('Suppliers', dynamic(null)),
+ AssetIp = column_ifexists('AssetIp', ''),
+ AssetHostname = column_ifexists('AssetHostname', ''),
+ AssetPort = column_ifexists('AssetPort', int(null)),
+ ServiceTransportProtocol = column_ifexists('ServiceTransportProtocol', ''),
+ ServicePort = column_ifexists('ServicePort', int(null)),
+ ServiceProtocol = column_ifexists('ServiceProtocol', ''),
+ Tags = column_ifexists('Tags', dynamic(null)),
+ RapidResponse = column_ifexists('RapidResponse', bool(null)),
+ Evidence = column_ifexists('Evidence', ''),
+ FirstDetectedAt = column_ifexists('FirstDetectedAt', datetime(null)),
+ LastDetectedAt = column_ifexists('LastDetectedAt', datetime(null)),
+ CreatedAt = column_ifexists('CreatedAt', datetime(null)),
+ PlatformUrl = column_ifexists('PlatformUrl', '')
+ // Delivery is at-least-once, so keep one row per event: the first copy that was ingested
+ | summarize arg_min(IngestedAt, *) by EventId
+ | extend
+ EventVendor = "Censys",
+ EventProduct = "Censys Supplier Risk",
+ ChangeType = tolower(ChangeType),
+ Status = tolower(Status),
+ Severity = toupper(Severity),
+ SupplierId = coalesce(tostring(SupplierId), tostring(Suppliers[0].id)),
+ SupplierDisplayName = coalesce(tostring(SupplierDisplayName), tostring(Suppliers[0].display_name)),
+ AssetName = iff(isnotempty(AssetHostname), AssetHostname, AssetIp),
+ IsOpen = tolower(Status) == "open",
+ SeverityRank = case(
+ toupper(Severity) == "CRITICAL", 4,
+ toupper(Severity) == "HIGH", 3,
+ toupper(Severity) == "MEDIUM", 2,
+ toupper(Severity) == "LOW", 1,
+ 0)
+ | project
+ TimeGenerated,
+ IngestedAt,
+ EventId,
+ InsertedAt,
+ OccurredAt,
+ ChangeType,
+ RiskInstanceId,
+ SupplyChainId,
+ RiskTypeId,
+ CveId,
+ RiskCategory,
+ RiskName,
+ Description,
+ Remediations,
+ Severity,
+ SeverityRank,
+ Score,
+ Confidence,
+ Status,
+ IsOpen,
+ ResolveTime,
+ ResolveReason,
+ SupplierId,
+ SupplierDisplayName,
+ Suppliers,
+ AssetType,
+ AssetName,
+ AssetIp,
+ AssetHostname,
+ AssetPort,
+ ServiceTransportProtocol,
+ ServicePort,
+ ServiceProtocol,
+ Tags,
+ RapidResponse,
+ Evidence,
+ FirstDetectedAt,
+ LastDetectedAt,
+ CreatedAt,
+ PlatformUrl,
+ EventVendor,
+ EventProduct
+ };
+ CensysRiskEvents_view
diff --git a/Solutions/Censys Supplier Risk/Parsers/CensysRisks.yaml b/Solutions/Censys Supplier Risk/Parsers/CensysRisks.yaml
new file mode 100644
index 00000000000..30043f8a6f8
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Parsers/CensysRisks.yaml
@@ -0,0 +1,15 @@
+id: 821035be-713b-4be9-a568-87b8b0b62f41
+Function:
+ Title: Parser for Censys risks
+ Version: "1.0.0"
+ LastUpdated: "2026-10-21"
+Category: Microsoft Sentinel Parser
+FunctionName: CensysRisks
+FunctionAlias: CensysRisks
+FunctionQuery: |
+ // Current state of every supplier risk: the latest event of each risk instance, whether the risk is open or closed
+ let CensysRisks_view = view () {
+ CensysRiskEvents
+ | summarize arg_max(InsertedAt, *) by RiskInstanceId
+ };
+ CensysRisks_view
diff --git a/Solutions/Censys Supplier Risk/ReleaseNotes.md b/Solutions/Censys Supplier Risk/ReleaseNotes.md
new file mode 100644
index 00000000000..56d45fefe7d
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/ReleaseNotes.md
@@ -0,0 +1,3 @@
+| **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** |
+|-------------|--------------------------------|---------------------------------------------|
+| 3.0.0 | 29-10-2026 | Initial Solution Release |
diff --git a/Solutions/Censys Supplier Risk/SolutionMetadata.json b/Solutions/Censys Supplier Risk/SolutionMetadata.json
new file mode 100644
index 00000000000..ef692c69da8
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/SolutionMetadata.json
@@ -0,0 +1,17 @@
+{
+ "publisherId": "censys",
+ "offerId": "censys_supplier_risk",
+ "firstPublishDate": "2026-10-29",
+ "lastPublishDate": "2026-10-29",
+ "providers": ["Censys"],
+ "categories": {
+ "domains": ["Security - Network", "Security - Cloud Security"],
+ "verticals": []
+ },
+ "support": {
+ "name": "Censys Support",
+ "email": "support@censys.com",
+ "tier": "Partner",
+ "link": "https://docs.censys.com"
+ }
+}
\ No newline at end of file
diff --git a/Solutions/Censys Supplier Risk/Workbooks/CensysSupplierRisk.json b/Solutions/Censys Supplier Risk/Workbooks/CensysSupplierRisk.json
new file mode 100644
index 00000000000..5fd3f6cfdfb
--- /dev/null
+++ b/Solutions/Censys Supplier Risk/Workbooks/CensysSupplierRisk.json
@@ -0,0 +1,1097 @@
+{
+ "version": "Notebook/1.0",
+ "items": [
+ {
+ "type": 1,
+ "content": {
+ "json": "## Censys Supplier Risk Overview\nOverview dashboard for Censys Supplier Risk risk events. All panels count risk events in the selected **Time Range**, one per event id, and every filter applies to every panel. Open counts use the status that each event carries, so a risk opened earlier than the Time Range with no event inside it will not appear."
+ },
+ "name": "0a3c227f-21b3-55b9-b3d4-6c6d84922765"
+ },
+ {
+ "type": 9,
+ "content": {
+ "version": "KqlParameterItem/1.0",
+ "parameters": [
+ {
+ "id": "3980b3ca-3e47-5259-ae54-e9cf79f7aa97",
+ "version": "KqlParameterItem/1.0",
+ "name": "TimeRange",
+ "label": "Time Range",
+ "type": 4,
+ "isRequired": true,
+ "value": {
+ "durationMs": 604800000
+ },
+ "typeSettings": {
+ "selectableValues": [
+ {
+ "durationMs": 86400000
+ },
+ {
+ "durationMs": 259200000
+ },
+ {
+ "durationMs": 604800000
+ },
+ {
+ "durationMs": 1209600000
+ },
+ {
+ "durationMs": 2592000000
+ }
+ ],
+ "allowCustom": true
+ }
+ },
+ {
+ "id": "531ff72e-136a-5683-8495-98755dec1185",
+ "version": "KqlParameterItem/1.0",
+ "name": "Severity",
+ "label": "Severity",
+ "type": 2,
+ "isRequired": true,
+ "multiSelect": true,
+ "quote": "'",
+ "delimiter": ",",
+ "query": "",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "typeSettings": {
+ "additionalResourceOptions": [
+ "value::all"
+ ],
+ "selectAllValue": "*",
+ "showDefault": false
+ },
+ "defaultValue": "value::all",
+ "value": [
+ "value::all"
+ ],
+ "jsonData": "[{\"value\": \"LOW\", \"label\": \"Low\"}, {\"value\": \"MEDIUM\", \"label\": \"Medium\"}, {\"value\": \"HIGH\", \"label\": \"High\"}, {\"value\": \"CRITICAL\", \"label\": \"Critical\"}]"
+ },
+ {
+ "id": "408ed010-0624-5802-be49-0b16ce3b0b55",
+ "version": "KqlParameterItem/1.0",
+ "name": "Supplier",
+ "label": "Supplier",
+ "type": 2,
+ "isRequired": true,
+ "multiSelect": true,
+ "quote": "'",
+ "delimiter": ",",
+ "query": "CensysRiskEvents\n| where isnotempty(SupplierDisplayName)\n| summarize by SupplierDisplayName\n| project value = SupplierDisplayName, label = SupplierDisplayName\n| order by label asc",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "typeSettings": {
+ "additionalResourceOptions": [
+ "value::all"
+ ],
+ "selectAllValue": "*",
+ "showDefault": false
+ },
+ "defaultValue": "value::all",
+ "value": [
+ "value::all"
+ ]
+ },
+ {
+ "id": "981f1274-a745-5685-9c0a-938042ba7b06",
+ "version": "KqlParameterItem/1.0",
+ "name": "RiskCategory",
+ "label": "Risk Category",
+ "type": 2,
+ "isRequired": true,
+ "multiSelect": true,
+ "quote": "'",
+ "delimiter": ",",
+ "query": "",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "typeSettings": {
+ "additionalResourceOptions": [
+ "value::all"
+ ],
+ "selectAllValue": "*",
+ "showDefault": false
+ },
+ "defaultValue": "value::all",
+ "value": [
+ "value::all"
+ ],
+ "jsonData": "[{\"value\": \"VULNERABILITY\", \"label\": \"Vulnerability\"}, {\"value\": \"MISCONFIGURATION\", \"label\": \"Misconfiguration\"}, {\"value\": \"EXPOSURE\", \"label\": \"Exposure\"}, {\"value\": \"THREAT\", \"label\": \"Threat\"}]"
+ },
+ {
+ "id": "2a6c8d3e-7b1f-5f0e-9a44-3c2d9e8b1a77",
+ "version": "KqlParameterItem/1.0",
+ "name": "Status",
+ "label": "Status",
+ "type": 2,
+ "isRequired": true,
+ "multiSelect": true,
+ "quote": "'",
+ "delimiter": ",",
+ "query": "",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "typeSettings": {
+ "additionalResourceOptions": [
+ "value::all"
+ ],
+ "selectAllValue": "*",
+ "showDefault": false
+ },
+ "defaultValue": "value::all",
+ "value": [
+ "value::all"
+ ],
+ "jsonData": "[{\"value\": \"open\", \"label\": \"Open\"}, {\"value\": \"closed\", \"label\": \"Closed\"}]"
+ }
+ ],
+ "style": "pills",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces"
+ },
+ "name": "d242da61-ec90-533c-946e-20debc8b18b2"
+ },
+ {
+ "type": 9,
+ "content": {
+ "version": "KqlParameterItem/1.0",
+ "parameters": [
+ {
+ "id": "b0aac801-3580-5807-8df7-7a5e49f3c503",
+ "version": "KqlParameterItem/1.0",
+ "name": "P1Severity",
+ "type": 1,
+ "isRequired": false,
+ "isHiddenWhenLocked": true,
+ "value": "None"
+ },
+ {
+ "id": "9952c2a7-5e8b-5f2f-a19d-dc9d340ca4fe",
+ "version": "KqlParameterItem/1.0",
+ "name": "P2Date",
+ "type": 1,
+ "isRequired": false,
+ "isHiddenWhenLocked": true,
+ "value": "None"
+ },
+ {
+ "id": "c97c3004-6c39-55d2-8b8a-a3c41ef751f5",
+ "version": "KqlParameterItem/1.0",
+ "name": "P2Status",
+ "type": 1,
+ "isRequired": false,
+ "isHiddenWhenLocked": true,
+ "value": "None"
+ },
+ {
+ "id": "c070c984-7e2c-568c-8d8f-b6ca8ffaffd8",
+ "version": "KqlParameterItem/1.0",
+ "name": "P3Category",
+ "type": 1,
+ "isRequired": false,
+ "isHiddenWhenLocked": true,
+ "value": "None"
+ },
+ {
+ "id": "b5d774c8-0c6d-5ffa-8e74-193a9f8dcd6e",
+ "version": "KqlParameterItem/1.0",
+ "name": "P4Risk",
+ "type": 1,
+ "isRequired": false,
+ "isHiddenWhenLocked": true,
+ "value": "None"
+ },
+ {
+ "id": "6179b664-6feb-5562-9189-6351e12c6713",
+ "version": "KqlParameterItem/1.0",
+ "name": "P5Supplier",
+ "type": 1,
+ "isRequired": false,
+ "isHiddenWhenLocked": true,
+ "value": "None"
+ },
+ {
+ "id": "c728699c-76e3-5dd5-b847-ed58b40037b7",
+ "version": "KqlParameterItem/1.0",
+ "name": "P6Event",
+ "type": 1,
+ "isRequired": false,
+ "isHiddenWhenLocked": true,
+ "value": "None"
+ }
+ ],
+ "style": "pills",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces"
+ },
+ "name": "383c127f-e50f-5a35-8def-1a9f9987899b"
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where Status == \"open\"\n| summarize [\"count\"] = count() by severity = Severity\n| order by severity asc",
+ "size": 3,
+ "title": "Open Risk Count by Severity",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "piechart",
+ "exportFieldName": "series",
+ "exportParameterName": "P1Severity",
+ "exportDefaultValue": "None",
+ "chartSettings": {
+ "group": "severity",
+ "seriesLabelSettings": [
+ {
+ "seriesName": "CRITICAL",
+ "color": "redBright"
+ },
+ {
+ "seriesName": "HIGH",
+ "color": "orange"
+ },
+ {
+ "seriesName": "MEDIUM",
+ "color": "yellow"
+ },
+ {
+ "seriesName": "LOW",
+ "color": "blue"
+ },
+ {
+ "seriesName": "UNSPECIFIED",
+ "color": "gray"
+ }
+ ],
+ "ySettings": {
+ "numberFormatSettings": {
+ "unit": 0,
+ "options": {
+ "style": "decimal",
+ "useGrouping": true,
+ "maximumSignificantDigits": 15
+ }
+ }
+ }
+ }
+ },
+ "name": "1b6bdd62-e666-568e-ad83-e6466e7d0772",
+ "customWidth": "50"
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| summarize [\"count\"] = count() by category = RiskCategory\n| order by category asc",
+ "size": 3,
+ "title": "Risk Events by Category",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "piechart",
+ "chartSettings": {
+ "group": "category",
+ "ySettings": {
+ "numberFormatSettings": {
+ "unit": 0,
+ "options": {
+ "style": "decimal",
+ "useGrouping": true,
+ "maximumSignificantDigits": 15
+ }
+ }
+ }
+ },
+ "exportFieldName": "series",
+ "exportParameterName": "P3Category",
+ "exportDefaultValue": "None"
+ },
+ "name": "ab26b647-f261-532b-937b-fc06058cd3f2",
+ "customWidth": "50"
+ },
+ {
+ "type": 1,
+ "content": {
+ "json": "\ud83d\udca1 _Click on a slice in the above 'Open Risk Count by Severity' chart to view more details_\n\n\ud83d\udcdd _Counts open risk events by severity, one per event id._"
+ },
+ "name": "3408c688-c583-5070-b687-a6b6c057b141",
+ "customWidth": "50"
+ },
+ {
+ "type": 1,
+ "content": {
+ "json": "\ud83d\udca1 _Click on a slice in the above 'Risk Events by Category' chart to view more details_\n\n\ud83d\udcdd _Counts risk events by category, one per event id._"
+ },
+ "name": "a91116af-f6ff-5304-8e46-14afb20ca027",
+ "customWidth": "50"
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where Status == \"open\"\n| where Severity == '{P1Severity}'\n| order by InsertedAt desc\n| project Time = InsertedAt, id = EventId, [\"Risk Name\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\"Platform URL\"] = PlatformUrl\n| take 10000",
+ "size": 0,
+ "title": "Events - {P1Severity}",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Time",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "id",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ },
+ {
+ "columnMatch": "Risk Name",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "27%"
+ }
+ },
+ {
+ "columnMatch": "Severity",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "8%"
+ }
+ },
+ {
+ "columnMatch": "Supplier",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "Platform URL",
+ "formatter": 7,
+ "formatOptions": {
+ "linkTarget": "Url",
+ "linkLabel": "Open in Censys",
+ "customColumnWidthSetting": "15%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ }
+ },
+ "name": "b4afc41f-40bd-50d8-a9f6-66f8d2b30133",
+ "customWidth": "100",
+ "conditionalVisibility": {
+ "parameterName": "P1Severity",
+ "comparison": "isNotEqualTo",
+ "value": "None"
+ }
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where RiskCategory == '{P3Category}'\n| order by InsertedAt desc\n| project Time = InsertedAt, id = EventId, [\"Risk Name\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\"Platform URL\"] = PlatformUrl\n| take 10000",
+ "size": 0,
+ "title": "Events - {P3Category}",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Time",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "id",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ },
+ {
+ "columnMatch": "Risk Name",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "27%"
+ }
+ },
+ {
+ "columnMatch": "Severity",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "8%"
+ }
+ },
+ {
+ "columnMatch": "Supplier",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "Platform URL",
+ "formatter": 7,
+ "formatOptions": {
+ "linkTarget": "Url",
+ "linkLabel": "Open in Censys",
+ "customColumnWidthSetting": "15%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ }
+ },
+ "name": "70e7cbe8-0bc7-571e-81e4-7d792f321c2b",
+ "customWidth": "100",
+ "conditionalVisibility": {
+ "parameterName": "P3Category",
+ "comparison": "isNotEqualTo",
+ "value": "None"
+ }
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "let TotalHours = (todatetime({TimeRange:end}) - todatetime({TimeRange:start})) / 1h;\n// Time span picked from the Time Range (at most about 100 points): 30 minutes, 1 hour or 1 day\nlet Span = iff(TotalHours <= 50, 30m, iff(TotalHours <= 100, 1h, 1d));\nlet Buckets = range Bucket from bin(todatetime({TimeRange:start}), Span) to todatetime({TimeRange:end}) step Span | extend k = 1;\nlet Statuses = datatable(Series: string)[\"Opened\", \"Closed\"] | extend k = 1;\nlet Counts = CensysRiskEvents\n | where OccurredAt {TimeRange}\n | where ('*' in ({Status}) or Status in ({Status}))\n | where ('*' in ({Severity}) or Severity in ({Severity}))\n | where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n | where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n | extend Series = case(Status == \"open\", \"Opened\", Status == \"closed\", \"Closed\", Status)\n | summarize Total = count() by Bucket = bin(OccurredAt, Span), Series;\nBuckets\n| join kind=inner Statuses on k\n| join kind=leftouter Counts on Bucket, Series\n| project Time = iff(Span >= 1d, format_datetime(Bucket, 'yyyy-MM-dd'), format_datetime(Bucket, 'yyyy-MM-dd HH:mm')), Series, [\"Risk Count\"] = coalesce(Total, 0)\n| order by Time asc, Series asc",
+ "size": 0,
+ "title": "Opened vs. Closed Risks",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "timechart",
+ "chartSettings": {
+ "xAxis": "Time",
+ "group": "Series",
+ "xSettings": {
+ "label": "Time"
+ },
+ "ySettings": {
+ "label": "Risk Count"
+ }
+ },
+ "exportedParameters": [
+ {
+ "fieldName": "x",
+ "parameterName": "P2Date",
+ "parameterType": 1,
+ "defaultValue": "None"
+ },
+ {
+ "fieldName": "series",
+ "parameterName": "P2Status",
+ "parameterType": 1,
+ "defaultValue": "None"
+ }
+ ]
+ },
+ "name": "4668c9cb-64a5-581d-b461-d8854d0f2409",
+ "customWidth": "100"
+ },
+ {
+ "type": 1,
+ "content": {
+ "json": "\ud83d\udca1 _Click on a data point in the above 'Opened vs. Closed Risks' chart to view more details_\n\n\ud83d\udcdd _Counts risk events per day by their status (Opened or Closed), by when they occurred._"
+ },
+ "name": "c2beca44-a16d-5fa8-821b-d7aea664ec9f",
+ "customWidth": "100"
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "let TotalHours = (todatetime({TimeRange:end}) - todatetime({TimeRange:start})) / 1h;\n// Time span picked from the Time Range (at most about 100 points): 30 minutes, 1 hour or 1 day\nlet Span = iff(TotalHours <= 50, 30m, iff(TotalHours <= 100, 1h, 1d));\nCensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where Status == iff('{P2Status}' == \"Opened\", \"open\", \"closed\")\n| where OccurredAt >= todatetime(substring('{P2Date}', 0, 16)) and OccurredAt < todatetime(substring('{P2Date}', 0, 16)) + Span\n| order by InsertedAt desc\n| project Time = InsertedAt, id = EventId, [\"Risk Name\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\"Platform URL\"] = PlatformUrl\n| take 10000",
+ "size": 0,
+ "title": "Events - {P2Status} on {P2Date}",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Time",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "id",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ },
+ {
+ "columnMatch": "Risk Name",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "27%"
+ }
+ },
+ {
+ "columnMatch": "Severity",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "8%"
+ }
+ },
+ {
+ "columnMatch": "Supplier",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "Platform URL",
+ "formatter": 7,
+ "formatOptions": {
+ "linkTarget": "Url",
+ "linkLabel": "Open in Censys",
+ "customColumnWidthSetting": "15%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ }
+ },
+ "name": "50857b22-679b-5891-bd85-96262bd29d80",
+ "customWidth": "100",
+ "conditionalVisibilities": [
+ {
+ "parameterName": "P2Date",
+ "comparison": "isNotEqualTo",
+ "value": "None"
+ },
+ {
+ "parameterName": "P2Status",
+ "comparison": "isNotEqualTo",
+ "value": "None"
+ }
+ ]
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where Status == \"open\" and isnotempty(RiskName)\n| summarize OpenRisks = count() by [\"Risk Name\"] = RiskName\n| order by OpenRisks desc\n| take 10",
+ "size": 0,
+ "title": "Top 10 Risks by Open Risk Count",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Risk Name",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "80%"
+ }
+ },
+ {
+ "columnMatch": "OpenRisks",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ },
+ "exportFieldName": "Risk Name",
+ "exportParameterName": "P4Risk",
+ "exportDefaultValue": "None"
+ },
+ "name": "4c44d1fa-a2d9-55b0-98e5-8030350c5f9d",
+ "customWidth": "50"
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where Status == \"open\"\n| summarize OpenRisks = count() by Supplier = SupplierDisplayName\n| order by OpenRisks desc",
+ "size": 0,
+ "title": "Supplier Risk Ranking",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Supplier",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "80%"
+ }
+ },
+ {
+ "columnMatch": "OpenRisks",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ },
+ "exportFieldName": "Supplier",
+ "exportParameterName": "P5Supplier",
+ "exportDefaultValue": "None"
+ },
+ "name": "a5821846-0945-540c-9c97-849cad7afa8b",
+ "customWidth": "50"
+ },
+ {
+ "type": 1,
+ "content": {
+ "json": "\ud83d\udca1 _Click on a row in the above 'Top 10 Risks by Open Risk Count' grid to view more details_\n\n\ud83d\udcdd _The 10 risk names with the most open risk events._"
+ },
+ "name": "cdf92177-e783-5d83-97d1-37d122085500",
+ "customWidth": "50"
+ },
+ {
+ "type": 1,
+ "content": {
+ "json": "\ud83d\udca1 _Click on a row in the above 'Supplier Risk Ranking' grid to view more details_\n\n\ud83d\udcdd _Suppliers ranked by their number of open risk events._"
+ },
+ "name": "4e9590ae-6386-5f24-9ca0-f3a096994918",
+ "customWidth": "50"
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where Status == \"open\"\n| where RiskName == '{P4Risk}'\n| order by InsertedAt desc\n| project Time = InsertedAt, id = EventId, [\"Risk Name\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\"Platform URL\"] = PlatformUrl\n| take 10000",
+ "size": 0,
+ "title": "Events - {P4Risk}",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Time",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "id",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ },
+ {
+ "columnMatch": "Risk Name",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "27%"
+ }
+ },
+ {
+ "columnMatch": "Severity",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "8%"
+ }
+ },
+ {
+ "columnMatch": "Supplier",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "Platform URL",
+ "formatter": 7,
+ "formatOptions": {
+ "linkTarget": "Url",
+ "linkLabel": "Open in Censys",
+ "customColumnWidthSetting": "15%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ }
+ },
+ "name": "22004c2d-0bf3-5e3d-9870-78807252bd52",
+ "customWidth": "100",
+ "conditionalVisibility": {
+ "parameterName": "P4Risk",
+ "comparison": "isNotEqualTo",
+ "value": "None"
+ }
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where Status == \"open\"\n| where SupplierDisplayName == '{P5Supplier}'\n| order by InsertedAt desc\n| project Time = InsertedAt, id = EventId, [\"Risk Name\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\"Platform URL\"] = PlatformUrl\n| take 10000",
+ "size": 0,
+ "title": "Events - {P5Supplier}",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Time",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "id",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ },
+ {
+ "columnMatch": "Risk Name",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "27%"
+ }
+ },
+ {
+ "columnMatch": "Severity",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "8%"
+ }
+ },
+ {
+ "columnMatch": "Supplier",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "Platform URL",
+ "formatter": 7,
+ "formatOptions": {
+ "linkTarget": "Url",
+ "linkLabel": "Open in Censys",
+ "customColumnWidthSetting": "15%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ }
+ },
+ "name": "67930e57-64b3-52a9-b227-bebac1f40c88",
+ "customWidth": "100",
+ "conditionalVisibility": {
+ "parameterName": "P5Supplier",
+ "comparison": "isNotEqualTo",
+ "value": "None"
+ }
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where RapidResponse == true\n| order by InsertedAt desc\n| project Time = InsertedAt, id = EventId, [\"Risk Name\"] = RiskName, Severity, Supplier = SupplierDisplayName, [\"Platform URL\"] = PlatformUrl\n| take 10000",
+ "size": 0,
+ "title": "Rapid Response Events",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Time",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "id",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ },
+ {
+ "columnMatch": "Risk Name",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "27%"
+ }
+ },
+ {
+ "columnMatch": "Severity",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "8%"
+ }
+ },
+ {
+ "columnMatch": "Supplier",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "15%"
+ }
+ },
+ {
+ "columnMatch": "Platform URL",
+ "formatter": 7,
+ "formatOptions": {
+ "linkTarget": "Url",
+ "linkLabel": "Open in Censys",
+ "customColumnWidthSetting": "15%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ },
+ "exportFieldName": "id",
+ "exportParameterName": "P6Event",
+ "exportDefaultValue": "None"
+ },
+ "name": "d29db818-909d-5958-8ef4-89e50ee80542",
+ "customWidth": "100"
+ },
+ {
+ "type": 1,
+ "content": {
+ "json": "\ud83d\udca1 _Click on a row in the above 'Rapid Response Events' grid to view more details_\n\n\ud83d\udcdd _Risk events flagged for rapid response (actively exploited), newest first._"
+ },
+ "name": "59f191f6-06f8-5ad8-b72b-8394af1f9a2a",
+ "customWidth": "100"
+ },
+ {
+ "type": 3,
+ "content": {
+ "version": "KqlItem/1.0",
+ "query": "CensysRiskEvents\n| where OccurredAt {TimeRange}\n| where ('*' in ({Status}) or Status in ({Status}))\n| where ('*' in ({Severity}) or Severity in ({Severity}))\n| where ('*' in ({Supplier}) or SupplierDisplayName in ({Supplier}))\n| where ('*' in ({RiskCategory}) or RiskCategory in ({RiskCategory}))\n| where EventId == '{P6Event}'\n| project Time = InsertedAt, id = EventId, [\"Change Type\"] = ChangeType, Status, [\"Risk Name\"] = RiskName, Severity, Supplier = SupplierDisplayName, Asset = AssetName, [\"Service Port\"] = ServicePort, [\"Resolve Reason\"] = ResolveReason, [\"Platform URL\"] = PlatformUrl\n| take 10000",
+ "size": 0,
+ "title": "Event - {P6Event}",
+ "queryType": 0,
+ "resourceType": "microsoft.operationalinsights/workspaces",
+ "showRefreshButton": true,
+ "showAnalytics": true,
+ "showExportToExcel": true,
+ "noDataMessage": "No data found for the selected filters.",
+ "timeContext": {
+ "durationMs": 7776000000
+ },
+ "visualization": "table",
+ "gridSettings": {
+ "formatters": [
+ {
+ "columnMatch": "Time",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "12%"
+ }
+ },
+ {
+ "columnMatch": "id",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "16%"
+ }
+ },
+ {
+ "columnMatch": "Change Type",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "8%"
+ }
+ },
+ {
+ "columnMatch": "Status",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "7%"
+ }
+ },
+ {
+ "columnMatch": "Risk Name",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "20%"
+ }
+ },
+ {
+ "columnMatch": "Severity",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "7%"
+ }
+ },
+ {
+ "columnMatch": "Supplier",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "11%"
+ }
+ },
+ {
+ "columnMatch": "Asset",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "10%"
+ }
+ },
+ {
+ "columnMatch": "Service Port",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "4%"
+ }
+ },
+ {
+ "columnMatch": "Resolve Reason",
+ "formatter": 0,
+ "formatOptions": {
+ "customColumnWidthSetting": "5%"
+ }
+ },
+ {
+ "columnMatch": "Platform URL",
+ "formatter": 7,
+ "formatOptions": {
+ "linkTarget": "Url",
+ "linkLabel": "Open in Censys",
+ "customColumnWidthSetting": "10%"
+ }
+ }
+ ],
+ "filter": true,
+ "rowLimit": 10000
+ }
+ },
+ "name": "996c3d06-11cb-52d7-86a5-2a6444795041",
+ "customWidth": "100",
+ "conditionalVisibility": {
+ "parameterName": "P6Event",
+ "comparison": "isNotEqualTo",
+ "value": "None"
+ }
+ }
+ ],
+ "fallbackResourceIds": [],
+ "fromTemplateId": "sentinel-CensysSupplierRisk",
+ "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
+}
diff --git a/Workbooks/Images/Preview/CensysSupplierRiskBlack1.png b/Workbooks/Images/Preview/CensysSupplierRiskBlack1.png
new file mode 100644
index 00000000000..8ae03395b1c
Binary files /dev/null and b/Workbooks/Images/Preview/CensysSupplierRiskBlack1.png differ
diff --git a/Workbooks/Images/Preview/CensysSupplierRiskWhite1.png b/Workbooks/Images/Preview/CensysSupplierRiskWhite1.png
new file mode 100644
index 00000000000..17905354d84
Binary files /dev/null and b/Workbooks/Images/Preview/CensysSupplierRiskWhite1.png differ
diff --git a/Workbooks/WorkbooksMetadata.json b/Workbooks/WorkbooksMetadata.json
index 35d2aa0831e..faf7debc706 100644
--- a/Workbooks/WorkbooksMetadata.json
+++ b/Workbooks/WorkbooksMetadata.json
@@ -11614,6 +11614,26 @@
"subtitle": "",
"provider": "PRODAFT"
},
+ {
+ "workbookKey": "CensysSupplierRiskWorkbook",
+ "logoFileName": "Censys.svg",
+ "description": "Gain insight into supplier risk collected from Censys Supplier Risk: open risks by severity, risk events by category, opened and closed risks over time, top risks by open risk count, supplier risk ranking and rapid response events, with details one selection away.",
+ "dataTypesDependencies": [
+ "CensysRiskEvents_CL"
+ ],
+ "dataConnectorsDependencies": [
+ "CensysSRRiskEventsFunction"
+ ],
+ "previewImagesFileNames": [
+ "CensysSupplierRiskWhite1.png",
+ "CensysSupplierRiskBlack1.png"
+ ],
+ "version": "1.0.0",
+ "title": "Censys Supplier Risk",
+ "templateRelativePath": "CensysSupplierRisk.json",
+ "subtitle": "",
+ "provider": "Censys"
+ },
{
"workbookKey": "DelineaIdentityAuthOverview",
"logoFileName": "DelineaPlatformLogo.svg",