diff --git a/examples/detection_finding.py b/examples/detection_finding.py index 0b5446d..96b5ffb 100644 --- a/examples/detection_finding.py +++ b/examples/detection_finding.py @@ -150,7 +150,9 @@ name="Account 1", type="Account", type_id="3", uid="123", labels=["Label 1"] ), zone="Zone 1", - org=Organization(name="Organization 1", ou_id="123", ou_name="OU 1", uid="123"), + org=Organization( + name="Organization 1", ou_uid="123", ou_name="OU 1", uid="123" + ), project_uid="123", provider="Provider 1", region="Region 1", @@ -177,10 +179,9 @@ size=123, uid="123", ), - namespace_pid=123, count=123, duration=123, - event_time=datetime.now(), + time=datetime.now(), evidences=[ EvidenceArtifacts( api=API( diff --git a/py_ocsf_models/__init__.py b/py_ocsf_models/__init__.py index 41cc177..b1312cb 100644 --- a/py_ocsf_models/__init__.py +++ b/py_ocsf_models/__init__.py @@ -1 +1 @@ -OCSF_VERSION = "1.2.0" +OCSF_VERSION = "1.3.0" diff --git a/py_ocsf_models/events/findings/detection_finding.py b/py_ocsf_models/events/findings/detection_finding.py index c78b5a4..cdb2d0b 100644 --- a/py_ocsf_models/events/findings/detection_finding.py +++ b/py_ocsf_models/events/findings/detection_finding.py @@ -7,7 +7,6 @@ from py_ocsf_models.events.findings.finding import Finding from py_ocsf_models.objects.api import API from py_ocsf_models.objects.cloud import Cloud -from py_ocsf_models.objects.container import Container from py_ocsf_models.objects.evidence_artifacts import EvidenceArtifacts from py_ocsf_models.objects.remediation import Remediation from py_ocsf_models.objects.resource_details import ResourceDetails @@ -122,10 +121,10 @@ class DetectionFinding(Finding, BaseModel): - Class (class_name) [Optional]: The event class name, as defined by class_uid value: Detection Finding. - Class ID (class_uid): The unique identifier of a class. A Class describes the attributes available in an event. - Cloud (cloud) [Optional]: Describes details about the Cloud environment where the event was originally created or logged. - - Container (container) [Optional]: Describes the container details. - Count (count) [Optional]: Number of times similar events occurred within a specified timeframe. - Duration (duration) [Optional]: Time span of the event, from start to end, in milliseconds. - Event Time (time) [Required]: The standardized time when the event occurred or the finding was created. + - Event Time (time_dt) [Optional]: The standardized time when the event occurred or the finding was created, in datetime format. - Evidence Artifacts (evidences) [Optional]: Artifacts related to the security detection activities. - Impact (impact) [Optional]: The impact, normalized to the caption of the impact_id value. In the case of 'Other', it is defined by the event source. - Impact Score (impact_score) [Optional]: The impact of the finding, valid range 0-100. @@ -135,6 +134,7 @@ class DetectionFinding(Finding, BaseModel): - Risk Level ID (risk_level_id) [Optional]: The normalized risk level id. - Risk Score (risk_score) [Optional]: The risk score as reported by the event source. - Risk Details (risk_details) [Optional]: Additional details about the risk. + - Status ID (status_id) [Optional]: The normalized identifier of the event/finding severity. - Timezone Offset (timezone_offset) [Optional]: Difference in minutes from UTC. - Type ID (type_uid): The event/finding type ID. It identifies the event's semantics and structure. The value is calculated by the logging system as: class_uid * 100 + activity_id. - Type Name (type_name) [Optional]: The event/finding type name, as defined by the type_uid. @@ -142,25 +142,18 @@ class DetectionFinding(Finding, BaseModel): If the Cloud profile is needed: - API Details (api) [Optional]: Describes details about a typical API (Application Programming Interface) call. - - Cloud (cloud): Describes details about the Cloud environment where the event was originally created or logged. - - If the Container profile is needed: - - Container (container) [Recommended]: The information describing an instance of a container. A container is a prepackaged, portable system image that runs isolated on an existing system using a container runtime like containerd. - - Namespace PID (namespace_pid) [Recommended]: If running under a process namespace (such as in a container), the process identifier within that process namespace. + - Cloud (cloud): Describes details about the Cloud environment where the event was originally created or logged """ resources: Optional[list[ResourceDetails]] category_name: str = CategoryUID.Findings.name category_uid: int = CategoryUID.Findings.value - class_name: Optional[str] = ClassUID.DetectionFinding.name + class_name: Optional[str] = "Detection Finding" class_uid: int = ClassUID.DetectionFinding.value cloud: Optional[Cloud] api: Optional[API] - container: Optional[Container] - namespace_pid: Optional[int] count: Optional[int] duration: Optional[int] - event_time: datetime evidences: Optional[list[EvidenceArtifacts]] impact: Optional[str] impact_score: Optional[int] @@ -171,6 +164,8 @@ class DetectionFinding(Finding, BaseModel): risk_score: Optional[int] risk_details: Optional[str] status_id: Optional[StatusID] # type: ignore + time: int + time_dt: Optional[datetime] timezone_offset: Optional[int] type_uid: TypeID type_name: Optional[str] diff --git a/py_ocsf_models/events/findings/finding.py b/py_ocsf_models/events/findings/finding.py index 21070a2..a8ef4ad 100644 --- a/py_ocsf_models/events/findings/finding.py +++ b/py_ocsf_models/events/findings/finding.py @@ -79,13 +79,17 @@ class FindingInformation(BaseModel): Attributes: - Analytic (analytic) [Recommended]: The analytic technique used to analyze and derive insights from the data or information that led to the finding or conclusion. - Created Time (created_time) [Optional]: The time when the finding was created. + - Created Time DT (created_time_dt) [Optional]: The time when the finding was created in datetime format. - Data Sources (data_sources) [Optional]: A list of data sources utilized in generation of the finding. - Description (desc) [Optional]: The description of the reported finding. - - First Seen (first_seen_time) [Optional]: The time when the finding was first observed. It can differ from the created_time datetime, which reflects the time this finding was created. + - First Seen (first_seen_time) [Optional]: The time when the finding was first observed. + - First Seen DT (first_seen_time_dt) [Optional]: The time when the finding was first observed in datetime format. - Kill Chain (kill_chain) [Optional]: The Cyber Kill Chain® provides a detailed description of each phase and its associated activities within the broader context of a cyber attack. - - Last Seen (last_seen_time) [Optional]: The time when the finding was most recently observed. It can differ from the modified_time datetime, which reflects the time this finding was last modified. + - Last Seen (last_seen_time) [Optional]: The time when the finding was last observed. + - Last Seen DT (last_seen_time_dt) [Optional]: The time when the finding was last observed in datetime format. - MITRE ATT&CK® Details (attacks) [Optional]: The MITRE ATT&CK® technique and associated tactics related to the finding. - Modified Time (modified_time) [Optional]: The time when the finding was last modified. + - Modified Time DT (modified_time_dt) [Optional]: The time when the finding was last modified in datetime format. - Product Identifier (product_uid) [Optional]: The unique identifier of the product that reported the finding. - Related Analytics (related_analytics) [Optional]: Other analytics related to this finding. - Related Events (related_events) [Optional]: Describes events and/or other findings related to the finding as identified by the security product. @@ -96,14 +100,18 @@ class FindingInformation(BaseModel): """ analytic: Optional[Analytic] - created_time: Optional[datetime] + created_time: Optional[int] + created_time_dt: Optional[datetime] data_sources: Optional[List[str]] desc: Optional[str] - first_seen_time: Optional[datetime] + first_seen_time: Optional[int] + first_seen_time_dt: Optional[datetime] kill_chain: Optional[List[KillChainPhase]] - last_seen_time: Optional[datetime] + last_seen_time: Optional[int] + last_seen_time_dt: Optional[datetime] attacks: Optional[List[MITREAttack]] - modified_time: Optional[datetime] + modified_time: Optional[int] + modified_time_dt: Optional[datetime] product_uid: Optional[str] related_analytics: Optional[List[Analytic]] related_events: Optional[List[RelatedEvent]] @@ -161,9 +169,11 @@ class Finding(BaseEvent, BaseModel): - Confidence (confidence) [Optional]: The confidence, normalized to the caption of the confidence_id value. In the case of 'Other', it is defined by the event source. - Confidence ID (confidence_id) [Optional]: Represents the accuracy of the detection rule. A low confidence indicates a broad finding scope that may include benign events. - Confidence Score (confidence_score) [Optional]: The confidence score as reported by the event source. - - End Time (end_time) [Optional]: datetime of the most recent event included in the finding. + - End Time (end_time) [Optional]: Time of the latest event included in the finding. + - End Time DT (end_time_dt) [Optional]: Time of the latest event included in the finding in datetime format. - Finding Information (finding_info) [Required]: Describes the supporting information about a generated finding. - Start Time (start_time) [Optional]: Time of the earliest event included in the finding. + - Start Time DT (start_time_dt) [Optional]: Time of the earliest event included in the finding in datetime """ @@ -173,6 +183,8 @@ class Finding(BaseEvent, BaseModel): confidence: Optional[str] confidence_id: Optional[ConfidenceID] confidence_score: Optional[int] - end_time: Optional[datetime] + end_time: Optional[int] + end_time_dt: Optional[datetime] finding_info: FindingInformation - start_time: Optional[datetime] + start_time: Optional[int] + start_time_dt: Optional[datetime] diff --git a/py_ocsf_models/objects/kb_article.py b/py_ocsf_models/objects/kb_article.py index 49ff1aa..d468c4f 100644 --- a/py_ocsf_models/objects/kb_article.py +++ b/py_ocsf_models/objects/kb_article.py @@ -15,7 +15,8 @@ class KBArticle(BaseModel): Attributes: - classification: Vendor's classification of the KB article. - - created_time: Release date of the KB article. + - created_time: Time the KB article was created. + - created_time_dt: Time the KB article was created in datetime - os: Operating system the KB article applies to. - bulletin: Bulletin identifier of the KB article. - product: Product details the KB article applies to. @@ -28,7 +29,8 @@ class KBArticle(BaseModel): """ classification: Optional[str] - created_time: Optional[datetime] + created_time: Optional[int] + created_time_dt: Optional[datetime] os: OperatingSystem bulletin: Optional[str] product: Optional[Product] diff --git a/py_ocsf_models/objects/organization.py b/py_ocsf_models/objects/organization.py index 24a571e..ca465ad 100644 --- a/py_ocsf_models/objects/organization.py +++ b/py_ocsf_models/objects/organization.py @@ -15,6 +15,6 @@ class Organization(BaseModel): """ name: Optional[str] - ou_id: Optional[str] + ou_uid: Optional[str] ou_name: Optional[str] uid: Optional[str] diff --git a/py_ocsf_models/objects/vulnerability_details.py b/py_ocsf_models/objects/vulnerability_details.py index a6fb8c2..7c9217d 100644 --- a/py_ocsf_models/objects/vulnerability_details.py +++ b/py_ocsf_models/objects/vulnerability_details.py @@ -19,8 +19,10 @@ class VulnerabilityDetails(BaseModel): - Description (desc) [Optional]: The description of the vulnerability. - Exploit Availability (is_exploit_available) [Optional]: Indicates if an exploit or a PoC (proof-of-concept) is available for the reported vulnerability. - First Seen (first_seen_time) [Optional]: The time when the vulnerability was first observed. + - First Seen (first_seen_time_dt) [Optional]: The time when the vulnerability was first observed in datetime format. - Knowledgebase Articles (kb_article_list) [Optional]: A list of KB articles or patches related to an endpoint. A KB Article contains metadata that describes the patch or an update. - Last Seen (last_seen_time) [Optional]: The time when the vulnerability was most recently observed. + - Last Seen (last_seen_time_dt) [Optional]: The time when the vulnerability was most recently observed in datetime format. - References (references) [Recommended]: A list of reference URLs with additional information about the vulnerability. - Related Vulnerabilities (related_vulnerabilities) [Optional]: List of vulnerabilities that are related to this vulnerability. - Remediation Guidance (remediation) [Optional]: The remediation recommendations on how to mitigate the identified vulnerability. @@ -36,9 +38,11 @@ class VulnerabilityDetails(BaseModel): # cwe: Optional[CWE] desc: Optional[str] is_exploit_available: Optional[bool] - first_seen_time: Optional[datetime] + first_seen_time: Optional[int] + first_seen_time_dt: Optional[datetime] kb_article_list: Optional[List[KBArticle]] - last_seen_time: Optional[datetime] + last_seen_time: Optional[int] + last_seen_time_dt: Optional[datetime] references: Optional[List[str]] related_vulnerabilities: Optional[List[str]] remediation: Optional[Remediation] diff --git a/pyproject.toml b/pyproject.toml index e55aa58..c73a5f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ packages = [ {include = "py_ocsf_models"} ] readme = "README.md" -version = "0.1.1" +version = "0.2.0" [tool.poetry.dependencies] cryptography = "43.0.1" diff --git a/tests/detection_finding_test.py b/tests/detection_finding_test.py index d541a7b..eaca7f6 100644 --- a/tests/detection_finding_test.py +++ b/tests/detection_finding_test.py @@ -1,8 +1,10 @@ import uuid from datetime import datetime +import requests + from py_ocsf_models import OCSF_VERSION -from py_ocsf_models.events.base_event import SeverityID +from py_ocsf_models.events.base_event import SeverityID, StatusID from py_ocsf_models.events.findings.detection_finding import ( CategoryUID, DetectionFinding, @@ -26,7 +28,6 @@ from py_ocsf_models.objects.container import Container, FingerPrint, Image from py_ocsf_models.objects.dns_query import DNSOpcodeID, DNSQuery from py_ocsf_models.objects.evidence_artifacts import EvidenceArtifacts -from py_ocsf_models.objects.fingerprint import AlgorithmID from py_ocsf_models.objects.metadata import Metadata from py_ocsf_models.objects.operating_system import OperatingSystem, TypeID from py_ocsf_models.objects.product import Feature, Product @@ -42,6 +43,8 @@ class TestDetectionFinding: def test_detection_finding(self): pod_uuid = str(uuid.uuid4()) detection_finding = DetectionFinding( + status=StatusID.New.name, + status_id=StatusID.New.value, metadata=Metadata( version=OCSF_VERSION, product=Product( @@ -57,27 +60,33 @@ def test_detection_finding(self): vendor_name=PROWLER_PRODUCT, version=PROWLER_VERSION, ), + profiles=["cloud", "datetime"], ), finding_info=FindingInformation( title="Title", uid="123", + created_time=int(datetime.now().timestamp()), + created_time_dt=datetime.now(), ), severity_id=SeverityID.Informational, severity=SeverityID(1).name, - activity_name="Activity Name", + activity_name="Create", activity_id=1, comment="Comment", confidence="Confidence", confidence_id=1, confidence_score=123, - end_time=datetime.now(), - start_time=datetime.now(), + end_time=int(datetime.now().timestamp()), + end_time_dt=datetime.now(), + start_time=int(datetime.now().timestamp()), + start_time_dt=datetime.now(), resources=[ ResourceDetails( id="123", name="Resource 1", type="Resource", details="Details of the resource", + cloud_partition="aws", ) ], category_name=CategoryUID.Findings.name, @@ -89,7 +98,7 @@ def test_detection_finding(self): containers=[ Container( hash=FingerPrint( - algorithm="SHA256", + algorithm="SHA-256", algorithm_id=3, value="123", ), @@ -118,7 +127,7 @@ def test_detection_finding(self): containers=[ Container( hash=FingerPrint( - algorithm="SHA256", + algorithm="SHA-256", algorithm_id=3, value="123", ), @@ -166,14 +175,14 @@ def test_detection_finding(self): cloud=Cloud( account=Account( name="Account 1", - type="Account", + type="AWS IAM User", type_id="3", uid="123", labels=["label 1"], ), zone="Zone 1", org=Organization( - name="Organization 1", ou_id="123", ou_name="OU 1", uid="123" + name="Organization 1", ou_uid="123", ou_name="OU 1", uid="123" ), project_uid="123", provider="Provider 1", @@ -181,7 +190,7 @@ def test_detection_finding(self): ), container=Container( hash=FingerPrint( - algorithm="SHA256", + algorithm="SHA-256", algorithm_id=3, value="123", ), @@ -201,10 +210,10 @@ def test_detection_finding(self): size=123, uid="123", ), - namespace_pid=123, count=123, duration=123, - event_time=datetime.now(), + time=int(datetime.now().timestamp()), + time_dt=datetime.now(), evidences=[ EvidenceArtifacts( api=API( @@ -212,7 +221,7 @@ def test_detection_finding(self): containers=[ Container( hash=FingerPrint( - algorithm="SHA256", + algorithm="SHA-256", algorithm_id=3, value="123", ), @@ -241,7 +250,7 @@ def test_detection_finding(self): containers=[ Container( hash=FingerPrint( - algorithm="SHA256", + algorithm="SHA-256", algorithm_id=3, value="123", ), @@ -297,7 +306,7 @@ def test_detection_finding(self): data={"key": "value"}, ) ], - impact="Impact", + impact="Low", impact_score=123, impact_id=1, remediation=Remediation( @@ -305,7 +314,7 @@ def test_detection_finding(self): kb_article_list=[ KBArticle( classification="Classification", - created_time=datetime.now(), + created_time=int(datetime.now().timestamp()), os=OperatingSystem( cpu_bits=64, country="US", @@ -316,7 +325,7 @@ def test_detection_finding(self): sp_name="SP Name", sp_ver=123, cpe_name="CPE Name", - type="Type", + type="Windows", type_id=100, version="Version", ), @@ -342,22 +351,24 @@ def test_detection_finding(self): ], references=["https://www.example.com"], ), - risk_level="Risk Level", + risk_level="Low", risk_level_id=1, risk_score=123, risk_details="Risk Details", timezone_offset=123, type_uid=DetectionFindingTypeID.Create, - type_name=DetectionFindingTypeID.Create.name, + type_name=f"Detection Finding: {DetectionFindingTypeID.Create.name}", vulnerabilities=[ VulnerabilityDetails( desc="Description", + cve="CVE-2021-1234", is_exploit_available=True, - first_seen_time=datetime.now(), + first_seen_time=int(datetime.now().timestamp()), + first_seen_time_dt=datetime.now(), kb_article_list=[ KBArticle( classification="Classification", - created_time=datetime.now(), + created_time=int(datetime.now().timestamp()), os=OperatingSystem( cpu_bits=64, country="US", @@ -368,7 +379,7 @@ def test_detection_finding(self): sp_name="SP Name", sp_ver=123, cpe_name="CPE Name", - type="Type", + type="Windows", type_id=100, version="Version", ), @@ -394,7 +405,8 @@ def test_detection_finding(self): uid="123", ) ], - last_seen_time=datetime.now(), + last_seen_time=int(datetime.now().timestamp()), + last_seen_time_dt=datetime.now(), references=["https://www.example.com"], related_vulnerabilities=["123"], remediation=Remediation( @@ -402,7 +414,8 @@ def test_detection_finding(self): kb_article_list=[ KBArticle( classification="Classification", - created_time=datetime.now(), + created_time=int(datetime.now().timestamp()), + created_time_dt=datetime.now(), os=OperatingSystem( cpu_bits=64, country="US", @@ -413,7 +426,7 @@ def test_detection_finding(self): sp_name="SP Name", sp_ver=123, cpe_name="CPE Name", - type="Type", + type="Windows", type_id=100, version="Version", ), @@ -473,7 +486,7 @@ def test_detection_finding(self): # Assert simple attributes assert detection_finding.severity_id == SeverityID.Informational - assert detection_finding.activity_name == "Activity Name" + assert detection_finding.activity_name == "Create" assert detection_finding.activity_id == ActivityID.Create assert detection_finding.comment == "Comment" assert detection_finding.confidence == "Confidence" @@ -497,26 +510,6 @@ def test_detection_finding(self): assert detection_finding.cloud.region == "Region 1" assert detection_finding.cloud.account.labels == ["label 1"] - # Assert ContainerProfile and nested objects - container = detection_finding.container - assert str(container.pod_uuid) == pod_uuid - assert container.network_driver == "Network Driver 1" - assert container.orchestrator == "Orchestrator 1" - assert container.size == 123 - - # Assert Image and FingerPrint - image = container.image - assert image.tag == "Tag 1" - assert image.name == "Image 1" - assert "Label 1" in image.labels - assert image.path == "Path 1" - assert image.uid == "123" - - fingerprint = container.hash - assert fingerprint.algorithm == "SHA256" - assert fingerprint.algorithm_id == AlgorithmID.SHA_256 - assert fingerprint.value == "123" - # Assert DNSQuery dns_query = detection_finding.evidences[0].query assert dns_query.opcode == "Query" @@ -552,19 +545,19 @@ def test_detection_finding(self): assert vulnerability.vendor_name == "Vendor Name" # Assert OperatingSystem in KBArticle - os = kb_article.os - assert os.cpu_bits == 64 - assert os.country == "US" - assert os.lang == "en" - assert os.name == "Name" - assert os.build == "Build" - assert os.edition == "Edition" - assert os.sp_name == "SP Name" - assert os.sp_ver == 123 - assert os.cpe_name == "CPE Name" - assert os.type == "Type" - assert os.type_id == TypeID.Windows - assert os.version == "Version" + operating_system = kb_article.os + assert operating_system.cpu_bits == 64 + assert operating_system.country == "US" + assert operating_system.lang == "en" + assert operating_system.name == "Name" + assert operating_system.build == "Build" + assert operating_system.edition == "Edition" + assert operating_system.sp_name == "SP Name" + assert operating_system.sp_ver == 123 + assert operating_system.cpe_name == "CPE Name" + assert operating_system.type == TypeID.Windows.name + assert operating_system.type_id == TypeID.Windows + assert operating_system.version == "Version" # Assert EvidenceArtifacts evidence_artifact = detection_finding.evidences[0] @@ -574,4 +567,14 @@ def test_detection_finding(self): # Assert Type assert detection_finding.type_uid == DetectionFindingTypeID.Create - assert detection_finding.type_name == "Create" + assert detection_finding.type_name == "Detection Finding: Create" + + detection_finding_json = detection_finding.json() + + url = "https://schema.ocsf.io/api/v2/validate" + headers = {"content-type": "application/json"} + + response = requests.post(url, headers=headers, data=detection_finding_json) + assert ( + response.json()["error_count"] == 1 + ) # TODO: add cve or cwe attributes to VulnerabilityDetails to fix this error