diff --git a/stix_shifter_modules/sysdig/README.md b/stix_shifter_modules/sysdig/README.md new file mode 100644 index 000000000..bce13e79b --- /dev/null +++ b/stix_shifter_modules/sysdig/README.md @@ -0,0 +1,1362 @@ +# Sysdig +## Supported STIX Mappings +See the [table of mappings](sysdig_supported_stix.md) for the STIX objects and operators supported by this connector. + +**Table of Contents** + +- [Sysdig API Endpoints](#Sysdig-api-endpoints) +- [Pattern expression with STIX attributes - Single Observation](#Single-observation) +- [Pattern expression with STIX attributes - Multiple Observation](#Multiple-observation) +- [Pattern expression with STIX attributes - Execute Query](#Stix-execute-query) +- [Limitations](#Limitations) +- [References](#References) + + +### Sysdig API Endpoints + + | Connector Method | Sysdig API Endpoint | Method | + |------------------|------------------------------------------------------|--------| + | Ping Endpoint | https://< sysdig-server >/api/v1/secureEvents/status | GET | + | Events Endpoint | https://< sysdig-server >/api/v1/secureEvents | GET | + +### Format for calling stix-shifter from the command line +``` +python main.py `` `` `` `` `` +``` +### Pattern expression with STIX attributes + +### Single Observation + +#### STIX Translate query +```shell +translate sysdig query "{}" "[x-sysdig-cluster:name != 'dummycluster'] START t'2023-10-25T16:43:26.000Z' STOP t'2023-11-05T16:43:26.003Z'" +``` +#### STIX Translate query - output +```json +{ + "queries": [ + "from=1698252206000000000&to=1699202606003000064&filter=(kubernetes.cluster.name!=\"dummycluster\")andsource!=\"auditTrail\"" + ] +} +``` +#### STIX Transmit query + +```shell +transmit +sysdig +"{\"host\": \"dummyhost\", \"port\": 123}" +"{\"auth\": {\"token\": \"abcdefghijklm\"}}" +results +"from=1698252206000000000&to=1699202606003000064&filter=kubernetes.cluster.name!=\"dummycluster\"andsource!=\"auditTrail\"" +0 +1 +``` +#### STIX Transmit result - output +```json +{ + "success": true, + "data": [ + { + "id": "12345678910", + "cursor": "ABCDEFGHIJKL", + "timestamp": "2023-11-01T17:15:14.635115435Z", + "customerId": 10000, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "Sysdig Runtime Notable Events", + "description": "This Notable Events policy contains rules which may indicate undesired behavior including security threats. The rules are more generalized than Threat Detection policies and may result in more noise. Tuning will likely be required for the events generated from this policy.", + "severity": 30, + "agentId": 111111, + "containerId": "1010101010", + "machineId": "10:10:10:10:10:aa", + "content": { + "falsePositive": false, + "fields": { + "container.id": "1010101010", + "container.image.repository": "quay.io/prometheus/node-exporter", + "container.image.tag": "v1.6.1", + "container.mounts": "/proc:/host/proc::false:private,/sys:/host/sys::false:private,/:/host/root::false:rslave,/var/lib/kubelet/pods/2f59b128-16ed-4a62-b314-6633e1c85725/etc-hosts:/etc/hosts::true:private,/var/lib/kubelet/pods/2f59b128-16ed-4a62-b314-6633e1c85725/containers/node-exporter/cf6dc919:/dev/termination-log::true:private", + "container.name": "node-exporter", + "evt.res": "", + "evt.type": "event", + "falco.rule": "Launch Sensitive Mount Container", + "group.gid": "0", + "group.name": "", + "proc.cmdline": "container:1010101010", + "proc.cwd": "", + "proc.exepath": "", + "proc.name": "container:1010101010", + "proc.pcmdline": "", + "proc.pid": "-1", + "proc.ppid": "-1", + "proc.sid": "-1", + "user.loginname": "", + "user.loginuid": "0", + "user.name": "11111", + "user.uid": "0", + "proc.anames" : [] + }, + "internalRuleName": "Launch Sensitive Mount Container", + "matchedOnDefault": false, + "origin": "Sysdig", + "output": "Container with sensitive mount started (user.name=65534 user.loginuid=0 proc.cmdline=container:8dd62ca5ca80 node-exporter (id=8dd62ca5ca80) image=quay.io/prometheus/node-exporter:v1.6.1 evt.type=container evt.res= proc.pid=-1 proc.cwd= proc.ppid=-1 proc.pcmdline= proc.sid=-1 proc.exepath= user.uid=0 user.loginname= group.gid=0 group.name= container.id=8dd62ca5ca80 container.name=node-exporter mounts=/proc:/host/proc::false:private,/sys:/host/sys::false:private,/:/host/root::false:rslave,/var/lib/kubelet/pods/2f59b128-16ed-4a62-b314-6633e1c85725/etc-hosts:/etc/hosts::true:private,/var/lib/kubelet/pods/2f59b128-16ed-4a62-b314-6633e1c85725/containers/node-exporter/cf6dc919:/dev/termination-log::true:private)", + "policyId": 111000, + "ruleName": "Launch Sensitive Mount Container", + "ruleSubType": 0, + "ruleTags": [ + "container", + "SOC2", + "SOC2_CC6.1", + "NIST", + "NIST_800-190", + "NIST_800-190_3.4.3", + "NIST_800-190_3.5.5", + "NIST_800-53", + "NIST_800-53_AC-6(9)", + "NIST_800-53_AC-6(10)", + "NIST_800-53_AU-6(8)", + "ISO", + "ISO_27001", + "ISO_27001_A.9.2.3", + "HIPAA", + "HIPAA_164.308(a)", + "HIPAA_164.312(a)", + "HIPAA_164.312(b)", + "HITRUST", + "HITRUST_CSF", + "HITRUST_CSF_01.c", + "HITRUST_CSF_09.aa", + "GDPR", + "GDPR_32.1", + "GDPR_32.2", + "MITRE", + "MITRE_T1609_container_administration_command", + "MITRE_T1611_escape_to_host", + "MITRE_TA0002_execution", + "MITRE_TA0004_privilege_escalation", + "MITRE_TA0008_lateral_movement", + "MITRE_T1610_deploy_container", + "MITRE_TA0005_defense_evasion", + "MITRE_T1055.009_process_injection_proc_memory", + "MITRE_T1543_create_or_modify_system_process", + "CIS" + ], + "ruleType": 6 + }, + "labels": { + "aws.accountId": "1111111111111", + "aws.instanceId": "i-101010101010", + "aws.region": "us-east-1", + "cloudProvider.account.id": "1111111111111", + "cloudProvider.name": "aws", + "cloudProvider.region": "us-east-1", + "container.image.digest": "sha256:12345678910", + "container.image.id": "12345", + "container.image.repo": "quay.io", + "container.image.tag": "v1.6", + "container.label.io.kubernetes.container.name": "node-exporter", + "container.label.io.kubernetes.pod.name": "prometheus-prometheus-node", + "container.label.io.kubernetes.pod.namespace": "prometheus-monitor", + "container.name": "node-exporter", + "host.hostName": "ip-111-111-11-11.ec2.internal", + "host.mac": "11:11:11:11:11:bb", + "kubernetes.cluster.name": "cluster", + "kubernetes.daemonSet.name": "prometheus-prometheus-node", + "kubernetes.namespace.name": "prometheus-monitor", + "kubernetes.node.name": "ip-111-111-11-11.ec2.internal", + "kubernetes.pod.name": "prometheus-prometheus-node-exporter", + "kubernetes.service.name": "prometheus-prometheus-node-exporter", + "kubernetes.workload.name": "prometheus-prometheus-node-exporter", + "kubernetes.workload.type": "daemonset" + }, + "finding_type": "threat" + } + ] +} + +``` + +#### STIX Translate results + +```shell +#### STIX Translate results - output +```json +{ + "type": "bundle", + "id": "bundle--763ec5eb-efb3-4c96-a96e-3a7bc3fb894b", + "objects": [ + { + "type": "identity", + "id": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "name": "sysdig", + "identity_class": "events", + "created": "2023-08-01T06:06:52.305Z", + "modified": "2023-08-02T06:06:52.305Z" + }, + { + "id": "observed-data--e91df7cd-1afe-4bf7-96ea-605f1f5a2a69", + "type": "observed-data", + "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "created": "2023-11-29T13:44:13.038Z", + "modified": "2023-11-29T13:44:13.038Z", + "objects": { + "0": { + "type": "x-ibm-finding", + "x_threat_originator": "policy", + "x_category": "runtime", + "x_threat_source": "syscall", + "x_policy_ref": "1", + "severity": 50, + "x_agent_id": 111111, + "name": "Launch Sensitive Mount Container", + "x_cluster_ref": "10", + "x_workload_name": "prometheus-prometheus-node-exporter", + "x_workload_type": "daemonset", + "finding_type": "threat" + }, + "1": { + "type": "x-sysdig-policy", + "description": "This Notable Events policy contains rules which may indicate undesired behavior including security threats. The rules are more generalized than Threat Detection policies and may result in more noise. Tuning will likely be required for the events generated from this policy.", + "policy_id": 111000, + "rule_name": "Launch Sensitive Mount Container", + "rule_subtype": 0, + "rule_type": 6 + }, + "2": { + "type": "x-oca-asset", + "extensions": { + "x-oca-container-ext": { + "container_id": "1010101010", + "x_digest": "sha256:12345678910", + "image_id": "12345", + "x_repo": "quay.io", + "x_tag": "v1.6", + "name": "node-exporter" + }, + "x-oca-pod-ext": { + "pod_name": "prometheus-prometheus-node" + "x_namespace": "prometheus-monitor" + } + }, + "hostname": "ip-111-111-11-11.ec2.internal", + "mac_refs": [ + "9" + ] + }, + "3": { + "type": "process", + "command_line": "container:1010101010", + "name": "container:1010101010", + "binary_ref": "4", + "pid": -1, + "x_sid": "-1", + "creator_user_ref": "6" + }, + "4": { + "type": "file", + "name": "container:1010101010" + }, + "5": { + "type": "process", + "pid": -1 + }, + "6": { + "type": "user-account", + "x_loginuid": "0", + "display_name": "11111", + "user_id": "0" + }, + "7": { + "type": "x-cloud-provider", + "account_id": "1111111111111", + "region": "us-east-1", + "name": "aws" + }, + "8": { + "type": "x-cloud-resource", + "aws_instance_id": "i-101010101010" + }, + "9": { + "type": "mac-addr", + "value": "11:11:11:11:11:bb" + }, + "10": { + "type": "x-sysdig-cluster", + "name": "cluster", + "x_node_ref": "2", + "daemonset": "prometheus-prometheus-node", + "namespace": "prometheus-monitor" + } + }, + "first_observed": "2023-11-01T17:15:14.635115435Z", + "last_observed": "2023-11-01T17:15:14.635115435Z", + "number_observed": 1 + } + ], + "spec_version": "2.0" +} + +``` + +### Multiple Observation with same timestamp + +#### STIX Translate query +```shell +translate +sysdig +query +"{}" +"([x-ibm-finding:name = 'contact EC2'] AND [x-sysdig-deployment:name = 'dummydeployment'])START t'2023-10-26T11:00:00.000Z' STOP t'2023-11-07T11:00:00.003Z'" + +``` + +#### STIX Translate query - output + +```json +{ + "queries": [ + "from=1700478000000000000&to=1700823600003000064&filter=(ruleName=\"Contact EC2\" or kubernetes.deployment.name=\"dummydeployment\")andsource!=\"auditTrail\"" + ] +} +``` + +#### STIX Transmit results + +```shell +transmit +sysdig +"{\"host\": \"dummyhost\", \"port\": 123}" +"{\"auth\": {\"token\": \"abcdefghijklm\"}}" +results +"from=1700478000000000000&to=1700823600003000064filter=(ruleName=\"Contact EC2\" or kubernetes.deployment.name=\"dummydeployment\")andsource!=\"auditTrail\"" +0 +1 + + +``` + +#### STIX Transmit results - output +```json +{ + "success": true, + "data": [ + { + "id": "12345678910", + "cursor": "ABCDEFGHIJKLMN", + "timestamp": "2023-11-22T11:16:28.101680299Z", + "customerId": 10101010, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "sysdig custom", + "description": "updated network rules with network tag", + "severity": 60, + "agentId": 111111, + "containerId": "1010101010", + "machineId": "11:11:1b:11:11:11", + "content": { + "falsePositive": false, + "fields": { + "container.id": "1010101010", + "container.image.repository": "docker.io", + "container.image.tag": "curl", + "container.name": "curl-sample-app1", + "evt.res": "EINPROGRESS", + "evt.type": "connect", + "falco.rule": "Contact EC2", + "fd.name": "111.111.11.111:10000->101.101.101.101:10", + "group.gid": "0", + "group.name": "root", + "proc.aname[2]": "containerd-shim", + "proc.aname[3]": "systemd", + "proc.aname[4]": "", + "proc.args": "-s http://101.101.101.101:10/iam/security-credentials", + "proc.cmdline": "curl -s http://101.101.101.101:10/iam/security-credentials", + "proc.cwd": "/", + "proc.exepath": "/usr/bin/curl", + "proc.name": "curl", + "proc.pcmdline": "sh", + "proc.pid": "12345", + "proc.pname": "sh", + "proc.ppid": "11111", + "proc.sid": "1", + "user.loginname": "", + "user.loginuid": "-1", + "user.name": "root", + "user.uid": "0", + "proc.anames": [ + "containerd-shim", + "systemd" + ] + }, + "internalRuleName": "Contact", + "matchedOnDefault": false, + "origin": "Secure UI", + "output": "Outbound connection (proc.cmdline=curl -s http://101.101.101.101:10/iam/security-credentials proc.name=curl proc.args=-s http://101.101.101.101:10/iam/security-credentials proc.pname=sh gparent=containerd-shim ggparent=systemd gggparent= connection=111.111.11.111:10000->101.101.101.101:10 curl-sample-app1 (id=10101010) evt.type=connect evt.res=EINPROGRESS proc.pid=12345 proc.cwd=/ proc.ppid=11111 proc.pcmdline=sh proc.sid=1 proc.exepath=/usr/bin/curl user.uid=0 user.loginuid=-1 user.loginname= user.name=root group.gid=0 group.name=root container.id=1010101010 container.name=curl-sample-app1 image=docker.io/radial/busyboxplus:curl)", + "policyId": 100100, + "ruleName": "Contact EC2", + "ruleSubType": 0, + "ruleTags": [ + "container", + "network", + "aws", + "SOC2", + "SOC2_CC6.8", + "SOC2_CC6.1", + "NIST", + "NIST_800-171", + "NIST_800-171_3.1.1", + "NIST_800-171_3.1.2", + "NIST_800-171_3.1.3", + "NIST_800-171_3.14.6", + "NIST_800-171_3.14.7", + "NIST_800-171_3.4.6", + "NIST_800-53", + "NIST_800-53_AC-4", + "NIST_800-53_AC-17", + "NIST_800-53_SI-4(18)", + "NIST_800-53_SI-4", + "NIST_800-53_CM-7", + "FedRAMP", + "FedRAMP_CM-7", + "ISO", + "ISO_27001", + "ISO_27001_A.9.1.2", + "HIPAA", + "HIPAA_164.308(a)", + "HIPAA_164.310(b)", + "HITRUST", + "HITRUST_CSF", + "HITRUST_CSF_01.c", + "HITRUST_CSF_01.i", + "HITRUST_CSF_01.j", + "HITRUST_CSF_01.l", + "HITRUST_CSF_01.n", + "HITRUST_CSF_01.x", + "HITRUST_CSF_01.y", + "HITRUST_CSF_09.ab", + "HITRUST_CSF_09.ac", + "HITRUST_CSF_09.i", + "HITRUST_CSF_09.m", + "HITRUST_CSF_09.s", + "HITRUST_CSF_10.j", + "HITRUST_CSF_10.m", + "HITRUST_CSF_11.a", + "HITRUST_CSF_11.b", + "GDPR", + "GDPR_32.1", + "GDPR_32.2", + "MITRE", + "MITRE_T1552_unsecured_credentials", + "MITRE_T1552.005_unsecured_credentials_cloud_instance_metadata_api", + "MITRE_TA0006_credential_access", + "MITRE_TA0007_discovery", + "MITRE_T1552.007_unsecured_credentials_container_api", + "MITRE_T1033_system_owner_user_discovery", + "MITRE_T119_automated-collection", + "MITRE_TA0009_collection" + ], + "ruleType": 6 + }, + "labels": { + "aws.accountId": "111111111111", + "aws.instanceId": "i-1111111111", + "aws.region": "us-east-1", + "cloudProvider.account.id": "111111111111", + "cloudProvider.name": "aws", + "cloudProvider.region": "us-east-1", + "container.image.digest": "sha256:12345", + "container.image.id": "10101010", + "container.image.repo": "docker.io/radial/busyboxplus", + "container.image.tag": "curl", + "container.label.io.kubernetes.container.name": "curl-sample-app1", + "container.label.io.kubernetes.pod.name": "curl-sample-app1", + "container.label.io.kubernetes.pod.namespace": "default", + "container.name": "curl-sample-app1", + "host.hostName": "ip-111-111-11-111.ec2.internal", + "host.mac": "11:11:11:11:11:11", + "kubernetes.cluster.name": "sysdig", + "kubernetes.namespace.name": "default", + "kubernetes.node.name": "ip-111-111-11-111.ec2.internal", + "kubernetes.pod.name": "curl-sample-app1", + "process.name": "curl -s http://101.101.101.101/iam/security-credentials" + }, + "finding_type": "threat", + "direction": "out", + "clientIpv4": "111.111.11.111", + "clientPort": "10000", + "serverIpv4": "101.101.101.101", + "serverPort": "10", + "l4protocol": "tcp" + } + ] +} + +``` + + +#### STIX Translate results +```shell +#### STIX Translate results - output +```json +{ + "type": "bundle", + "id": "bundle--8359298b-52b0-4e63-bea9-d84598cb4b5e", + "objects": [ + { + "type": "identity", + "id": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "name": "sysdig", + "identity_class": "events", + "created": "2023-08-01T06:06:52.305Z", + "modified": "2023-08-02T06:06:52.305Z" + }, + { + "id": "observed-data--48c2872b-2cdb-4d7e-abe3-345e19e4f078", + "type": "observed-data", + "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "created": "2023-11-30T06:27:46.467Z", + "modified": "2023-11-30T06:27:46.467Z", + "objects": { + "0": { + "type": "x-ibm-finding", + "x_threat_originator": "policy", + "x_category": "runtime", + "x_threat_source": "syscall", + "x_policy_ref": "1", + "severity": 100, + "x_agent_id": 111111, + "name": "Contact EC2", + "x_cluster_ref": "12", + "finding_type": "threat" + }, + "1": { + "type": "x-sysdig-policy", + "description": "updated network rules with network tag", + "policy_id": 100100, + "rule_name": "Contact EC2", + "rule_subtype": 0, + "rule_type": 6 + }, + "2": { + "type": "x-oca-asset", + "extensions": { + "x-oca-container-ext": { + "container_id": "1010101010", + "x_digest": "sha256:12345", + "image_id": "10101010", + "x_repo": "docker.io/radial/busyboxplus", + "x_tag": "curl", + "name": "curl-sample-app1" + }, + "x-oca-pod-ext": { + "pod_name": "curl-sample-app1" + "x_namespace": "default" + } + }, + "hostname": "ip-111-111-11-111.ec2.internal", + "mac_refs": [ + "11" + ] + }, + "3": { + "type": "process", + "command_line": "curl -s http://101.101.101.101:10/iam/security-credentials", + "cwd": "/", + "name": "curl", + "binary_ref": "5", + "pid": 12345, + "parent_ref": "7", + "x_sid": "1", + "creator_user_ref": "8" + }, + "4": { + "type": "directory", + "path": "/usr/bin/curl" + }, + "5": { + "type": "file", + "parent_directory_ref": "4", + "name": "curl" + }, + "6": { + "type": "file", + "name": "sh" + }, + "7": { + "type": "process", + "command_line": "sh", + "name": "sh", + "binary_ref": "6", + "pid": 11111, + "x_parent_names": [ + "containerd-shim", + "systemd" + ] + }, + "8": { + "type": "user-account", + "x_loginuid": "-1", + "display_name": "root", + "user_id": "0" + }, + "9": { + "type": "x-cloud-provider", + "account_id": "111111111111", + "region": "us-east-1", + "name": "aws" + }, + "10": { + "type": "x-cloud-resource", + "aws_instance_id": "i-1111111111" + }, + "11": { + "type": "mac-addr", + "value": "11:11:11:11:11:11" + }, + "12": { + "type": "x-sysdig-cluster", + "name": "sysdig", + "x_node_ref": "2", + "namespace": "default" + }, + "13": { + "type": "network-traffic", + "src_ref": "14", + "src_port": 10000, + "dst_ref": "15", + "dst_port": 10, + "protocols": [ + "tcp" + ] + }, + "14": { + "type": "ipv4-addr", + "value": "111.111.11.111" + }, + "15": { + "type": "ipv4-addr", + "value": "101.101.101.101" + } + }, + "first_observed": "2023-11-22T11:16:28.101680299Z", + "last_observed": "2023-11-22T11:16:28.101680299Z", + "number_observed": 1 + } + ], + "spec_version": "2.0" +} + + +``` + +### Multiple Observation with different timestamp + +#### STIX Translate query +```shell +translate +sysdig +query +"{}" +"[x-oca-asset:extensions.'x-oca-container-ext'.container_id = '1010101010'] START t'2023-10-20T16:43:26.000Z' STOP t'2023-11-01T16:43:26.003Z' OR [x-cloud-provider:name = 'aws'] START t'2023-11-20T16:43:26.000Z' STOP t'2023-11-25T16:43:26.003Z'" +``` + +#### STIX Translate query - output + +```json +{ + "queries": [ + "from=1697820206000000000&to=1698857006003000064&filter=(containerId=\"1010101010\")andsource!=\"auditTrail\"", + "from=1700498606000000000&to=1700930606003000064&filter=(cloudProvider.name=\"aws\")andsource!=\"auditTrail\"" + ] +} +``` + +#### STIX Transmit results + +```shell +transmit +sysdig +"{\"host\": \"dummyhost\", \"port\": 123}" +"{\"auth\": {\"token\": \"abcdefghijklm\"}}" +results +"from=1697820206000000000&to=1698857006003000064&filter=(containerId=\"1010101010\")andsource!=\"auditTrail\"" +0 +1 + +transmit +sysdig +"{\"host\": \"dummyhost\", \"port\": 123}" +"{\"auth\": {\"token\": \"abcdefghijklm\"}}" +results +"from=1700498606000000000&to=1700930606003000064&filter=(cloudProvider.name=\"aws\")andsource!=\"auditTrail\"" +0 +1 + +``` + +#### STIX Transmit results - output +```json +{ + "success": true, + "data": [ + { + "id": "12345678910", + "cursor": "ABCDEFGHIJKLM", + "timestamp": "2023-10-25T04:10:56.471969414Z", + "customerId": 101010, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "Sysdig Runtime Notable", + "description": "This Notable Events policy contains rules which may indicate undesired behavior including security threats.", + "severity": 60, + "agentId":11111111, + "containerId": "1010101010", + "machineId": "00:00:aa:a0:a0:0a", + "content": { + "falsePositive": false, + "fields": { + "container.id": "1010101010", + "container.image.repository": "quay.io/openshift-release-dev", + "container.image.tag": "", + "container.name": "sti-build", + "evt.res": "SUCCESS", + "evt.type": "execve", + "falco.rule": "Launch Package Management Process", + "group.gid": "0", + "group.name": "root", + "proc.aname[2]": "exe", + "proc.aname[3]": "openshift-sti-b", + "proc.aname[4]": "conmon", + "proc.cmdline": "pip /opt/app-root/bin/pip install -r requirements.txt", + "proc.cwd": "/opt/app-root/src/", + "proc.exepath": "/opt/app-root/bin/pip", + "proc.name": "pip", + "proc.pcmdline": "assemble /usr/libexec/s2i/assemble", + "proc.pid": "100000", + "proc.pname": "assemble", + "proc.ppid": "101010", + "proc.sid": "1000", + "user.loginname": "", + "user.loginuid": "-1", + "user.name": "default", + "user.uid": "100", + "proc.anames": [ + "exe", + "openshift-sti-b", + "conmon" + ] + }, + "internalRuleName": "Launch Package Management", + "matchedOnDefault": false, + "origin": "Sysdig", + "output": "Package management process launched in container (user.name=default user.loginuid=-1 proc.name=pip proc.pname=assemble gparent=exe ggparent=openshift-sti-b gggparent=conmon proc.cmdline=pip /opt/app-root/bin/pip install -r requirements.txt container.id=1010101010 container_name=sti-build evt.type=execve evt.res=SUCCESS proc.pid=100000 proc.cwd=/opt/app-root/src/ proc.ppid=195665 proc.pcmdline=assemble /usr/libexec/s2i/assemble proc.sid=111 proc.exepath=/opt/app-root/bin/pip user.uid=100 user.loginname= group.gid=0 group.name=root container.name=sti-build image=quay.io)", + "policyId": 1000000, + "ruleName": "Launch Package Management Process", + "ruleSubType": 0, + "ruleTags": [ + "container", + "process", + "SOC2", + "SOC2_CC6.1", + "NIST", + "NIST_800-171", + "NIST_800-171_3.14.1", + "NIST_800-171_3.14.2", + "NIST_800-171_3.14.3", + "NIST_800-171_3.14.4", + "NIST_800-171_3.14.5", + "NIST_800-171_3.14.6", + "NIST_800-171_3.14.7", + "NIST_800-171_3.4.5", + "NIST_800-53", + "NIST_800-53_CM-5", + "NIST_800-53_SI-7", + "NIST_800-53_SI-4", + "NIST_800-53_SI-3", + "FedRAMP", + "FedRAMP_SI-3", + "ISO", + "ISO_27001", + "ISO_27001_A.12.5.1", + "ISO_27001_A.12.6.2", + "ISO_27001_A.14.2.4", + "HIPAA", + "HIPAA_164.308(a)", + "HIPAA_164.312(c)", + "HIPAA_164.312(e)", + "HITRUST", + "HITRUST_CSF", + "HITRUST_CSF_01.x", + "HITRUST_CSF_09.ab", + "HITRUST_CSF_09.ac", + "HITRUST_CSF_09.b", + "HITRUST_CSF_09.j", + "HITRUST_CSF_09.k", + "HITRUST_CSF_09.m", + "HITRUST_CSF_10.c", + "HITRUST_CSF_10.j", + "HITRUST_CSF_10.k", + "HITRUST_CSF_11.a", + "HITRUST_CSF_11.b", + "GDPR", + "GDPR_32.1", + "GDPR_32.2", + "MITRE", + "MITRE_T1068_exploitation_for_privilege_escalation", + "MITRE_TA0003_persistence", + "MITRE_TA0004_privilege_escalation", + "MITRE_T1569_system_services", + "MITRE_TA0042_resource_development", + "MITRE_TA0002_execution", + "MITRE_T1608.002_stage_capabilities_upload_tool" + ], + "ruleType": 6 + }, + "labels": { + "container.image.digest": "sha256:12345", + "container.image.id": "12345", + "container.image.repo": "quay.io/openshift-release-dev", + "container.label.io.kubernetes.container.name": "sti-build", + "container.label.io.kubernetes.pod.name": "django-psql-example", + "container.label.io.kubernetes.pod.namespace": "sample-app2", + "container.name": "sti-build", + "host.hostName": "kube-ck8r6iht033m28pqg7ug-cp4scluster-default-000001cc.iks.ibm", + "host.mac": "00:a0:00:a0:a0:4a", + "kubernetes.cluster.name": "cluster2", + "kubernetes.namespace.name": "sample-app2", + "kubernetes.node.name": "10.100.10.100", + "kubernetes.pod.name": "example-1-build", + "process.name": "pip /opt/app-root/bin/pip install" + }, + "finding_type": "threat" + } + ] +} + +``` +```json +{ + "success": true, + "data": [ + { + "id": "12345678910", + "cursor": "ABCDEFGHIJKLM", + "timestamp": "2023-11-25T16:40:29.896484649Z", + "customerId": 12345, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "Sysdig Runtime Activity Logs", + "description": "This policy contains rules which provide a greater insight into general activities occuring on the system. They are very noisy, but useful in threat hunting situations if you are looking for specific actions being taken during runtime. It is not recommended to use this policy for detection purposes unless tuning is enabled. Additional manual tuning will likely be required.", + "severity": 70, + "agentId": 5555555, + "containerId": "10101010", + "machineId": "11:aa:1a:1a:1a:1a", + "content": { + "falsePositive": false, + "fields": { + "container.id": "10101010", + "container.image.repository": "docker.io/curlimages/curl", + "container.name": "curl-test1", + "evt.arg.uid": "curl_user", + "evt.res": "", + "evt.type": "setuid", + "falco.rule": "Non sudo setuid", + "group.gid": "101", + "group.name": "curl_group", + "proc.aname[2]": "systemd", + "proc.aname[3]": "", + "proc.aname[4]": "", + "proc.cmdline": "entrypoint.sh /entrypoint.sh bash", + "proc.cwd": "/home/curl_user/", + "proc.exepath": "/entrypoint.sh", + "proc.name": "entrypoint.sh", + "proc.pcmdline": "containerd-shim -namespace k8s.io -address /run/containerd/containerd.sock", + "proc.pid": "111111", + "proc.pname": "containerd-shim", + "proc.ppid": "000000", + "proc.sid": "1", + "user.loginname": "", + "user.loginuid": "-1", + "user.name": "curl_user", + "user.uid": "100", + "proc.anames": [ + "systemd" + ] + }, + "internalRuleName": "Non sudo setuid", + "matchedOnDefault": false, + "origin": "Sysdig", + "output": "Unexpected setuid call by non-sudo, non-root program (user.name=curl_user user_loginuid=-1 user.uid=100 proc.name=entrypoint.sh parent=containerd-shim gparent=systemd ggparent= gggparent= proc.cmdline=entrypoint.sh /entrypoint.sh bash uid=curl_user container.id=10101010 evt.type=setuid evt.res= proc.pid=111111 proc.cwd=/home/curl_user/ proc.ppid=000000 proc.pcmdline=containerd-shim -namespace k8s.io -id -address /run/containerd/containerd.sock proc.sid=1 proc.exepath=/entrypoint.sh user.loginuid=-1 user.loginname= group.gid=101 group.name=curl_group container.name=curl-test1 image=docker.io/curlimages/curl)", + "policyId": 100000, + "ruleName": "Non sudo setuid", + "ruleSubType": 0, + "ruleTags": [ + "host", + "container", + "users", + "SOC2", + "SOC2_CC6.1", + "NIST", + "NIST_800-53", + "NIST_800-53_AC-6(9)", + "NIST_800-53_AC-6(10)", + "NIST_800-53_AU-6(8)", + "ISO", + "ISO_27001", + "ISO_27001_A.9.2.3", + "HIPAA", + "HIPAA_164.308(a)", + "HIPAA_164.312(a)", + "HIPAA_164.312(b)", + "HITRUST", + "HITRUST_CSF", + "HITRUST_CSF_01.c", + "HITRUST_CSF_09.aa", + "GDPR", + "GDPR_32.1", + "GDPR_32.2", + "MITRE", + "MITRE_TA0005_defense_evasion", + "MITRE_T1548.001_abuse_elevation_control_mechanism_setuid_and_setgid MITRE_TA0004_privilege_escalation", + "MITRE_T1222_file_and_directory_permissions_modification", + "MITRE_T1222.002_file_and_directory_permissions_modification_linux_and_mac_file_and_directory" + ], + "ruleType": 6 + }, + "labels": { + "aws.accountId": "12345", + "aws.instanceId": "i-000000000", + "aws.region": "us-east-1", + "cloudProvider.account.id": "12345", + "cloudProvider.name": "aws", + "cloudProvider.region": "us-east-1", + "container.image.digest": "sha256:12345", + "container.image.id": "1111111", + "container.image.repo": "docker.io/curlimages", + "container.image.tag": "latest", + "container.label.io.kubernetes.container.name": "curl-test1", + "container.label.io.kubernetes.pod.name": "curl-test1", + "container.label.io.kubernetes.pod.namespace": "default", + "container.name": "curl-test1", + "host.hostName": "ip-111-111-11-111.ec2.internal", + "host.mac": "11:aa:11:1a:1a:1a", + "kubernetes.cluster.name": "sysdig", + "kubernetes.namespace.name": "default", + "kubernetes.node.name": "ip-111-111-11-111.ec2.internal", + "kubernetes.pod.name": "curl-test1", + "process.name": "entrypoint.sh /entrypoint.sh bash" + }, + "finding_type": "threat" + } + ] +} +``` + + +#### STIX Translate results +```shell +#### STIX Translate results - output +```json +{ + "type": "bundle", + "id": "bundle--290b25ca-8c55-4784-8a52-8fdcd7561ae1", + "objects": [ + { + "type": "identity", + "id": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "name": "sysdig", + "identity_class": "events", + "created": "2023-08-01T06:06:52.305Z", + "modified": "2023-08-02T06:06:52.305Z" + }, + { + "id": "observed-data--93c77df8-384c-458a-bfa4-1bcba273f14d", + "type": "observed-data", + "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "created": "2023-11-30T11:03:35.393Z", + "modified": "2023-11-30T11:03:35.393Z", + "objects": { + "0": { + "type": "x-ibm-finding", + "x_threat_originator": "policy", + "x_category": "runtime", + "x_threat_source": "syscall", + "x_policy_ref": "1", + "severity": 60, + "x_agent_id": 11111111, + "name": "Launch Package Management Process", + "x_cluster_ref": "10", + "finding_type": "threat" + }, + "1": { + "type": "x-sysdig-policy", + "description": "This Notable Events policy contains rules which may indicate undesired behavior including security threats.", + "policy_id": 1000000, + "rule_name": "Launch Package Management Process", + "rule_subtype": 0, + "rule_type": 6 + }, + "2": { + "type": "x-oca-asset", + "extensions": { + "x-oca-container-ext": { + "container_id": "1010101010", + "x_digest": "sha256:12345", + "image_id": "12345", + "x_repo": "quay.io/openshift-release-dev", + "name": "sti-build" + }, + "x-oca-pod-ext": { + "pod_name": "django-psql-example" + "x_namespace": "sample-app2" + } + }, + "hostname": "kube-ck8r6iht033m28pqg7ug-cp4scluster-default-000001cc.iks.ibm", + "mac_refs": [ + "9" + ], + "ip_refs": [ + "11" + ] + }, + "3": { + "type": "process", + "command_line": "pip /opt/app-root/bin/pip install -r requirements.txt", + "cwd": "/opt/app-root/src/", + "name": "pip", + "binary_ref": "5", + "pid": 100000, + "parent_ref": "7", + "x_sid": "1000", + "creator_user_ref": "8" + }, + "4": { + "type": "directory", + "path": "/opt/app-root/bin/pip" + }, + "5": { + "type": "file", + "parent_directory_ref": "4", + "name": "pip" + }, + "6": { + "type": "file", + "name": "assemble" + }, + "7": { + "type": "process", + "command_line": "assemble /usr/libexec/s2i/assemble", + "name": "assemble", + "binary_ref": "6", + "pid": 101010, + "x_parent_names": [ + "exe", + "openshift-sti-b", + "conmon" + ] + }, + "8": { + "type": "user-account", + "x_loginuid": "-1", + "display_name": "default", + "user_id": "100" + }, + "9": { + "type": "mac-addr", + "value": "00:a0:00:a0:a0:4a" + }, + "10": { + "type": "x-sysdig-cluster", + "name": "cluster2", + "x_node_ref": "2", + "namespace": "sample-app2" + }, + "11": { + "type": "ipv4-addr", + "value": "10.100.10.100" + } + }, + "first_observed": "2023-10-25T04:10:56.471969414Z", + "last_observed": "2023-10-25T04:10:56.471969414Z", + "number_observed": 1 + } + ], + "spec_version": "2.0" +} + +``` + +```json +{ + "type": "bundle", + "id": "bundle--de03b315-0a87-4d67-8154-c808f64b87bc", + "objects": [ + { + "type": "identity", + "id": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "name": "sysdig", + "identity_class": "events", + "created": "2023-08-01T06:06:52.305Z", + "modified": "2023-08-02T06:06:52.305Z" + }, + { + "id": "observed-data--8ab06af8-f8ba-4574-b3c6-e5997430b65a", + "type": "observed-data", + "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "created": "2023-11-30T11:14:05.203Z", + "modified": "2023-11-30T11:14:05.203Z", + "objects": { + "0": { + "type": "x-ibm-finding", + "x_threat_originator": "policy", + "x_category": "runtime", + "x_threat_source": "syscall", + "x_policy_ref": "1", + "severity": 30, + "x_agent_id": 5555555, + "name": "Non sudo setuid", + "x_cluster_ref": "12", + "finding_type": "threat" + }, + "1": { + "type": "x-sysdig-policy", + "description": "This policy contains rules which provide a greater insight into general activities occuring on the system. They are very noisy, but useful in threat hunting situations if you are looking for specific actions being taken during runtime. It is not recommended to use this policy for detection purposes unless tuning is enabled. Additional manual tuning will likely be required.", + "policy_id": 100000, + "rule_name": "Non sudo setuid", + "rule_subtype": 0, + "rule_type": 6 + }, + "2": { + "type": "x-oca-asset", + "extensions": { + "x-oca-container-ext": { + "container_id": "10101010", + "x_digest": "sha256:12345", + "image_id": "1111111", + "x_repo": "docker.io/curlimages", + "x_tag": "latest", + "name": "curl-test1" + }, + "x-oca-pod-ext": { + "pod_name": "curl-test1", + "x_namespace": "default" + } + }, + "hostname": "ip-111-111-11-111.ec2.internal", + "mac_refs": [ + "11" + ] + }, + "3": { + "type": "process", + "command_line": "entrypoint.sh /entrypoint.sh bash", + "cwd": "/home/curl_user/", + "name": "entrypoint.sh", + "binary_ref": "5", + "pid": 111111, + "parent_ref": "7", + "x_sid": "1", + "creator_user_ref": "8" + }, + "4": { + "type": "directory", + "path": "/entrypoint.sh" + }, + "5": { + "type": "file", + "parent_directory_ref": "4", + "name": "entrypoint.sh" + }, + "6": { + "type": "file", + "name": "containerd-shim" + }, + "7": { + "type": "process", + "command_line": "containerd-shim -namespace k8s.io -address /run/containerd/containerd.sock", + "name": "containerd-shim", + "binary_ref": "6", + "pid": 0, + "x_parent_names": [ + "systemd" + ] + }, + "8": { + "type": "user-account", + "x_loginuid": "-1", + "display_name": "curl_user", + "user_id": "100" + }, + "9": { + "type": "x-cloud-provider", + "account_id": "12345", + "region": "us-east-1", + "name": "aws" + }, + "10": { + "type": "x-cloud-resource", + "aws_instance_id": "i-000000000" + }, + "11": { + "type": "mac-addr", + "value": "11:aa:11:1a:1a:1a" + }, + "12": { + "type": "x-sysdig-cluster", + "name": "sysdig", + "x_node_ref": "2", + "namespace": "default" + } + }, + "first_observed": "2023-11-25T16:40:29.896484649Z", + "last_observed": "2023-11-25T16:40:29.896484649Z", + "number_observed": 1 + } + ], + "spec_version": "2.0" +} + +``` + + +#### STIX Execute query +```shell +execute sysdig sysdig "{\"type\":\"identity\",\"id\":\"identity--f431f809-377b-45e0-aa1c-6a4751cae5ff\",\"name\":\"sysdig\",\"identity_class\":\"events\", \"created\":\"2022-05-22T13:22:50.336Z\",\"modified\":\"2022-05-25T13:22:50.336Z\"}" "{\"host\":\"dummyhost\"}" "{\"auth\":{\"token\":\"abcdefghijkl\"}}" "[x-ibm-finding:severity!=100]START t'2023-10-25T16:43:26.000Z' STOP t'2023-11-05T16:43:26.003Z'" + +``` + +#### STIX Execute query - output +```json +{ + "queries": [ + "from=1698252206000000000&to=1699202606003000064&filter=(severity!=0)andsource!=\"auditTrail\"" + ] +} +``` +```json +{ + "type": "bundle", + "id": "bundle--e0cc0a4d-13f7-4746-bf1c-5448679f68da", + "objects": [ + { + "type": "identity", + "id": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "name": "sysdig", + "identity_class": "events", + "created": "2023-04-11T16:11:11.878Z", + "modified": "2023-11-10T16:11:11.878Z" + }, + { + "id": "observed-data--07d7dc18-2103-4ae5-974b-1f2044dd5246", + "type": "observed-data", + "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "created": "2023-11-30T09:50:33.962Z", + "modified": "2023-11-30T09:50:33.962Z", + "objects": { + "0": { + "type": "x-ibm-finding", + "x_threat_originator": "policy", + "x_category": "runtime", + "x_threat_source": "syscall", + "x_policy_ref": "1", + "severity": 30, + "x_agent_id": 101010, + "name": "Launch Sensitive", + "x_cluster_ref": "10", + "x_workload_name": "prometheus-prometheus-node", + "x_workload_type": "daemonset", + "finding_type": "threat" + }, + "1": { + "type": "x-sysdig-policy", + "description": "This Notable Events policy contains rules which may indicate undesired behavior including security threats. The rules are more generalized than Threat Detection policies and may result in more noise. Tuning will likely be required for the events generated from this policy.", + "policy_id": 111111, + "rule_name": "Launch Sensitive", + "rule_subtype": 0, + "rule_type": 6 + }, + "2": { + "type": "x-oca-asset", + "extensions": { + "x-oca-container-ext": { + "container_id": "1111111", + "x_digest": "sha256:12345", + "image_id": "1000000", + "x_repo": "quay.io/prometheus", + "x_tag": "v1.6.1", + "name": "node-exporter" + }, + "x-oca-pod-ext": { + "pod_name": "prometheus-prometheus-node-exporter-52glj", + "x_namespace": "prometheus" + } + }, + "hostname": "ip-111-111-11-11.ec2.internal", + "mac_refs": [ + "9" + ] + }, + "3": { + "type": "process", + "command_line": "container:1111111", + "name": "container:11111111", + "binary_ref": "4", + "pid": -1, + "x_sid": "-1", + "creator_user_ref": "6" + }, + "4": { + "type": "file", + "name": "container:1111111" + }, + "5": { + "type": "process", + "pid": -1 + }, + "6": { + "type": "user-account", + "x_loginuid": "0", + "display_name": "12345", + "user_id": "0" + }, + "7": { + "type": "x-cloud-provider", + "account_id": "12345678910", + "region": "us-east-1", + "name": "aws" + }, + "8": { + "type": "x-cloud-resource", + "aws_instance_id": "i-111111111" + }, + "9": { + "type": "mac-addr", + "value": "11:11:11:1f:11:bb" + }, + "10": { + "type": "x-sysdig-cluster", + "name": "sysdig-cluster1", + "x_node_ref": "2", + "daemonset": "prometheus-prometheus-node", + "namespace": "prometheus" + } + }, + "first_observed": "2023-11-01T17:15:14.635115435Z", + "last_observed": "2023-11-01T17:15:14.635115435Z", + "number_observed": 1 + } + ] +} +``` + +### Limitations +- Query timestamp range should be upto maximum of 14 days +- Sysdig does not support LIKE, MATCHES operators + +### References +- [Sysdig] (https://docs.sysdig.com/en/docs/sysdig-secure/) +- [Sysdig API] (https:///secure/swagger.html#tag/Secure-Events) \ No newline at end of file diff --git a/stix_shifter_modules/sysdig/__init__.py b/stix_shifter_modules/sysdig/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stix_shifter_modules/sysdig/configuration/config.json b/stix_shifter_modules/sysdig/configuration/config.json new file mode 100644 index 000000000..1ec653472 --- /dev/null +++ b/stix_shifter_modules/sysdig/configuration/config.json @@ -0,0 +1,34 @@ +{ + "connection": { + "type": { + "displayName": "Sysdig", + "group": "sysdig" + }, + "host": { + "type": "text", + "regex": "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9_:/\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9_:/\\-]*[A-Za-z0-9])$" + }, + "port": { + "type": "number", + "default": 443, + "min": 1, + "max": 65535 + }, + "help": { + "type": "link", + "default": "data-sources.html" + }, + "selfSignedCert": { + "type": "password", + "optional": true + } + }, + "configuration": { + "auth": { + "type" : "fields", + "token": { + "type": "password" + } + } + } +} \ No newline at end of file diff --git a/stix_shifter_modules/sysdig/configuration/lang_en.json b/stix_shifter_modules/sysdig/configuration/lang_en.json new file mode 100644 index 000000000..66e3235bb --- /dev/null +++ b/stix_shifter_modules/sysdig/configuration/lang_en.json @@ -0,0 +1,28 @@ +{ + "connection": { + "host": { + "label": "Management IP address or hostname", + "description": "Specify the IP address or hostname of the data source" + }, + "port": { + "label": "Host port", + "description": "Set the port number that is associated with the hostname or IP address" + }, + "help": { + "label": "Need additional help?", + "description": "More details on the data source setting can be found in the specified link" + }, + "selfSignedCert": { + "label": "Sysdig connection certificate", + "description": "Use self-signed SSL certificate or CA content(root and intermediate) of data source" + } + }, + "configuration": { + "auth": { + "token": { + "label": "API token", + "description": "Token with readonly access to the Sysdig API" + } + } + } +} \ No newline at end of file diff --git a/stix_shifter_modules/sysdig/entry_point.py b/stix_shifter_modules/sysdig/entry_point.py new file mode 100644 index 000000000..b79e0bf91 --- /dev/null +++ b/stix_shifter_modules/sysdig/entry_point.py @@ -0,0 +1,12 @@ +from stix_shifter_utils.utils.base_entry_point import BaseEntryPoint + + +class EntryPoint(BaseEntryPoint): + + def __init__(self, connection={}, configuration={}, options={}): + super().__init__(connection, configuration, options) + self.set_async(False) + if connection: + self.setup_transmission_basic(connection, configuration) + + self.setup_translation_simple(dialect_default='default') diff --git a/stix_shifter_modules/sysdig/stix_translation/__init__.py b/stix_shifter_modules/sysdig/stix_translation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stix_shifter_modules/sysdig/stix_translation/json/config_map.json b/stix_shifter_modules/sysdig/stix_translation/json/config_map.json new file mode 100644 index 000000000..05c4a9260 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/json/config_map.json @@ -0,0 +1,33 @@ +{ + "int_supported_fields": [ + "severity", + "ruleType", + "ruleSubType", + "policyId", + "agentId", + "aws.accountId" + ], + "string_supported_fields": [ + "kubernetes.cluster.name", + "kubernetes.namespace.name", + "kubernetes.deployment.name", + "containerId", + "container.name", + "container.image.id", + "container.image.repo", + "container.image.tag", + "container.image.digest", + "container.label.io.kubernetes.pod.name", + "container.label.io.kubernetes.pod.namespace", + "ruleName", + "category", + "originator", + "source", + "host.hostName", + "cloudProvider.name", + "aws.region" + ], + "mac_supported_fields": [ + "machineId" + ] +} diff --git a/stix_shifter_modules/sysdig/stix_translation/json/from_stix_map.json b/stix_shifter_modules/sysdig/stix_translation/json/from_stix_map.json new file mode 100644 index 000000000..d8700b3d2 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/json/from_stix_map.json @@ -0,0 +1,110 @@ +{ + "mac-addr": { + "fields": { + "value": [ + "machineId" + ] + } + }, + "x-oca-asset": { + "fields": { + "hostname": [ + "host.hostName" + ], + "extensions.'x-oca-container-ext'.container_id": [ + "containerId" + ], + "extensions.'x-oca-container-ext'.name": [ + "container.name" + ], + "extensions.'x-oca-container-ext'.image_id": [ + "container.image.id" + ], + "extensions.'x-oca-container-ext'.x_repo": [ + "container.image.repo" + ], + "extensions.'x-oca-container-ext'.x_tag": [ + "container.image.tag" + ], + "extensions.'x-oca-container-ext'.x_digest": [ + "container.image.digest" + ], + "extensions.'x-oca-pod-ext'.pod_name": [ + "container.label.io.kubernetes.pod.name" + ], + "extensions.'x-oca-pod-ext'.x_namespace": [ + "container.label.io.kubernetes.pod.namespace" + ] + } + }, + "x-ibm-finding": { + "fields": { + "name": [ + "ruleName" + ], + "severity": [ + "severity" + ], + "x_category": [ + "category" + ], + "x_threat_originator": [ + "originator" + ], + "x_threat_source": [ + "source" + ], + "x_agent_id": [ + "agentId" + ] + } + }, + "x-sysdig-cluster": { + "fields": { + "name": [ + "kubernetes.cluster.name" + ], + "namespace": [ + "kubernetes.namespace.name" + ] + } + }, + "x-sysdig-deployment": { + "fields": { + "name": [ + "kubernetes.deployment.name" + ] + } + }, + "x-sysdig-policy": { + "fields": { + "rule_name": [ + "ruleName" + ], + "rule_type": [ + "ruleType" + ], + "rule_subtype": [ + "ruleSubType" + ], + "policy_id": [ + "policyId" + ] + } + }, + "x-cloud-provider": { + "fields": { + "account_id": [ + "aws.accountId" + ], + "name": [ + "cloudProvider.name" + ], + "region": [ + "aws.region" + ] + } + } +} + + diff --git a/stix_shifter_modules/sysdig/stix_translation/json/operators.json b/stix_shifter_modules/sysdig/stix_translation/json/operators.json new file mode 100644 index 000000000..0b96088fa --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/json/operators.json @@ -0,0 +1,13 @@ +{ + "ComparisonExpressionOperators.And": "and", + "ComparisonExpressionOperators.Or": "or", + "ComparisonComparators.Equal": "=", + "ComparisonComparators.NotEqual": "!=", + "ComparisonComparators.GreaterThan": ">", + "ComparisonComparators.GreaterThanOrEqual": ">=", + "ComparisonComparators.LessThan": "<", + "ComparisonComparators.LessThanOrEqual": "<=", + "ComparisonComparators.In": "in", + "ObservationOperators.Or": "or", + "ObservationOperators.And": "or" +} \ No newline at end of file diff --git a/stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/from_stix_map.json b/stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/from_stix_map.json new file mode 100644 index 000000000..c63d547c4 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/from_stix_map.json @@ -0,0 +1,110 @@ +{ + "mac-addr": { + "fields": { + "value": [ + "machineId" + ] + } + }, + "x-oca-asset": { + "fields": { + "hostname": [ + "host.hostName" + ], + "extensions.'x-oca-container-ext'.container_id": [ + "containerId" + ], + "extensions.'x-oca-container-ext'.name": [ + "container.name" + ], + "extensions.'x-oca-container-ext'.image_id": [ + "container.image.id" + ], + "extensions.'x-oca-container-ext'.x_repo": [ + "container.image.repo" + ], + "extensions.'x-oca-container-ext'.x_tag": [ + "container.image.tag" + ], + "extensions.'x-oca-container-ext'.x_digest": [ + "container.image.digest" + ], + "extensions.'x-oca-pod-ext'.pod_name": [ + "container.label.io.kubernetes.pod.name" + ], + "extensions.'x-oca-pod-ext'.x_namespace": [ + "container.label.io.kubernetes.pod.namespace" + ] + } + }, + "x-ibm-finding": { + "fields": { + "name": [ + "ruleName" + ], + "x_severity": [ + "severity" + ], + "x_category": [ + "category" + ], + "x_threat_originator": [ + "originator" + ], + "x_threat_source": [ + "source" + ], + "x_agent_id": [ + "agentId" + ] + } + }, + "x-sysdig-cluster": { + "fields": { + "name": [ + "kubernetes.cluster.name" + ], + "namespace": [ + "kubernetes.namespace.name" + ] + } + }, + "x-sysdig-deployment": { + "fields": { + "name": [ + "kubernetes.deployment.name" + ] + } + }, + "x-sysdig-policy": { + "fields": { + "rule_name": [ + "ruleName" + ], + "rule_type": [ + "ruleType" + ], + "rule_subtype": [ + "ruleSubType" + ], + "policy_id": [ + "policyId" + ] + } + }, + "x-cloud-provider": { + "fields": { + "account_id": [ + "aws.accountId" + ], + "name": [ + "cloudProvider.name" + ], + "region": [ + "aws.region" + ] + } + } +} + + diff --git a/stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/to_stix_map.json b/stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/to_stix_map.json new file mode 100644 index 000000000..fdb11c7f8 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/to_stix_map.json @@ -0,0 +1,332 @@ +{ + "category": { + "key": "x-ibm-finding.x_category", + "object": "finding" + }, + "originator": { + "key": "x-ibm-finding.x_threat_originator", + "object": "finding" + }, + "source": { + "key": "x-ibm-finding.x_threat_source", + "object": "finding" + }, + "agentId": { + "key": "x-ibm-finding.x_agent_id", + "object": "finding" + }, + "finding_type": { + "key": "x-ibm-finding.finding_type", + "object": "finding" + }, + "l4protocol": { + "key": "network-traffic.protocols", + "object": "network", + "transformer": "ToLowercaseArray" + }, + "clientPort": { + "key": "network-traffic.src_port", + "object": "network", + "transformer": "ToInteger" + }, + "serverPort": { + "key": "network-traffic.dst_port", + "object": "network", + "transformer": "ToInteger" + }, + "serverIpv4": [ + { + "key": "ipv4-addr.value", + "object": "dst_ip" + }, + { + "key": "network-traffic.dst_ref", + "object": "network", + "references": "dst_ip" + } + ], + "clientIpv4": [ + { + "key": "ipv4-addr.value", + "object": "src_ip" + }, + { + "key": "network-traffic.src_ref", + "object": "network", + "references": "src_ip" + } + ], + "severity": { + "key": "x-ibm-finding.x_severity", + "object": "finding", + "transformer": "SeverityToScore" + }, + "containerId": { + "key": "x-oca-asset.extensions.x-oca-container-ext.container_id", + "object": "asset" + }, + "description": [ + { + "key": "x-sysdig-policy.description", + "object": "policy" + }, + { + "key": "x-ibm-finding.x_policy_ref", + "object": "finding", + "references": "policy" + } + ], + "content": { + "ruleName": { + "key": "x-sysdig-policy.rule_name", + "object": "policy" + }, + "ruleType": { + "key": "x-sysdig-policy.rule_type", + "object": "policy" + }, + "ruleSubType": { + "key": "x-sysdig-policy.rule_subtype", + "object": "policy" + }, + "policyId": { + "key": "x-sysdig-policy.policy_id", + "object": "policy" + }, + "fields": { + "falco.rule": { + "key": "x-ibm-finding.name", + "object": "finding" + }, + "proc.cmdline": { + "key": "process.command_line", + "object": "proc" + }, + "proc.name": [ + { + "key": "file.name", + "object": "file" + }, + { + "key": "process.name", + "object": "proc" + }, + { + "key": "process.binary_ref", + "object": "proc", + "references": "file" + } + ], + "proc.pid": { + "key": "process.pid", + "object": "proc", + "transformer": "ToInteger" + }, + "proc.sid": { + "key": "process.x_sid", + "object": "proc" + }, + "proc.exepath": [ + { + "key": "directory.path", + "object": "file_dir" + }, + { + "key": "file.parent_directory_ref", + "object": "file", + "references": "file_dir" + } + ], + "proc.cwd": { + "key": "process.cwd", + "object": "proc" + }, + "proc.pname": [ + { + "key": "file.name", + "object": "parent_file" + }, + { + "key": "process.name", + "object": "parent_proc" + }, + { + "key": "process.parent_ref", + "object": "proc", + "references": "parent_proc" + }, + { + "key": "process.binary_ref", + "object": "parent_proc", + "references": "parent_file" + } + ], + "proc.pcmdline": { + "key": "process.command_line", + "object": "parent_proc" + }, + "proc.ppid": { + "key": "process.pid", + "object": "parent_proc", + "transformer": "ToInteger" + }, + "proc.anames": { + "key": "process.x_parent_names", + "object": "parent_proc" + }, + "user.loginname": { + "key": "user-account.account_login", + "object": "user" + }, + "user.loginuid": { + "key": "user-account.x_loginuid", + "object": "user" + }, + "user.name": [ + { + "key": "user-account.display_name", + "object": "user" + }, + { + "key": "process.creator_user_ref", + "object": "proc", + "references": "user" + } + ], + "user.uid": { + "key": "user-account.user_id", + "object": "user" + } + } + }, + "labels": { + "host.hostName": { + "key": "x-oca-asset.hostname", + "object": "asset" + }, + "container.image.digest": { + "key": "x-oca-asset.extensions.x-oca-container-ext.x_digest", + "object": "asset" + }, + "container.image.id": { + "key": "x-oca-asset.extensions.x-oca-container-ext.image_id", + "object": "asset" + }, + "container.image.tag": { + "key": "x-oca-asset.extensions.x-oca-container-ext.x_tag", + "object": "asset" + }, + "container.image.repo": { + "key": "x-oca-asset.extensions.x-oca-container-ext.x_repo", + "object": "asset" + }, + "container.label.io.kubernetes.pod.name": { + "key": "x-oca-asset.extensions.x-oca-pod-ext.pod_name", + "object": "asset" + }, + "container.label.io.kubernetes.pod.namespace": { + "key": "x-oca-asset.extensions.x-oca-pod-ext.x_namespace", + "object": "asset" + }, + "container.name": { + "key": "x-oca-asset.extensions.x-oca-container-ext.name", + "object": "asset" + }, + "host.mac": [ + { + "key": "mac-addr.value", + "object": "mac" + }, + { + "key": "x-oca-asset.mac_refs", + "object": "asset", + "references": [ + "mac" + ] + } + ], + "kubernetes.cluster.name": [ + { + "key": "x-sysdig-cluster.name", + "object": "cluster" + }, + { + "key": "x-ibm-finding.x_cluster_ref", + "object": "finding", + "references": "cluster" + }, + { + "key": "x-sysdig-cluster.x_node_ref", + "object": "cluster", + "references": "asset" + } + ], + "kubernetes.daemonSet.name": { + "key": "x-sysdig-cluster.daemonset", + "object": "cluster" + }, + "kubernetes.namespace.name": { + "key": "x-sysdig-cluster.namespace", + "object": "cluster" + }, + "kubernetes.deployment.name": [ + { + "key": "x-sysdig-deployment.name", + "object": "deployment" + }, + { + "key": "x-ibm-finding.x_deployment_ref", + "object": "finding", + "references": "deployment" + } + ], + "kubernetes.node.name": [ + { + "key": "ipv4-addr.value", + "object": "ip", + "transformer": "HostnameToIpAddress" + }, + { + "key": "x-oca-asset.ip_refs", + "object": "asset", + "references": [ + "ip" + ] + } + ], + "kubernetes.workload.name": { + "key": "x-ibm-finding.x_workload_name", + "object": "finding" + }, + "kubernetes.workload.type": { + "key": "x-ibm-finding.x_workload_type", + "object": "finding" + }, + "aws.accountId": { + "key": "x-cloud-provider.account_id", + "object": "cloud_provider" + }, + "cloudProvider.name": { + "key": "x-cloud-provider.name", + "object": "cloud_provider" + }, + "aws.region": { + "key": "x-cloud-provider.region", + "object": "cloud_provider" + }, + "aws.instanceId": { + "key": "x-cloud-resource.aws_instance_id", + "object": "cloud_resource" + } + }, + "timestamp": [ + { + "key": "first_observed", + "transformer": "TimestampConversion" + }, + { + "key": "last_observed", + "transformer": "TimestampConversion" + } + ] +} \ No newline at end of file diff --git a/stix_shifter_modules/sysdig/stix_translation/json/to_stix_map.json b/stix_shifter_modules/sysdig/stix_translation/json/to_stix_map.json new file mode 100644 index 000000000..3d3209359 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/json/to_stix_map.json @@ -0,0 +1,332 @@ +{ + "category": { + "key": "x-ibm-finding.x_category", + "object": "finding" + }, + "originator": { + "key": "x-ibm-finding.x_threat_originator", + "object": "finding" + }, + "source": { + "key": "x-ibm-finding.x_threat_source", + "object": "finding" + }, + "agentId": { + "key": "x-ibm-finding.x_agent_id", + "object": "finding" + }, + "finding_type": { + "key": "x-ibm-finding.finding_type", + "object": "finding" + }, + "l4protocol": { + "key": "network-traffic.protocols", + "object": "network", + "transformer": "ToLowercaseArray" + }, + "clientPort": { + "key": "network-traffic.src_port", + "object": "network", + "transformer": "ToInteger" + }, + "serverPort": { + "key": "network-traffic.dst_port", + "object": "network", + "transformer": "ToInteger" + }, + "serverIpv4": [ + { + "key": "ipv4-addr.value", + "object": "dst_ip" + }, + { + "key": "network-traffic.dst_ref", + "object": "network", + "references": "dst_ip" + } + ], + "clientIpv4": [ + { + "key": "ipv4-addr.value", + "object": "src_ip" + }, + { + "key": "network-traffic.src_ref", + "object": "network", + "references": "src_ip" + } + ], + "severity": { + "key": "x-ibm-finding.severity", + "object": "finding", + "transformer": "SeverityToScore" + }, + "containerId": { + "key": "x-oca-asset.extensions.x-oca-container-ext.container_id", + "object": "asset" + }, + "description": [ + { + "key": "x-sysdig-policy.description", + "object": "policy" + }, + { + "key": "x-ibm-finding.x_policy_ref", + "object": "finding", + "references": "policy" + } + ], + "content": { + "ruleName": { + "key": "x-sysdig-policy.rule_name", + "object": "policy" + }, + "ruleType": { + "key": "x-sysdig-policy.rule_type", + "object": "policy" + }, + "ruleSubType": { + "key": "x-sysdig-policy.rule_subtype", + "object": "policy" + }, + "policyId": { + "key": "x-sysdig-policy.policy_id", + "object": "policy" + }, + "fields": { + "falco.rule": { + "key": "x-ibm-finding.name", + "object": "finding" + }, + "proc.cmdline": { + "key": "process.command_line", + "object": "proc" + }, + "proc.name": [ + { + "key": "file.name", + "object": "file" + }, + { + "key": "process.name", + "object": "proc" + }, + { + "key": "process.binary_ref", + "object": "proc", + "references": "file" + } + ], + "proc.pid": { + "key": "process.pid", + "object": "proc", + "transformer": "ToInteger" + }, + "proc.sid": { + "key": "process.x_sid", + "object": "proc" + }, + "proc.exepath": [ + { + "key": "directory.path", + "object": "file_dir" + }, + { + "key": "file.parent_directory_ref", + "object": "file", + "references": "file_dir" + } + ], + "proc.cwd": { + "key": "process.cwd", + "object": "proc" + }, + "proc.pname": [ + { + "key": "file.name", + "object": "parent_file" + }, + { + "key": "process.name", + "object": "parent_proc" + }, + { + "key": "process.parent_ref", + "object": "proc", + "references": "parent_proc" + }, + { + "key": "process.binary_ref", + "object": "parent_proc", + "references": "parent_file" + } + ], + "proc.pcmdline": { + "key": "process.command_line", + "object": "parent_proc" + }, + "proc.ppid": { + "key": "process.pid", + "object": "parent_proc", + "transformer": "ToInteger" + }, + "proc.anames": { + "key": "process.x_parent_names", + "object": "parent_proc" + }, + "user.loginname": { + "key": "user-account.account_login", + "object": "user" + }, + "user.loginuid": { + "key": "user-account.x_loginuid", + "object": "user" + }, + "user.name": [ + { + "key": "user-account.display_name", + "object": "user" + }, + { + "key": "process.creator_user_ref", + "object": "proc", + "references": "user" + } + ], + "user.uid": { + "key": "user-account.user_id", + "object": "user" + } + } + }, + "labels": { + "host.hostName": { + "key": "x-oca-asset.hostname", + "object": "asset" + }, + "container.image.digest": { + "key": "x-oca-asset.extensions.x-oca-container-ext.x_digest", + "object": "asset" + }, + "container.image.id": { + "key": "x-oca-asset.extensions.x-oca-container-ext.image_id", + "object": "asset" + }, + "container.image.tag": { + "key": "x-oca-asset.extensions.x-oca-container-ext.x_tag", + "object": "asset" + }, + "container.image.repo": { + "key": "x-oca-asset.extensions.x-oca-container-ext.x_repo", + "object": "asset" + }, + "container.label.io.kubernetes.pod.name": { + "key": "x-oca-asset.extensions.x-oca-pod-ext.pod_name", + "object": "asset" + }, + "container.label.io.kubernetes.pod.namespace": { + "key": "x-oca-asset.extensions.x-oca-pod-ext.x_namespace", + "object": "asset" + }, + "container.name": { + "key": "x-oca-asset.extensions.x-oca-container-ext.name", + "object": "asset" + }, + "host.mac": [ + { + "key": "mac-addr.value", + "object": "mac" + }, + { + "key": "x-oca-asset.mac_refs", + "object": "asset", + "references": [ + "mac" + ] + } + ], + "kubernetes.cluster.name": [ + { + "key": "x-sysdig-cluster.name", + "object": "cluster" + }, + { + "key": "x-ibm-finding.x_cluster_ref", + "object": "finding", + "references": "cluster" + }, + { + "key": "x-sysdig-cluster.x_node_ref", + "object": "cluster", + "references": "asset" + } + ], + "kubernetes.daemonSet.name": { + "key": "x-sysdig-cluster.daemonset", + "object": "cluster" + }, + "kubernetes.namespace.name": { + "key": "x-sysdig-cluster.namespace", + "object": "cluster" + }, + "kubernetes.deployment.name": [ + { + "key": "x-sysdig-deployment.name", + "object": "deployment" + }, + { + "key": "x-ibm-finding.x_deployment_ref", + "object": "finding", + "references": "deployment" + } + ], + "kubernetes.node.name": [ + { + "key": "ipv4-addr.value", + "object": "ip", + "transformer": "HostnameToIpAddress" + }, + { + "key": "x-oca-asset.ip_refs", + "object": "asset", + "references": [ + "ip" + ] + } + ], + "kubernetes.workload.name": { + "key": "x-ibm-finding.x_workload_name", + "object": "finding" + }, + "kubernetes.workload.type": { + "key": "x-ibm-finding.x_workload_type", + "object": "finding" + }, + "aws.accountId": { + "key": "x-cloud-provider.account_id", + "object": "cloud_provider" + }, + "cloudProvider.name": { + "key": "x-cloud-provider.name", + "object": "cloud_provider" + }, + "aws.region": { + "key": "x-cloud-provider.region", + "object": "cloud_provider" + }, + "aws.instanceId": { + "key": "x-cloud-resource.aws_instance_id", + "object": "cloud_resource" + } + }, + "timestamp": [ + { + "key": "first_observed", + "transformer": "TimestampConversion" + }, + { + "key": "last_observed", + "transformer": "TimestampConversion" + } + ] +} \ No newline at end of file diff --git a/stix_shifter_modules/sysdig/stix_translation/query_constructor.py b/stix_shifter_modules/sysdig/stix_translation/query_constructor.py new file mode 100644 index 000000000..99bfb6b4a --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/query_constructor.py @@ -0,0 +1,466 @@ +""" This Module will convert Stix Pattern to Sysdig data source supported query """ +import re +import json +import logging +from os import path +from datetime import datetime, timedelta +from stix_shifter_utils.stix_translation.src.patterns.pattern_objects import ObservationExpression, \ + ComparisonExpression, ComparisonComparators, Pattern, \ + CombinedComparisonExpression, CombinedObservationExpression + +logger = logging.getLogger(__name__) + +START_STOP_PATTERN = r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z)" +MAC = '^(([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2}))$' +TYPE_MAP_PATH = "json/config_map.json" + +STOP_TIME = datetime.utcnow() + + +class StartStopQualifierValueException(Exception): + """ Start Stop qualifier exception """ + pass + + +class FileNotFoundException(Exception): + """ file not found exception """ + pass + + +class QueryStringPatternTranslator: + """ + translate stix pattern to native data source query language + """ + + def __init__(self, pattern: Pattern, data_model_mapper, options): + logger.info("Sysdig Connector") + self.dmm = data_model_mapper + self.options = options + self.comparator_lookup = self.dmm.map_comparator() + self.type_map = self.load_json(TYPE_MAP_PATH) + self.translated_query = self.parse_expression(pattern) + + @staticmethod + def load_json(rel_path_of_file): + """ + Consumes a json file and returns a dictionary + :param rel_path_of_file: str + :return: dict + """ + _json_path = path.dirname(path.realpath(__file__)) + "/" + rel_path_of_file + try: + if path.exists(_json_path): + with open(_json_path, encoding='utf-8') as f_obj: + return json.load(f_obj) + raise FileNotFoundException + except FileNotFoundException as ex: + raise FileNotFoundError(f'{rel_path_of_file} not found') from ex + + def _format_set(self, values, mapped_field_type, mapped_fields_array): + """ + Formats value in the event of set operation + :param values + :param mapped_field_type: str + :param mapped_fields_array: list + :return formatted value + """ + gen = values.element_iterator() + formatted_value = ','.join(QueryStringPatternTranslator._escape_value( + self._format_value_type(value, mapped_field_type, mapped_fields_array), mapped_field_type) + for value in gen) + return f'({formatted_value})' + + @staticmethod + def _format_equality(value, mapped_field_type): + """ + Formats value in the event of equality operation + :param value + :return formatted value + """ + return QueryStringPatternTranslator._escape_value(value, mapped_field_type) + + @staticmethod + def _escape_value(value, mapped_field_type): + """ + adds escape characters to string type value + :param value + :return formatted value + """ + if mapped_field_type == "int": + return value + if isinstance(value, str): + value = f'\"{value}\"' + return str(value) + + @staticmethod + def _field_severity(value): + """ + convert severity 1-100 to data source value 0-7 + High: 0-3, Medium: 4-5, Low: 6, Info: 7 + param value + return value(int) + examples: + severity is 100, after conversion is 0. + severity is 30, after conversion is 7. + Reference : + Sysdig Docs https://docs.sysdig.com/en/docs/sysdig-secure/secure-events/event-forwarding/#policy-event-severity + """ + value = int(value) + if 91 <= value <= 100: + value = 0 + elif 81 <= value <= 90: + value = 1 + elif 71 <= value <= 80: + value = 2 + elif 61 <= value <= 70: + value = 3 + elif 51 <= value <= 60: + value = 4 + elif 41 <= value <= 50: + value = 5 + elif 31 <= value <= 40: + value = 6 + elif 1 <= value <= 30: + value = 7 + else: + raise NotImplementedError('only 1-100 integer values are supported with this field') + return value + + def _format_value_type(self, value, mapped_field_type, mapped_fields_array): + """ + check input value format that matches with the mapped field value type + :param value + :param mapped_field_type: str + :param mapped_fields_array: list + :return formatted value + """ + converted_value = str(value) + if mapped_field_type == "mac": + compile_mac_regex = re.compile(MAC) + if not compile_mac_regex.search(converted_value): + raise NotImplementedError(f'Invalid mac address - {converted_value} provided') + elif mapped_field_type == "int": + if not converted_value.isdigit(): + raise NotImplementedError(f'string type input - {converted_value} is not supported for ' + f'integer type fields') + if mapped_fields_array[0] == "severity": + value = self._field_severity(value) + converted_value = str(value) + return converted_value + + def _check_value_comparator_support(self, comparator, mapped_field_type): + """ + checks the comparator and value support + :param comparator + :param mapped_field_type: str + """ + operator = self.comparator_lookup[str(comparator)] + + if mapped_field_type == 'string' and comparator not in [ComparisonComparators.In, + ComparisonComparators.Equal, + ComparisonComparators.NotEqual + ]: + raise NotImplementedError(f'{operator} operator is not supported for string type input') + if mapped_field_type == 'int' and comparator not in [ComparisonComparators.In, + ComparisonComparators.Equal, + ComparisonComparators.NotEqual, + ComparisonComparators.GreaterThan, + ComparisonComparators.GreaterThanOrEqual, + ComparisonComparators.LessThanOrEqual, + ComparisonComparators.LessThan]: + raise NotImplementedError(f'{operator} operator is not supported for integer type input') + + @staticmethod + def _format_negate(comparator): + """ + returns negation of input operator + :param comparator:str + :return str + """ + negate_comparator = { + ">": "<=", + ">=": "<", + "<": ">=", + "<=": ">", + "=": "!=", + "in": "not in" + } + return negate_comparator[comparator] + + @staticmethod + def _check_time_range_values(converted_timestamp): + """ + checks for valid start and stop time + :param converted_timestamp: list + """ + if converted_timestamp[0] > converted_timestamp[1]: + raise StartStopQualifierValueException('Start time should be lesser than Stop time') + + @staticmethod + def _parse_time_range(qualifier, time_range): + """ + Converts qualifier timestamp to epoch + :param qualifier: str + :param time_range: int + return: list of converted epoch values + """ + try: + compile_timestamp_regex = re.compile(START_STOP_PATTERN) + if qualifier and compile_timestamp_regex.search(qualifier): + time_range_iterator = compile_timestamp_regex.finditer(qualifier) + time_range_list = [each.group() for each in time_range_iterator] + else: + start_time = STOP_TIME - timedelta(minutes=time_range) + converted_start_time = start_time.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' + # limit 3 digit value for millisecond + converted_stop_time = STOP_TIME.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' + time_range_list = [converted_start_time, converted_stop_time] + except (KeyError, IndexError, TypeError) as ex: + raise ex + return time_range_list + + @staticmethod + def get_epoch_time(timestamp): + """ + Converting timestamp (YYYY-MM-DDThh:mm:ss.000Z) to 13-digit Unix time (epoch + milliseconds) + :param timestamp: str, timestamp + :return: int, epoch time + """ + time_patterns = ['%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S.%fZ'] + epoch = datetime(1970, 1, 1) + for time_pattern in time_patterns: + try: + converted_time = int( + ((datetime.strptime(timestamp, time_pattern) - epoch).total_seconds()) * 1000 * 1000000) + return converted_time + except ValueError: + pass + raise NotImplementedError("cannot convert the timestamp {} to epoch time".format(timestamp)) + + def _add_timestamp_to_query(self, query, qualifier): + """ + adds timestamp filter to Sysdig query + :param query: str + :param qualifier + :return str + """ + converted_timestamp = \ + QueryStringPatternTranslator._parse_time_range(qualifier, self.options["time_range"]) + QueryStringPatternTranslator._check_time_range_values(converted_timestamp) + from_epoch = self.get_epoch_time(converted_timestamp[0]) + to_epoch = self.get_epoch_time(converted_timestamp[1]) + final_query = f'from={from_epoch}&to={to_epoch}&filter={query}' + return final_query + + def _check_mapped_field_type(self, mapped_field_array): + """ + Returns the type of mapped field array + :param mapped_field_array: list + :return: str + """ + mapped_field = mapped_field_array[0] + mapped_field_type = "string" + for key, value in self.type_map.items(): + if mapped_field in value and key in ["int_supported_fields", "string_supported_fields", + "mac_supported_fields"]: + mapped_field_type = key.split('_')[0] + break + return mapped_field_type + + @staticmethod + def _parse_mapped_fields(value, comparator, mapped_fields_array): + """ + parse mapped fields into query expression + :param value: str + :param comparator: str + :param mapped_fields_array: list + :return: str + """ + comparison_string = "" + mapped_fields_count = len(mapped_fields_array) + for mapped_field in mapped_fields_array: + if "in" in comparator: + comparison_string += f'{mapped_field} {comparator} {value}' + else: + comparison_string += f'{mapped_field}{comparator}{value}' + if mapped_fields_count > 1: + comparison_string += " OR " + mapped_fields_count -= 1 + return comparison_string + + def _lookup_comparison_operator(self, expression_operator): + """ + lookup operators support in Sysdig connector + :param expression_operator:enum object + :return str + """ + if str(expression_operator) not in self.comparator_lookup: + raise NotImplementedError( + f'Comparison operator {expression_operator.name} unsupported for Sysdig connector') + operator = self.comparator_lookup[str(expression_operator)] + return operator + + def _eval_comparison_value(self, expression, mapped_field_type, mapped_fields_array): + """ + Function for parsing comparison expression value + :param expression: expression object + :param mapped_field_type:str + :param mapped_fields_array: list + :return: formatted expression value + """ + if expression.comparator == ComparisonComparators.In: + value = self._format_set(expression.value, mapped_field_type, mapped_fields_array) + elif expression.comparator not in [ComparisonComparators.Like, ComparisonComparators.Matches]: + value = self._format_value_type(expression.value, mapped_field_type, mapped_fields_array) + self._check_value_comparator_support(expression.comparator, mapped_field_type) + value = self._format_equality(value, mapped_field_type) + else: + raise NotImplementedError('Unknown comparator expression operator') + return value + + def _eval_combined_comparison_exp(self, expression): + """ + Function for parsing combined comparison expression + :param expression: expression object + """ + operator = self._lookup_comparison_operator(expression.operator) + expression_01 = self._parse_expression(expression.expr1) + expression_02 = self._parse_expression(expression.expr2) + if not expression_01 or not expression_02: + return '' + if isinstance(expression.expr1, CombinedComparisonExpression): + expression_01 = f'{expression_01}' + if isinstance(expression.expr2, CombinedComparisonExpression): + expression_02 = f'{expression_02}' + + query_string = f'{expression_01}{operator}{expression_02}' + return f'{query_string}' + + def _eval_combined_observation_exp(self, expression, qualifier=None): + """ + Function for parsing combined observation expression + :param expression: expression object + :param qualifier: qualifier + """ + operator = self._lookup_comparison_operator(expression.operator) + expression_01 = self._parse_expression(expression.expr1, qualifier) + expression_02 = self._parse_expression(expression.expr2, qualifier) + query = '' + if expression_01 and expression_02: + query = f'{expression_01} {operator} {expression_02}' + + elif expression_01: + query = f'{expression_01}' + elif expression_02: + query = f'{expression_02}' + return query + + def _parse_expression(self, expression, qualifier=None): + """ + parse ANTLR pattern to Sysdig native query + :param expression: expression object, ANTLR parsed expression object + :param qualifier: str, default in None + :return str + """ + if isinstance(expression, ComparisonExpression): # Base Case + stix_objects = expression.object_path.split(':') + mapped_fields_array = self.dmm.map_field(stix_objects[0], stix_objects[1]) + comparator = self._lookup_comparison_operator(expression.comparator) + if expression.negated: + comparator = QueryStringPatternTranslator._format_negate(comparator) + mapped_field_type = self._check_mapped_field_type(mapped_fields_array) + value = self._eval_comparison_value(expression, mapped_field_type, mapped_fields_array) + comparison_string = self._parse_mapped_fields(value, comparator, mapped_fields_array) + return comparison_string + + elif isinstance(expression, CombinedComparisonExpression): + return self._eval_combined_comparison_exp(expression) + + elif isinstance(expression, ObservationExpression): + query_string = self._parse_expression(expression.comparison_expression) + query_string = self._add_timestamp_to_query(query_string, qualifier) + return query_string + + elif hasattr(expression, 'qualifier') and hasattr(expression, 'observation_expression'): + if isinstance(expression.observation_expression, CombinedObservationExpression): + operator = self._lookup_comparison_operator(expression.observation_expression.operator) + expression_01 = self._parse_expression(expression.observation_expression.expr1, + expression.qualifier) + expression_02 = self._parse_expression(expression.observation_expression.expr2, + expression.qualifier) + query_string = f'{expression_01} {operator} {expression_02}' + else: + query_string = self._parse_expression(expression.observation_expression, + expression.qualifier) + if qualifier is not None: + query_string = self._add_timestamp_to_query(query_string, qualifier) + return query_string + + elif isinstance(expression, CombinedObservationExpression): + + return self._eval_combined_observation_exp(expression, qualifier) + + elif isinstance(expression, Pattern): + return self._parse_expression(expression.expression) + else: + raise RuntimeError(f'Unknown Recursion Case for expression={expression},' + f' type(expression)={type(expression)}') + + def parse_expression(self, pattern: Pattern): + """ + Conversion of ANTLR pattern to Sysdig query + :param pattern: expression object, ANTLR parsed expression object + :return: str, native query + """ + return self._parse_expression(pattern) + + @staticmethod + def format_multiple_observations(query): + """ + timestamp formatting for multiple observations + """ + split_queries = query.split(' or ') + query_dict = {} + for row in split_queries: + # making single time stamp instead of having multiple same time stamps for multiple queries + split_query = row.split('filter=') + query_timestamp = split_query[0] + if query_dict.get(query_timestamp): + query_dict[query_timestamp] += " or " + split_query[1] + else: + query_dict[query_timestamp] = 'filter=' + split_query[1] + final_query = [key + value for key, value in query_dict.items()] + return final_query + + @staticmethod + def removing_audit_trail_logs(format_queries): + """ + Displays error message when searched for auditTrail events. + Adds the condition to filter auditTrail events for other queries. + """ + query_list = [] + remove_audit = "andsource!=\"auditTrail\"" + if any('auditTrail' in query for query in format_queries): + raise NotImplementedError('Sysdig connector does not provide auditTrail event') + else: + for query in format_queries: + split_format_query = query.split('filter=') + query_list.append(f'{split_format_query[0]}filter=({split_format_query[1]}){remove_audit}') + return query_list + +def translate_pattern(pattern: Pattern, data_model_mapping, options): + """ + Conversion of ANTLR pattern to native data source query + :param pattern: expression object, ANTLR parsed expression object + :param data_model_mapping: DataMapper object, mapping object obtained by parsing json + :param options: dict, time_range defaults to 5 + :return: string, Sysdig queries + """ + translated_query_strings = QueryStringPatternTranslator(pattern, data_model_mapping, options) + final_queries = translated_query_strings.translated_query + + if final_queries.count('from') > 1: + final_queries = QueryStringPatternTranslator.format_multiple_observations(final_queries) + return QueryStringPatternTranslator.removing_audit_trail_logs(final_queries) + else: + return QueryStringPatternTranslator.removing_audit_trail_logs([final_queries]) diff --git a/stix_shifter_modules/sysdig/stix_translation/query_translator.py b/stix_shifter_modules/sysdig/stix_translation/query_translator.py new file mode 100644 index 000000000..575372d07 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/query_translator.py @@ -0,0 +1,26 @@ +import logging + +from stix_shifter_utils.modules.base.stix_translation.base_query_translator import BaseQueryTranslator +from . import query_constructor + +logger = logging.getLogger(__name__) + + +class QueryTranslator(BaseQueryTranslator): + + def transform_antlr(self, data, antlr_parsing_object): + """ + Transforms STIX pattern into a different query format. Based on a mapping file + :param antlr_parsing_object: Antlr parsing objects for the STIX pattern + :type antlr_parsing_object: object + :param mapping: The mapping file path to use as instructions on how to transform the given STIX query into another format. This should default to something if one isn't passed in + :type mapping: str (filepath) + :return: transformed query string + :rtype: str + """ + + logger.info("Converting STIX2 Pattern to data source query") + + query_string = query_constructor.translate_pattern( + antlr_parsing_object, self, self.options) + return query_string diff --git a/stix_shifter_modules/sysdig/stix_translation/transformers.py b/stix_shifter_modules/sysdig/stix_translation/transformers.py new file mode 100644 index 000000000..eac9e27e4 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_translation/transformers.py @@ -0,0 +1,66 @@ +from stix_shifter_utils.stix_translation.src.utils.transformers import ValueTransformer +from stix_shifter_utils.utils import logger +import re +from datetime import datetime + +LOGGER = logger.set_logger(__name__) +connector = __name__.split('.')[1] + + +class TimestampConversion(ValueTransformer): + """ Convert timezone date to timestamp (YYYY-MM-DDThh:mm:ss.000Z)""" + + @staticmethod + def transform(timestamp): + try: + # with milliseconds + if re.search(r"\d{4}(-\d{2}){2} \d{2}(:\d{2}){2}.\d{0,6}\+\d{2}:\d{2}", str(timestamp)): + converted_date = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f%z") + timestamp = datetime.strftime(converted_date, "%Y-%m-%dT%H:%M:%S.%fZ") + # for without milliseconds, setting three zeroes in the millisecond in the converted date + elif re.search(r"\d{4}(-\d{2}){2} \d{2}(:\d{2}){2}\+\d{2}:\d{2}", str(timestamp)): + converted_date = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S%z") + timestamp = datetime.strftime(converted_date, "%Y-%m-%dT%H:%M:%S.%f")[:-3] + 'Z' + except: + LOGGER.error(f'{connector} connector error cannot convert this timestamp %s', timestamp) + return timestamp + + +class SeverityToScore(ValueTransformer): + + @staticmethod + def transform(severity): + """ + sysdig severity values levels 0-7 : + High: 0-3, Medium: 4-5, Low: 6, Info: 7 + converting sysdig severity values 0-7 to 1-100 + Example : + High: sysdig severity : 0, after conversion : 100 + Low : sysdig severity : 6, after conversion : 40 + Reference : + Sysdig Docs https://docs.sysdig.com/en/docs/sysdig-secure/secure-events/event-forwarding/#policy-event-severity + """ + try: + # value transformer to convert severity value on a scale of 1-100 + if isinstance(severity, int): + return (10 - int(severity)) * 10 + return severity + except KeyError: + LOGGER.error(f'{connector} connector error cannot convert severity scale value') + + +class HostnameToIpAddress(ValueTransformer): + """ + Converts Node Name into IP address + """ + @staticmethod + def transform(address): + try: + match = re.search(r'\d+\-\d+\-\d+\-\d+', address) + if match: + # Getting the matched IP address + extracted_ip = match.group() + return extracted_ip.replace('-', '.') + return address + except KeyError: + LOGGER.error(f'{connector} connector error cannot convert ip value') diff --git a/stix_shifter_modules/sysdig/stix_transmission/__init__.py b/stix_shifter_modules/sysdig/stix_transmission/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/stix_shifter_modules/sysdig/stix_transmission/api_client.py b/stix_shifter_modules/sysdig/stix_transmission/api_client.py new file mode 100644 index 000000000..846241eb6 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_transmission/api_client.py @@ -0,0 +1,37 @@ +from stix_shifter_utils.stix_transmission.utils.RestApiClientAsync import RestApiClientAsync + + +class APIClient: + PING_ENDPOINT = 'api/v1/secureEvents/status' + EVENTS_ENDPOINT = 'api/v1/secureEvents?' + + def __init__(self, connection, configuration): + self.auth = configuration.get('auth') + self.result_limit = connection['options'].get("result_limit") + headers = {} + if 'token' in self.auth: + headers['Authorization'] = "Bearer " + self.auth.get('token') + headers["Accept"] = "application/json" + headers["Content-Type"] = "application/json;charset=UTF-8" + # Added self-signed certificate parameter for verification + self.client = RestApiClientAsync(connection.get('host'), + connection.get('port', None), + headers, + url_modifier_function=None, + cert_verify=connection.get('selfSignedCert')) + self.timeout = connection['options'].get('timeout') + + async def ping_data_source(self): + """ + ping the Data Source + :return: Response object + """ + return await self.client.call_api(self.PING_ENDPOINT, 'GET', headers=self.client.headers, timeout=self.timeout) + + async def get_search_results(self, query): + """ + :param query: Data Source Query + :return: Response Object + """ + return await self.client.call_api(self.EVENTS_ENDPOINT, 'GET', urldata=query, + headers=self.client.headers, timeout=self.timeout) diff --git a/stix_shifter_modules/sysdig/stix_transmission/connector.py b/stix_shifter_modules/sysdig/stix_transmission/connector.py new file mode 100644 index 000000000..3287aa29f --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_transmission/connector.py @@ -0,0 +1,211 @@ +import json +from stix_shifter_utils.modules.base.stix_transmission.base_json_sync_connector import BaseJsonSyncConnector +from stix_shifter_utils.utils.error_response import ErrorResponder +from stix_shifter_utils.utils import logger +from .api_client import APIClient + + +class InvalidMetadataException(Exception): + pass + + +class Connector(BaseJsonSyncConnector): + SYSDIG_MAX_PAGE_SIZE = 999 + + def __init__(self, connection, configuration): + self.api_client = APIClient(connection, configuration) + self.logger = logger.set_logger(__name__) + self.connector = __name__.split('.')[1] + + async def create_results_connection(self, query, offset, length, metadata=None): + """ + Fetching the results using query, offset and length + :param query: str, Data Source query + :param offset: str, Offset value + :param length: str, Length value + :param metadata: dict + :return: return_obj, dict + """ + return_obj = {} + response_dict = {} + data = [] + local_result_count = 0 + # Using 'prev' cursor instead of 'next' cursor in Sysdig since the records are returned in desc order by last + # modified timestamp. The 'prev' is not None when all the results are returned in the first api call itself + # and there are no more records. + # Hence, checking for result_count returned from API call with the limit. When the result_count is less than + # the limit, setting prev_page_token to None. This is to overcome Sysdig behaviour. + try: + if metadata: + if isinstance(metadata, dict) and metadata.get('result_count') and metadata.get('prev_page_token'): + result_count, prev_page_token = metadata['result_count'], metadata['prev_page_token'] + result_count = int(result_count) + total_records = int(length) + if abs(self.api_client.result_limit - result_count) < total_records: + total_records = abs(self.api_client.result_limit - result_count) + + else: + # Raise exception when metadata doesnt contain result count or next page token. + raise InvalidMetadataException(metadata) + else: + result_count, prev_page_token = 0, '0' + total_records = int(offset) + int(length) + if total_records > int(self.api_client.result_limit): + total_records = int(self.api_client.result_limit) + if total_records <= Connector.SYSDIG_MAX_PAGE_SIZE: + limit = total_records + else: + limit = Connector.SYSDIG_MAX_PAGE_SIZE + query_limit = limit + limit = f'&limit={limit}' + if (result_count == 0 and prev_page_token == '0') or (prev_page_token != '0' and result_count < + self.api_client.result_limit): + """api call for searching alert based on query""" + split_query = query.split('&filter=') + if metadata: + response_wrapper = await self.api_client.get_search_results( + f'&cursor={prev_page_token}&filter={split_query[1]}{limit}') + else: + response_wrapper = await self.api_client.get_search_results(query + limit) + response_code = response_wrapper.code + if response_code == 200: + response_dict = json.loads(response_wrapper.read()) + return_obj['success'] = True + result_count += len(response_dict['data']) + local_result_count += len(response_dict['data']) + prev_page_token = response_dict['page'].get('prev') + if len(response_dict['data']) < query_limit: + prev_page_token = None + data += response_dict['data'] + # Loop until if there is next page and records fetched is less than total records. + while prev_page_token: + if not metadata and result_count < total_records: + remaining_records = total_records - result_count + elif metadata and local_result_count < total_records: + remaining_records = total_records - local_result_count + else: + break + if remaining_records > Connector.SYSDIG_MAX_PAGE_SIZE: + limit = Connector.SYSDIG_MAX_PAGE_SIZE + else: + limit = remaining_records + query_limit = limit + limit = f'&limit={limit}' + prev_response_wrapper = await self.api_client.get_search_results( + f'&cursor={prev_page_token}&filter={split_query[1]}{limit}') + prev_response_code = prev_response_wrapper.code + prev_response_dict = json.loads(prev_response_wrapper.read()) + if prev_response_code == 200: + result_count += len(prev_response_dict['data']) + local_result_count += len(response_dict['data']) + prev_page_token = prev_response_dict['page'].get('prev') + if len(prev_response_dict['data']) < query_limit: + prev_page_token = None + data += prev_response_dict['data'] + else: + return_obj = self.exception_response(prev_response_wrapper.code, + prev_response_dict.get('error')) + data = [] + break + if data: + self.update_event(data) + + if metadata: + return_obj['data'] = data if data else [] + else: + return_obj['data'] = data[int(offset):total_records] if data else [] + + if prev_page_token and result_count < self.api_client.result_limit: + return_obj['metadata'] = {"result_count": result_count, + "prev_page_token": str(prev_page_token)} + else: + if not return_obj.get('error') and return_obj.get('success') is not False: + return_obj['success'] = True + return_obj['data'] = [] + else: + if response_code == 401: + message = 'cannot verify credentials' + else: + response_dict = json.loads(response_wrapper.read()) + message = response_dict.get('message') + return_obj = self.exception_response(response_wrapper.code, message) + else: + return_obj['success'] = True + return_obj['data'] = [] + + except InvalidMetadataException as ex: + response_dict['code'] = 100 + response_dict['message'] = f'Invalid metadata: {str(ex)}' + ErrorResponder.fill_error(return_obj, response_dict, ['message'], connector=self.connector) + + except Exception as ex: + if "timeout_error" in str(ex): + response_dict['code'] = 408 + response_dict['message'] = str(ex) + self.logger.error(f'{self.connector} connector error when getting search results: %s', ex) + ErrorResponder.fill_error(return_obj, response_dict, ['message'], + connector=self.connector) + return return_obj + + @staticmethod + def update_event(data): + """ + Set the finding_type as 'threat' for the event. + Replace invalid tag with empty string. + Parse the fd.name and set the fields for network-traffic in the event. + """ + for event in data: + fields = event["content"]["fields"] + # replace unsupported field value with empty string + for f in fields: + if fields[f] == '': + fields[f] = '' + + # Setting the finding_type for the event as 'threat' + event["finding_type"] = "threat" + # Parsing network fields from event data + if fields.get("evt.type") and fields["evt.type"] == "connect": + if "->" in fields["fd.name"]: + source, destination = fields["fd.name"].split('->') + event["clientIpv4"], event["clientPort"] = source.split(":") + event["serverIpv4"], event["serverPort"] = destination.split(":") + if fields.get('fd.l4proto'): + event["l4protocol"] = fields['fd.l4proto'] + else: + event["l4protocol"] = "tcp" + + # Adding ancestor names in list + fields["proc.anames"] = [val for key, val in fields.items() if 'proc.aname' in key and len(val) > 0] + + async def ping_connection(self): + """ + Ping the endpoint + :return: dict + """ + return_obj = {} + response_dict = {} + try: + response = await self.api_client.ping_data_source() + response_code = response.code + if response_code == 200: + return_obj['success'] = True + else: + return_obj = self.exception_response(response_code, 'cannot verify credentials') + except Exception as ex: + response_dict['message'] = ex + self.logger.error(f'{self.connector} connector error while pinging: %s', ex) + ErrorResponder.fill_error(return_obj, response_dict, ['message'], + connector=self.connector) + return return_obj + + def exception_response(self, code, response_txt): + """ + create the exception response + :param code, int + :param response_txt, dict + :return: return_obj, dict + """ + return_obj = {} + response_dict = {'code': code, 'message': str(response_txt)} + ErrorResponder.fill_error(return_obj, response_dict, ['message'], connector=self.connector) + return return_obj diff --git a/stix_shifter_modules/sysdig/stix_transmission/error_mapper.py b/stix_shifter_modules/sysdig/stix_transmission/error_mapper.py new file mode 100644 index 000000000..512bd6606 --- /dev/null +++ b/stix_shifter_modules/sysdig/stix_transmission/error_mapper.py @@ -0,0 +1,34 @@ +from stix_shifter_utils.utils.error_mapper_base import ErrorMapperBase +from stix_shifter_utils.utils.error_response import ErrorCode +from stix_shifter_utils.utils import logger + +error_mapping = { + 400: ErrorCode.TRANSMISSION_INVALID_PARAMETER, + 422: ErrorCode.TRANSMISSION_QUERY_LOGICAL_ERROR, + 100: ErrorCode.TRANSMISSION_INVALID_PARAMETER, + 401: ErrorCode.TRANSMISSION_AUTH_CREDENTIALS, + 408: ErrorCode.TRANSMISSION_CONNECT +} + + +class ErrorMapper: + logger = logger.set_logger(__name__) + DEFAULT_ERROR = ErrorCode.TRANSMISSION_MODULE_DEFAULT_ERROR + + @staticmethod + def set_error_code(json_data, return_obj, connector=None): + code = None + try: + code = int(json_data['code']) + except Exception: + pass + + error_code = ErrorMapper.DEFAULT_ERROR + + if code in error_mapping: + error_code = error_mapping[code] + + if error_code == ErrorMapper.DEFAULT_ERROR: + ErrorMapper.logger.error("failed to map: " + str(json_data)) + + ErrorMapperBase.set_error_code(return_obj, error_code, connector=connector) diff --git a/stix_shifter_modules/sysdig/sysdig_supported_stix.md b/stix_shifter_modules/sysdig/sysdig_supported_stix.md new file mode 100644 index 000000000..6f4720501 --- /dev/null +++ b/stix_shifter_modules/sysdig/sysdig_supported_stix.md @@ -0,0 +1,125 @@ +##### Updated on 12/10/23 +## Sysdig +### Results STIX Domain Objects +* Identity +* Observed Data +
+### Supported STIX Operators +*Comparison AND/OR operators are inside the observation while observation AND/OR operators are between observations (square brackets).* + +| STIX Operator | Data Source Operator | +|:------------------|----------------------| +| AND (Comparison) | and | +| OR (Comparison) | or | +| = | = | +| != | != | +| > | > | +| >= | >= | +| < | < | +| <= | <= | +| IN | in | +| OR (Observation) | or | +| AND (Observation) | or | +|
| | +### Searchable STIX objects and properties +| STIX Object and Property | Mapped Data Source Fields | + +|--|--| +| **mac-addr**:value | machineId | +| **x-oca-asset**:hostname | host.hostName | +| **x-oca-asset**:extensions.'x-oca-container-ext'.container_id | containerId | +| **x-oca-asset**:extensions.'x-oca-container-ext'.name | container.name | +| **x-oca-asset**:extensions.'x-oca-container-ext'.image_id | container.image.id | +| **x-oca-asset**:extensions.'x-oca-container-ext'.x_repo | container.image.repo | +| **x-oca-asset**:extensions.'x-oca-container-ext'.x_tag | container.image.tag | +| **x-oca-asset**:extensions.'x-oca-container-ext'.x_digest | container.image.digest | +| **x-oca-asset**:extensions.'x-oca-pod-ext'.pod_name | container.label.io.kubernetes.pod.name | +| **x-oca-asset**:extensions.'x-oca-pod-ext'.x_namespace | container.label.io.kubernetes.pod.namespace | +| **x-ibm-finding**:name | ruleName | +| **x-ibm-finding**:severity | severity | +| **x-ibm-finding**:x_category| category | +| **x-ibm-finding**:x_threat_originator | originator | +| **x-ibm-finding**:x_threat_source | source | +| **x-ibm-finding**:x_agent_id| agentId | +| **x-sysdig-cluster**:name | kubernetes.cluster.name | +| **x-sysdig-cluster**:x_namespace | kubernetes.namespace.name | +| **x-sysdig-deployment**:name | kubernetes.deployment.name | +| **x-sysdig-policy**:rule_name | ruleName | +| **x-sysdig-policy**:rule_type | ruleType | +| **x-sysdig-policy**:rule_subtype | ruleSubType | +| **x-sysdig-policy**:policy_id | policyId | +| **x-cloud-provider**:account_id | aws.accountId | +| **x-cloud-provider**:name | cloudProvider.name | +| **x-cloud-provider**:region | aws.region | +|
| | +### Supported STIX Objects and Properties for Query Results +| STIX Object | STIX Property | Data Source Field | + +|--|--|--| +| mac-addr | value | host.mac | +| x-oca-asset | extensions.x-oca-container-ext.container_id | containerId | +| x-oca-asset | hostname | host.hostName | +| x-oca-asset | extensions.x-oca-container-ext.x_digest | container.image.digest | +| x-oca-asset | extensions.x-oca-container-ext.image_id | container.image.id | +| x-oca-asset | extensions.x-oca-container-ext.x_tag | container.image.tag | +| x-oca-asset | extensions.x-oca-container-ext.x_repo | container.image.repo | +| x-oca-asset | extensions.x-oca-container-ext.name | container.name | +| x-oca-asset | x-oca-asset.extensions.x-oca-pod-ext.pod_name | container.label.io.kubernetes.pod.name | +| x-oca-asset | x-oca-asset.extensions.x-oca-pod-ext.x_namespace | container.label.io.kubernetes.pod.namespace | +| x-oca-asset | ip_refs | kubernetes.node.name | +| x-oca-asset | mac_refs | host.mac | +| x-ibm-finding | x_category| category | +| x-ibm-finding | x_threat_originator | originator | +| x-ibm-finding | x_threat_source | source | +| x-ibm-finding | x_agent_id | agentId | +| x-ibm-finding | finding_type | finding_type | +| x-ibm-finding | severity | severity | +| x-ibm-finding | x_policy_ref | description | +| x-ibm-finding | name | falco.rule | +| x-ibm-finding | x_cluster_ref | kubernetes.cluster.name | +| x-ibm-finding | deployment_ref | kubernetes.deployment.name | +| x-ibm-finding | x_workload_name | kubernetes.workload.name | +| x-ibm-finding | x_workload_type | kubernetes.workload.type | +| x-sysdig-cluster | name | kubernetes.cluster.name | +| x-sysdig-cluster | daemonset | kubernetes.daemonSet.name | +| x-sysdig-cluster | namespace | kubernetes.namespace.name | +| x-sysdig-cluster | x_node_ref | kubernetes.cluster.name | +| x-sysdig-deployment | name | kubernetes.deployment.name | +| x-sysdig-policy | description | description | +| x-sysdig-policy | rule_name | ruleName | +| x-sysdig-policy | rule_type | ruleType | +| x-sysdig-policy | rule_subtype | ruleSubType | +| x-sysdig-policy | policy_id | policyId | +| x-cloud-provider | account_id | aws.accountId | +| x-cloud-provider | name | cloudProvider.name | +| x-cloud-provider | region | aws.region | +| x-cloud-resource | aws_instance_id | aws.instanceId | +| process | command_line | proc.cmdline | proc.pcmdline | +| process | name | proc.name | +| process | pid |proc.pid | +| process | x_pid |proc.sid | +| process | cwd | proc.cwd | +| process | x_pname | proc.pname | +| process | pid | proc.ppid | +| process | x_parent_names | proc.anames | +| process | creator_user_ref | user.name | +| process | binary_ref | proc.pname | +| process | binary_ref | proc.name | +| process | parent_ref | proc.pname | +| user-account | account_login | user.loginname | +| user-account | x_loginuid | user.loginuid | +| user-account | display_name | user.name | +| user-account | user_id | user.uid | +| network-traffic | l4protocol | protocols | +| network-traffic | clientPort | src_port | +| network-traffic | serverPort | dst_port | +| network-traffic | serverIpv4 | dst_ref | +| network-traffic | clientIpv4 | src_ref | +| file | proc.name | name | +| file | proc.exepath | parent_directory_ref | +| file | proc.pname | name | +| ipv4-addr | serverIpv4 | value | +| ipv4-addr | clientIpv4 | value | +| ipv4-addr | kubernetes.node.name | value | +| directory | proc.exepath | path | +|
| | | diff --git a/stix_shifter_modules/sysdig/tests/stix_translation/test_sysdig_json_to_stix.py b/stix_shifter_modules/sysdig/tests/stix_translation/test_sysdig_json_to_stix.py new file mode 100644 index 000000000..a813ba740 --- /dev/null +++ b/stix_shifter_modules/sysdig/tests/stix_translation/test_sysdig_json_to_stix.py @@ -0,0 +1,521 @@ +""" test script to perform unit test case for sysdig translate results """ +import unittest +from stix_shifter_modules.sysdig.entry_point import EntryPoint +from stix_shifter_utils.stix_translation.src.json_to_stix import json_to_stix_translator +from stix_shifter_utils.stix_translation.src.utils.transformer_utils import get_module_transformers + +MODULE = "sysdig" +entry_point = EntryPoint() +map_data = entry_point.get_results_translator().map_data +data_source = { + "type": "identity", + "id": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "name": "sysdig", + "identity_class": "events" +} +options = {} + +data = { + "id": "111aaa1111112222eeee333fff999fff666e00eee", + "cursor": "ababababababababababababababab", + "timestamp": "2023-08-12T16:18:13.823377041Z", + "customerId": 11111111, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "Sysdig Runtime Threat Detection", + "description": "This policy contains rules which Sysdig considers High Confidence of a security incident. " + "They are tightly coupled to common attacker TTP's. They have been designed to minimize " + "false positives but may still result in some depending on your environment.", + "severity": 3, + "agentId": 0000000, + "containerId": "12345678910", + "machineId": "00:00:00:00:00:11", + "content": { + "falsePositive": 'false', + "fields": { + "container.id": "1111b1b11b11", + "container.image.repository": "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + "container.name": "tuned", + "evt.arg.level": "SOL_SOCKET", + "evt.res": "SUCCESS", + "evt.type": "setsockopt", + "falco.rule": "Possible Backdoor using BPF", + "fd.sip.name": "", + "fd.sport": "", + "group.gid": "0", + "group.name": "root", + "proc.aname[2]": "run", + "proc.aname[3]": "conmon", + "proc.aname[4]": "systemd", + "proc.cmdline": "tuned -Es /usr/sbin/tuned --no-dbus", + "proc.cwd": "/var/lib/tuned/", + "proc.exepath": "/usr/sbin/tuned", + "proc.name": "tuned", + "proc.pcmdline": "openshift-tuned -v=0", + "proc.pid": "123456", + "proc.pname": "openshift-tuned", + "proc.ppid": "77777", + "proc.sid": "33333", + "user.loginname": "", + "user.loginuid": "-1", + "user.name": "root", + "user.uid": "0", + "proc.anames": [ + "run", + "conmon", + "systemd" + ] + }, + "internalRuleName": "Possible Backdoor using BPF", + "matchedOnDefault": 'false', + "origin": "Sysdig", + "output": "Possible BPFDoor Attempt Detected (proc.cmdline=tuned -Es /usr/sbin/tuned --no-dbus " + "port= domain= proc.pcmdline=openshift-tuned -v=0 level=SOL_SOCKET proc." + "pname=openshift-tuned gparent=run ggparent=conmon ggparent=systemd container=tuned " + "(id=5801c9b81b33) evt.type=setsockopt evt.res=SUCCESS proc.pid=123241552 proc.cwd=/var/lib/" + "tuned/ proc.ppid=77777 proc.pcmdline=openshift-tuned -v=0 proc.sid=33333 proc.exepath=/usr" + "/sbin/tuned user.uid=0 user.loginuid=-1 user.loginname= user.name=root group.gid=0 group." + "name=root container.id=333b3b33333 container.name=tuned image=quay.io/openshift-release-" + "dev/ocp-v4.0-art-dev)", + "policyId": 1000000, + "ruleName": "Possible Backdoor using BPF", + "ruleSubType": 0, + "ruleTags": [ + "network", + "MITRE", + "MITRE_TA0007_discovery", + "MITRE_TA0006_credential_access", + "MITRE_T1040_network_sniffing", + "MITRE_TA0005_defense_evasion" + ], + "ruleType": 6 + }, + "labels": { + "container.image.digest": "sha256:aaaaa12345678910", + "container.image.id": "12345aaaa6a789a", + "container.image.repo": "quay.io/openshift-release-dev/ocp-v4.0-art-dev", + "container.label.io.kubernetes.container.name": "tuned", + "container.label.io.kubernetes.pod.name": "tuned-4nf6b", + "container.label.io.kubernetes.pod.namespace": "openshift-cluster-node-tuning-operator", + "container.name": "tuned", + "host.hostName": "kube-ch548qct0c9fugqhf7bg-cp4scluster-default-00000183.iks.ibm", + "host.mac": "00:00:00:00:00:00", + "kubernetes.cluster.name": "ap5s-cluster", + "kubernetes.daemonSet.name": "tuned", + "kubernetes.namespace.name": "openshift-cluster-node-tuning-operator", + "kubernetes.node.name": "00.000.00.000", + "kubernetes.pod.name": "tuned-12345", + "kubernetes.workload.name": "tuned", + "kubernetes.workload.type": "daemonset", + "process.name": "tuned -Es /usr/sbin/tuned --no-dbus" + }, + "finding_type": "threat" +} + +data_1 = { + "id": "123445678910abcdefghijklmn", + "cursor": "ABCDEFGHIJ12345678910", + "timestamp": "2023-08-01T19:02:06.198576572Z", + "customerId": 1111111, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "Sysdig Runtime Threat Detection", + "description": "This policy contains rules which Sysdig considers High Confidence of a security incident." + " They are tightly coupled to common attacker TTP's. They have been designed to minimize false " + "positives but may still result in some depending on your environment.", + "severity": 3, + "agentId": 12345678, + "containerId": "123abc456", + "machineId": "00:00:00:00:11:1a", + "content": { + "falsePositive": 'false', + "fields": { + "container.image.repository": "au.icr.io/armada-master/keepalived", + "container.name": "ibm-cloud-provider-ip-111-11-11-111", + "evt.args": "domain=17(AF_PACKET) type=3 proto=0 ", + "evt.type": "socket", + "falco.rule": "Packet socket created in container", + "fd.cport": "", + "fd.name": "", + "fd.sport": "", + "proc.aname[2]": "conmon", + "proc.aname[3]": "systemd", + "proc.aname[4]": "", + "proc.cmdline": "tcpdump -v -l -n -i eth1 ip proto 112 or host 222.0.0.00", + "proc.cwd": "/", + "proc.exepath": "/usr/bin/tcpdump", + "proc.name": "tcpdump", + "proc.pcmdline": "keepalived", + "proc.pname": "keepalived", + "user.loginname": "", + "user.loginuid": "-1", + "user.name": "root", + "user.uid": "0", + "proc.anames": [ + "conmon", + "systemd" + ] + }, + "internalRuleName": "Packet socket created in container", + "matchedOnDefault": 'false', + "origin": "Sysdig", + "output": "Packet socket was created in a container (evt.type=socket evt.args=domain=17(AF_PACKET) type=3" + " proto=0 proc.name=tcpdump proc.pname=keepalived gparent=conmon ggparent=systemd gggparent=" + " container.name=ibm-cloud-provider-ip-111-11-11-111 image=au.icr.io/armada-master/keepalived proc." + "cmdline=tcpdump -v -l -n -i eth1 ip proto 112 or host 222.0.0.22 proc.cwd=/ proc.pcmdline=keepalived" + " user.name=root user.loginuid=-1 user.uid=0 user.loginname= fd.cport= " + "fd.sport= fd.name=)", + "policyId": 12345678, + "ruleName": "Packet socket created in container", + "ruleSubType": 0, + "ruleTags": [ + "network", + "SOC2", + "SOC2_CC6.8", + "NIST", + "NIST_800-53", + "NIST_800-53_AC-6(9)", + "NIST_800-53_AC-6(10)", + "ISO", + "ISO_27001", + "ISO_27001_A.9.1.2", + "HIPAA", + "HIPAA_164.308(a)", + "HIPAA_164.312(a)", + "HITRUST", + "HITRUST_CSF", + "HITRUST_CSF_01.c", + "HITRUST_CSF_09.aa", + "MITRE", + "MITRE_T1133_external_remote_services", + "MITRE_TA0003_persistence", + "MITRE_TA0006_credential_access", + "MITRE_TA0009_collection", + "MITRE_TA0004_privilege_escaltion", + "MITRE_T1557_adversary_in_the_middle" + ], + "ruleType": 6 + }, + "labels": { + "container.image.digest": "sha256:abcedfghijklmnoped674c589316d7a9e0d3862f9387a9197d8c5ee8b83fcb085eb661478a5", + "container.image.id": "12345abcdefgh", + "container.image.repo": "au.icr.io/armada-master/keepalived", + "container.image.tag": "1111", + "container.label.io.kubernetes.container.name": "ibm-cloud-provider-ip-123-45-67-8910", + "container.label.io.kubernetes.pod.name": "ibm-cloud-provider-ip-123-45-67-8910-1234567-jp98p", + "container.label.io.kubernetes.pod.namespace": "ibm-system", + "container.name": "ibm-cloud-provider-ip-123-12-12-123", + "host.hostName": "kube-ch548qct0cabcdefghijkl-cp4scluster-default-0000028c.iks.ibm", + "host.mac": "00:00:00:11:11:1a", + "kubernetes.cluster.name": "ap5s-cluster", + "kubernetes.deployment.name": "ibm-cloud-provider-ip-111-11-11-111", + "kubernetes.namespace.name": "ibm-system", + "kubernetes.node.name": "10.000.00.000", + "kubernetes.pod.name": "ibm-cloud-provider-ip-123-45-67-8910-12345d8f4777-jp98p", + "kubernetes.replicaSet.name": "ibm-cloud-provider-ip-123-45-67-8910-847d8f47b7", + "kubernetes.workload.name": "ibm-cloud-provider-ip-123-45-67-8910", + "kubernetes.workload.type": "deployment", + "process.name": "tcpdump -v -l -n -i eth1 ip proto 112 or host 222.0.0.11" + }, + "finding_type": "threat" +} +data_2 = { + "id": "123456789100000", + "cursor": "ABCDEFGHIJKLMN", + "timestamp": "2023-11-22T11:16:28.101680299Z", + "customerId": 100, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "sysdig custom network rules-2", + "description": "updated network rules with network tag", + "severity": 0, + "agentId": 12345, + "containerId": "12345678910", + "machineId": "10:00:00:00:00:00", + "content": { + "falsePositive": 'false', + "fields": { + "container.id": "12345678910", + "container.image.repository": "docker.io/radial", + "container.image.tag": "curl", + "container.name": "curl-sample-app1", + "evt.res": "EINPROGRESS", + "evt.type": "connect", + "falco.rule": "Contact EC2 Instance Metadata Service From Container", + "fd.name": "111.111.11.111:1000->111.11.111.111:00", + "group.gid": "0", + "group.name": "root", + "proc.aname[2]": "containerd-shim", + "proc.aname[3]": "systemd", + "proc.aname[4]": "", + "proc.args": "-s http://111.111.111.111:11/iam/security-credentials", + "proc.cmdline": "curl -s http://111.111.111.111:11/iam/security-credentials", + "proc.cwd": "/", + "proc.exepath": "/usr/bin/curl", + "proc.name": "curl", + "proc.pcmdline": "sh", + "proc.pid": "11111", + "proc.pname": "sh", + "proc.ppid": "12345", + "proc.sid": "1", + "user.loginname": "", + "user.loginuid": "-1", + "user.name": "root", + "user.uid": "0", + "proc.anames": [ + "containerd-shim", + "systemd" + ] + }, + "internalRuleName": "Contact EC2 Instance Metadata Service From Container", + "matchedOnDefault": 'false', + "origin": "Secure UI", + "ruleName": "Contact EC2 Instance Metadata Service From Container", + "ruleSubType": 0, + "ruleType": 6 + }, + "labels": { + "aws.accountId": "1111111111", + "aws.instanceId": "i-00000000", + "aws.region": "us-east-1", + "cloudProvider.account.id": "111111111", + "cloudProvider.name": "aws", + "cloudProvider.region": "us-east-1", + "container.image.digest": "sha256:111111", + "container.image.id": "4776f1f7d1f6", + "container.image.repo": "docker.io/radial", + "container.image.tag": "curl", + "container.label.io.kubernetes.container.name": "curl-sample-app1", + "container.label.io.kubernetes.pod.name": "curl-sample-app1", + "container.label.io.kubernetes.pod.namespace": "default", + "container.name": "curl-sample-app1", + "host.hostName": "ip-111-111-11-111.ec2.internal", + "host.mac": "11:aa:bb:dd:11:00", + "kubernetes.cluster.name": "sysdig-eks-cluster", + "kubernetes.namespace.name": "default", + "kubernetes.node.name": "ip-111-111-11-111.ec2.internal", + "kubernetes.pod.name": "curl-sample-app1", + "process.name": "curl -s http://111.111.111.111:11/iam/security-credentials" + }, + "finding_type": "threat", + "direction": "out", + "source_ip": "111.111.11.111", + "source_port": "10000", + "destination_ip": "111.11.111.111", + "destination_port": "00", + "proto": "tcp" +} + + +class TestsysdigResultsToStix(unittest.TestCase): + """ + class to perform unit test case for sysdig translate results + """ + + @staticmethod + def get_first(itr, constraint): + """ + return the obj in the itr if constraint is true + """ + return next( + (obj for obj in itr if constraint(obj)), + None + ) + + @staticmethod + def get_first_of_type(itr, typ): + """ + to check whether the object belongs to respective stix object + """ + return TestsysdigResultsToStix.get_first(itr, lambda o: isinstance(o, dict) and o.get('type') == typ) + + def test_common_prop(self): + """to test common stix object properties""" + + result_bundle = json_to_stix_translator.convert_to_stix(data_source, map_data, [data], + get_module_transformers(MODULE), options) + result_bundle_objects = result_bundle['objects'] + + result_bundle_identity = result_bundle_objects[0] + assert result_bundle_identity['type'] == data_source['type'] + + observed_data = result_bundle_objects[1] + + assert observed_data is not None + assert observed_data['id'] is not None + assert observed_data['type'] == "observed-data" + assert observed_data['created_by_ref'] == result_bundle_identity['id'] + assert observed_data['first_observed'] is not None + assert observed_data['last_observed'] is not None + assert observed_data['number_observed'] is not None + + def test_x_sysdig_cluster_json_to_stix(self): + """ to test x-sysdig-cluster stix object properties """ + result_bundle = json_to_stix_translator.convert_to_stix( + data_source, map_data, [data], get_module_transformers(MODULE), options) + result_bundle_objects = result_bundle['objects'] + result_bundle_identity = result_bundle_objects[0] + assert result_bundle_identity['type'] == data_source['type'] + observed_data = result_bundle_objects[1] + assert 'objects' in observed_data + objects = observed_data['objects'] + cluster_obj = TestsysdigResultsToStix.get_first_of_type(objects.values(), 'x-sysdig-cluster') + assert cluster_obj is not None + assert cluster_obj['type'] == 'x-sysdig-cluster' + assert cluster_obj['name'] == 'ap5s-cluster' + assert cluster_obj['daemonset'] == 'tuned' + assert cluster_obj['namespace'] == 'openshift-cluster-node-tuning-operator' + node_ref = cluster_obj['x_node_ref'] + assert (node_ref in objects), f"node_ref with key {cluster_obj['x_node_ref']} " \ + f"not found" + + def test_x_sysdig_deployment_json_to_stix(self): + """to test x-sysdig-deployment stix object properties""" + + result_bundle = json_to_stix_translator.convert_to_stix( + data_source, map_data, [data_1], get_module_transformers(MODULE), options) + result_bundle_objects = result_bundle['objects'] + + result_bundle_identity = result_bundle_objects[0] + assert result_bundle_identity['type'] == data_source['type'] + + observed_data = result_bundle_objects[1] + + assert 'objects' in observed_data + objects = observed_data['objects'] + + deployment_obj = TestsysdigResultsToStix.get_first_of_type(objects.values(), 'x-sysdig-deployment') + assert deployment_obj is not None + assert deployment_obj['type'] == 'x-sysdig-deployment' + assert deployment_obj['name'] == 'ibm-cloud-provider-ip-111-11-11-111' + + def test_x_ibm_finding_json_to_stix(self): + """to test x-ibm-finding stix object properties""" + + result_bundle = json_to_stix_translator.convert_to_stix( + data_source, map_data, [data], get_module_transformers(MODULE), options) + result_bundle_objects = result_bundle['objects'] + + result_bundle_identity = result_bundle_objects[0] + assert result_bundle_identity['type'] == data_source['type'] + + observed_data = result_bundle_objects[1] + + assert 'objects' in observed_data + objects = observed_data['objects'] + + finding_obj = TestsysdigResultsToStix.get_first_of_type(objects.values(), + 'x-ibm-finding') + assert finding_obj is not None + assert finding_obj["type"] == 'x-ibm-finding' + assert finding_obj["x_threat_originator"] == 'policy' + assert finding_obj["x_category"] == 'runtime' + assert finding_obj["x_threat_source"] == 'syscall' + + cluster_ref = finding_obj['x_cluster_ref'] + assert (cluster_ref in objects), f"cluster_ref with key {finding_obj['x_cluster_ref']} " \ + f"not found" + + policy_ref = finding_obj['x_policy_ref'] + assert (policy_ref in objects), f" policy_ref with key {finding_obj['x_policy_ref']} " \ + f"not found" + + def test_x_oca_asset_json_to_stix(self): + """to test x-oca-asset stix object properties""" + + result_bundle = json_to_stix_translator.convert_to_stix( + data_source, map_data, [data_1], get_module_transformers(MODULE), options) + result_bundle_objects = result_bundle['objects'] + + result_bundle_identity = result_bundle_objects[0] + assert result_bundle_identity['type'] == data_source['type'] + + observed_data = result_bundle_objects[1] + + assert 'objects' in observed_data + objects = observed_data['objects'] + + asset_obj = TestsysdigResultsToStix.get_first_of_type(objects.values(), + 'x-oca-asset') + assert asset_obj is not None + assert asset_obj["type"] == 'x-oca-asset' + assert asset_obj['extensions']['x-oca-container-ext']['container_id'] == '123abc456' + assert asset_obj['extensions']['x-oca-container-ext']['x_digest'] == \ + 'sha256:abcedfghijklmnoped674c589316d7a9e0d3862f9387a9197d8' \ + 'c5ee8b83fcb085eb661478a5' + ip_ref = asset_obj['ip_refs'] + assert (values in objects for values in ip_ref), f"ip_refs with key {asset_obj['ip_refs']} " \ + f"not found" + + mac_ref = asset_obj['mac_refs'] + assert (values in objects for values in mac_ref), f"mac_refs with key {asset_obj['mac_refs']} " \ + f"not found" + + def test_x_sysdig_policy_json_to_stix(self): + """to test x-sysdig-policy stix object properties""" + + result_bundle = json_to_stix_translator.convert_to_stix( + data_source, map_data, [data_1], get_module_transformers(MODULE), options) + result_bundle_objects = result_bundle['objects'] + + result_bundle_identity = result_bundle_objects[0] + assert result_bundle_identity['type'] == data_source['type'] + + observed_data = result_bundle_objects[1] + + assert 'objects' in observed_data + objects = observed_data['objects'] + + policy_obj = TestsysdigResultsToStix.get_first_of_type(objects.values(), + 'x-sysdig-policy') + assert policy_obj is not None + assert policy_obj["type"] == 'x-sysdig-policy' + assert policy_obj["rule_name"] == 'Packet socket created in container' + assert policy_obj["rule_type"] == 6 + + def test_x_mac_addr_json_to_stix(self): + """to test mac-addr stix object properties""" + + result_bundle = json_to_stix_translator.convert_to_stix( + data_source, map_data, [data_1], get_module_transformers(MODULE), options) + result_bundle_objects = result_bundle['objects'] + + result_bundle_identity = result_bundle_objects[0] + assert result_bundle_identity['type'] == data_source['type'] + + observed_data = result_bundle_objects[1] + + assert 'objects' in observed_data + objects = observed_data['objects'] + + policy_obj = TestsysdigResultsToStix.get_first_of_type(objects.values(), + 'mac-addr') + assert policy_obj is not None + assert policy_obj["type"] == 'mac-addr' + assert policy_obj["value"] == '00:00:00:11:11:1a' + + def test_cloud_provider_details(self): + """to test cloud provider stix object properties""" + + result_bundle = json_to_stix_translator.convert_to_stix( + data_source, map_data, [data_2], get_module_transformers(MODULE), options) + result_bundle_objects = result_bundle['objects'] + + result_bundle_identity = result_bundle_objects[0] + assert result_bundle_identity['type'] == data_source['type'] + + observed_data = result_bundle_objects[1] + + assert 'objects' in observed_data + objects = observed_data['objects'] + + cloud_obj = TestsysdigResultsToStix.get_first_of_type(objects.values(), + 'x-cloud-provider') + assert cloud_obj is not None + assert cloud_obj["type"] == 'x-cloud-provider' + assert cloud_obj["account_id"] == '1111111111' + assert cloud_obj["name"] == 'aws' + assert cloud_obj["region"] == 'us-east-1' diff --git a/stix_shifter_modules/sysdig/tests/stix_translation/test_sysdig_stix_to_query.py b/stix_shifter_modules/sysdig/tests/stix_translation/test_sysdig_stix_to_query.py new file mode 100644 index 000000000..cb95b7062 --- /dev/null +++ b/stix_shifter_modules/sysdig/tests/stix_translation/test_sysdig_stix_to_query.py @@ -0,0 +1,360 @@ +import re +import unittest +from stix_shifter.stix_translation import stix_translation + +translation = stix_translation.StixTranslation() + + +def _remove_timestamp_from_query(queries): + """ removes timestamp from query """ + pattern = r'[\^from\&to=\d]{47}' + if isinstance(queries, list): + return [re.sub(pattern, '', str(query)) for query in queries] + elif isinstance(queries, str): + return re.sub(pattern, '', queries) + + +class TestQueryTranslator(unittest.TestCase): + """ + class to perform unit test case for sysdig translate query + """ + if __name__ == "__main__": + unittest.main() + + def _test_query_assertions(self, query, queries): + """ + to assert each query in the list against expected result + """ + self.assertIsInstance(queries, list) + self.assertIsInstance(query, dict) + self.assertIsInstance(query['queries'], list) + for index, each_query in enumerate(query.get('queries'), start=0): + self.assertEqual(each_query, queries[index]) + + def test_mac_addr_IN_operator(self): + stix_pattern = "[mac-addr:value IN('00:aa:dd:99:77:00','00:77:66:99:11:dd')] START " \ + "t'2023-08-01T00:00:00.00Z'STOP t'2023-08-14T10:00:00.00Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1690848000000000000&to=1692007200000000000&filter=(machineId in " + "(\"00:aa:dd:99:77:00\",\"00:77:66:99:11:dd\"))andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_mac_addr_not_equal_operator(self): + stix_pattern = "[mac-addr:value!='00:aa:dd:99:77:00'] START " \ + "t'2023-08-01T00:00:00.00Z'STOP t'2023-08-14T10:00:00.00Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1690848000000000000&to=1692007200000000000&filter=(machineId!=" + "\"00:aa:dd:99:77:00\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_oca_asset_query(self): + stix_pattern = "[x-oca-asset:extensions.'x-oca-container-ext'.x_repo != 'opencontent-etcd-operator']" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1695630479966000128&to=1695630779966000128&filter=(container." + "image.repo!=\"opencontent-etcd-operator\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_oca_asset_equal_operator(self): + stix_pattern = "[x-oca-asset:extensions.'x-oca-container-ext'.x_repo = 'opencontent-etcd-operator']" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1695630479966000128&to=1695630779966000128&filter=(container." + "image.repo=\"opencontent-etcd-operator\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_cloud_provider(self): + stix_pattern = "[x-cloud-provider:name='aws']" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1700445360000000000&to=1700791020003000064&filter=(cloudProvider.name=\"aws\")" + "andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_sysdig_policy_equal_operator(self): + stix_pattern = "[x-sysdig-policy:policy_id = 11111111]" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1695901608584999936&to=1695901908584999936&filter=(policyId=11111111)" + "andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_sysdig_policy_not_equal_operator(self): + stix_pattern = "[x-sysdig-policy:rule_name != 'Possible Backdoor using BPF']" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1695901830566000128&to=1695902130566000128&filter=(ruleName!=\"Possible " + "Backdoor using BPF\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_oca_asset_IN_operator(self): + stix_pattern = "[x-oca-asset:extensions.'x-oca-container-ext'.x_repo IN('opencontent-etcd-operator')]" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1700133198447000064&to=1700133498447000064&filter=(container.image.repo in " + "(\"opencontent-etcd-operator\"))andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_ibm_finding_query(self): + stix_pattern = "[x-ibm-finding:x_threat_source = 'syscall']" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1695631204718000128&to=1695631504718000128&filter=(source=\"syscall\")" + "andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_sysdig_deployment_query(self): + stix_pattern = "[x-sysdig-deployment:name = 'ibm-cloud-provider-ip-111-11-11-111']" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1695632727713999872&to=1695633027713999872&filter=(kubernetes.deployment." + "name=\"ibm-cloud-provider-ip-111-11-11-111\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_sysdig_deployment_not_equal_operator(self): + stix_pattern = "[x-sysdig-deployment:name != 'ibm-cloud-provider-ip-111-11-11-111']START " \ + "t'2023-08-01T00:00:00.00Z'STOP t'2023-08-14T10:00:00.00Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1697185737769999872&to=1697186037769999872&filter=(kubernetes.deployment." + "name!=\"ibm-cloud-provider-ip-111-11-11-111\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_x_ibm_finding_int_type_query(self): + stix_pattern = "[x-ibm-finding:severity > 100]" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1695646240753999872&to=1695646540753999872&filter=(severity>0)andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_comparison(self): + stix_pattern = "[x-sysdig-policy:policy_id = 77777777] AND [x-ibm-finding:severity != 100]" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1698317811217999872&to=1698318111217999872&filter=(policyId=77777777 or " + "severity!=0)andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_combined_comparison(self): + stix_pattern = "[x-sysdig-policy:policy_id > 77777777 AND x-ibm-finding:severity <= 80]" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1698317511743000064&to=1698317811743000064&filter=(severity<=2andpolicyId>77777777)" + "andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_comparison_with_precedence_bracket(self): + stix_pattern = "[(x-oca-asset:extensions.'x-oca-container-ext'.image_id='1a1a1a1a111a' " \ + "AND x-sysdig-cluster:namespace != " \ + "'openshift-cluster-node-tuning-operator' ) OR x-ibm-finding:x_category = 'runtime']" \ + "START t'2023-09-01T08:43:10.003Z'STOP t'2023-09-10T10:43:10.005Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1693557790003000064&to=1694342590004999936&filter=(category=\"runtime\"or" + "kubernetes.namespace.name!=\"openshift-cluster-node-tuning-operator\"andcontainer.image.id=" + "\"1a1a1a1a111a\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_comparison_joined_by_AND_query(self): + stix_pattern = "[x-sysdig-deployment:name = 'ibm-cloud-provider-ip-111-11-11-111' AND " \ + "x-ibm-finding:x_threat_source = 'syscall']START t'2023-08-01T00:00:00.00Z' " \ + "STOP t'2023-08-14T10:00:00.00Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1690848000000000000&to=1692007200000000000&filter=(source=\"syscall\"and" + "kubernetes.deployment.name=\"ibm-cloud-provider-ip-111-11-11-111\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_comparison_joined_by_OR_query(self): + stix_pattern = "[x-sysdig-deployment:name = 'ibm-cloud-provider-ip-111-11-11-111' OR " \ + "x-ibm-finding:x_threat_source = 'syscall']START t'2023-08-01T00:00:00.00Z' " \ + "STOP t'2023-08-14T10:00:00.00Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1690848000000000000&to=1692007200000000000&filter=(source=\"syscall\"or" + "kubernetes.deployment.name=\"ibm-cloud-provider-ip-111-11-11-111\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_query_from_morethan_two_comparison_expressions_joined_by_and(self): + stix_pattern = "[x-sysdig-cluster:namespace = 'dummycluster' AND x-sysdig-deployment:name != 'app-manager' " \ + "AND mac-addr:value IN('00:aa:dd:99:77:00','00:77:66:99:11:dd')]" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1698317121307000064&to=1698317421307000064&filter=(machineId in " + "(\"00:aa:dd:99:77:00\",\"00:77:66:99:11:dd\")andkubernetes.deployment.name!=\"app-manager\"and" + "kubernetes.namespace.name=\"dummycluster\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_observation_with_qualifier_query(self): + stix_pattern = "([x-sysdig-cluster:x_namespace='ap5s'AND x-sysdig-deployment:name='app-manager'] " \ + "AND [mac-addr:value='00:77:66:99:11:dd' OR x-ibm-finding:name !='90-DayImageAge'] OR " \ + "[x-oca-asset:extensions.'x-oca-container-ext'.x_repo='opencontent-etcd-operator'])" \ + "START t'2023-09-10T08:43:10.003Z' STOP t'2023-09-20T10:43:10.005Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1694335390003000064&to=1695206590004999936&filter=(ruleName!=\"90-DayImageAge\"ormachineId=" + "\"00:77:66:99:11:dd\" or container.image.repo=\"opencontent-etcd-operator\")" + "andsource!=\"auditTrail\"" + ] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_observation_without_qualifier_query(self): + stix_pattern = "[x-sysdig-cluster:x_namespace!='dummycluster' OR x-sysdig-deployment:name='app-manager'] OR " \ + "[mac-addr:value IN('00:aa:dd:99:77:99','66:77:88:99:11:dd') AND x-ibm-finding:name " \ + "='90-DayImageAge'] AND [x-oca-asset:extensions.'x-oca-container-ext'." \ + "x_repo='opencontent-etcd-operator']" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1699874733960000000&to=1699875033960000000&filter=(kubernetes.deployment.name=\"app-manager\" " + "or ruleName=\"90-DayImageAge\"andmachineId in (\"00:aa:dd:99:77:99\",\"66:77:88:99:11:dd\") " + "or container.image.repo=\"opencontent-etcd-operator\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_observation_with_and_without_qualifier_query(self): + stix_pattern = "[x-oca-asset:extensions.'x-oca-container-ext'.image_id='1a1a1a1a1111a' " \ + "OR x-sysdig-cluster:namespace != " \ + "'openshift-cluster-node-tuning-operator' ]START t'2023-09-01T08:43:10.003Z'STOP " \ + "t'2023-09-10T10:43:10.005Z' AND [x-ibm-finding:x_category = 'runtime']" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1693557790003000064&to=1694342590004999936&filter=(kubernetes.namespace.name!=" + "\"openshift-cluster-node-tuning-operator\"orcontainer.image.id=\"1a1a1a1a1111a\")" + "andsource!=\"auditTrail\"", + "from=1698316240620000000&to=1698316540620000000&filter=(category=" + "\"runtime\")andsource!=\"auditTrail\""] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_observation_by_AND_operator(self): + stix_pattern = "[x-ibm-finding:severity <= 100] AND [x-sysdig-deployment:name = 'app-manager'] AND " \ + "[x-sysdig-cluster:namespace = 'ap5s']START t'2023-09-01T08:43:10.003Z'STOP " \ + "t'2023-09-10T10:43:10.005Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = [ + "from=1699442518569999872&to=1699442818569999872&filter=(severity<=0 or kubernetes.deployment.name=" + "\"app-manager\")andsource!=\"auditTrail\"", + "from=1693557790003000064&to=1694342590004999936&filter=(kubernetes.namespace.name=\"ap5s\")" + "andsource!=\"auditTrail\"" + ] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_observation_combined_by_AND_OR_operators(self): + stix_pattern = "[x-ibm-finding:severity >= 50 AND x-sysdig-deployment:name = 'app-manager']" \ + "START t'2023-09-15T08:43:10.003Z'STOP t'2023-09-25T10:43:10.005Z' OR " \ + "[x-sysdig-cluster:namespace = 'cluster'] AND [mac-addr:value IN " \ + "('00:aa:dd:99:22:00','00:55:88:99:11:dd') OR x-ibm-finding:x_threat_source = 'syscall']" \ + "START t'2023-09-01T08:43:10.003Z'STOP t'2023-09-10T10:43:10.005Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1694767390003000064&to=1695638590004999936&filter=(kubernetes.deployment.name=\"app-manager\"" + "andseverity>=5)andsource!=\"auditTrail\"", + "from=1699442789169999872&to=1699443089169999872&filter=(kubernetes.namespace.name=\"cluster\")" + "andsource!=\"auditTrail\"", + "from=1693557790003000064&to=1694342590004999936&filter=(source=\"syscall\"ormachineId in " + "(\"00:aa:dd:99:22:00\",\"00:55:88:99:11:dd\"))andsource!=\"auditTrail\"" + ] + + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_multiple_comparison_with_OR_query(self): + stix_pattern = "[x-ibm-finding:severity >= 100] OR [x-sysdig-deployment:name = 'app-manager'] OR " \ + "[x-sysdig-cluster:namespace = 'cluster']START t'2023-09-01T08:43:10.003Z' STOP " \ + "t'2023-09-10T10:43:10.005Z'" + query = translation.translate('sysdig', 'query', '{}', stix_pattern) + query['queries'] = _remove_timestamp_from_query(query['queries']) + queries = ["from=1698304848484999936&to=1698305148484999936&filter=(severity>=0 or " + "kubernetes.deployment.name=\"app-manager\")andsource!=\"auditTrail\"", + "from=1693557790003000064&to=1694342590004999936&filter=(kubernetes.namespace.name=\"cluster\")" + "andsource!=\"auditTrail\"" + ] + queries = _remove_timestamp_from_query(queries) + self._test_query_assertions(query, queries) + + def test_invalid_int_type_value(self): + stix_pattern = "[x-ibm-finding:severity = 'sysdig']" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "not_implemented" == result['code'] + assert "sysdig is not supported for integer type fields" in result['error'] + + def test_invalid_severity_value(self): + stix_pattern = "[x-ibm-finding:severity = 200]" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "not_implemented" == result['code'] + assert "only 1-100 integer values are supported with this field" in result['error'] + + def test_with_greater_time_stamp_type(self): + stix_pattern = "[x-sysdig-deployment:name = 'app-manager'] START t'2023-10-01T08:43:10.003Z' " \ + "STOP t'2023-09-10T10:43:10.005Z'" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "translation_error" == result['code'] + assert "Start time should be lesser than Stop time" in result['error'] + + def test_with_invalid_mapped_field_query(self): + stix_pattern = "[x-ibm-finding:x_archived = 0]" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "mapping_error" == result['code'] + assert "Unable to map the following STIX objects and properties" in result['error'] + + def test_not_supported_operator_MATCHES(self): + stix_pattern = "[x-ibm-finding:severity MATCHES '100']" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "mapping_error" == result['code'] + assert "Unable to map the following STIX Operators" in result['error'] + + def test_not_supported_operator_LIKE(self): + stix_pattern = "[x-ibm-finding:severity LIKE 100]" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "mapping_error" == result['code'] + assert "Unable to map the following STIX Operators" in result['error'] + + def test_check_invalid_mac_adr_format(self): + stix_pattern = "[mac-addr:value='00.aa.dd.99.77.99']" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "not_implemented" == result['code'] + assert "Invalid mac address - 00.aa.dd.99.77.99 provide" in result['error'] + + def test_check_invalid_operator(self): + stix_pattern = "[x-sysdig-deployment:name > 'app-manager']" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "not_implemented" == result['code'] + assert "> operator is not supported for string type input" in result['error'] + + def test_check_audittrail_error(self): + stix_pattern = "[x-ibm-finding:x_threat_source = 'auditTrail']" + result = translation.translate('sysdig', 'query', '{}', stix_pattern) + assert result['success'] is False + assert "not_implemented" == result['code'] + assert "Sysdig connector does not provide auditTrail event" in result['error'] diff --git a/stix_shifter_modules/sysdig/tests/stix_transmission/test_sysdig.py b/stix_shifter_modules/sysdig/tests/stix_transmission/test_sysdig.py new file mode 100644 index 000000000..3c82f9258 --- /dev/null +++ b/stix_shifter_modules/sysdig/tests/stix_transmission/test_sysdig.py @@ -0,0 +1,588 @@ +""" test script to perform unit test case for sysdig transmit module """ +import json +import unittest +from unittest.mock import patch +from stix_shifter_modules.sysdig.entry_point import EntryPoint +from stix_shifter.stix_transmission import stix_transmission +from stix_shifter.stix_transmission.stix_transmission import run_in_thread +from tests.utils.async_utils import get_mock_response + + +class sysdigMockResponse: + """ class for sysdig mock response""" + + def __init__(self, code, data): + self.code = code + self.content = data + + def read(self): + return bytearray(self.content, 'utf-8') + + +class TestsysdigConnection(unittest.TestCase): + mocked_response = {'page': {'total': 1, 'prev': 'ABC3ODI4OFHIwM2EzZTY1ODJkYzMzYWEwODBhMTVmMGM123', + 'next': 'ABCzE3ODI4OGYwM2EzZTY1ODJkYzMzYWEwODBhMTVmMGM123'}, + 'data': [ + { + "id": "12345678910", + "cursor": "ABCDEFGHIJKLMN", + "timestamp": "2023-11-22T11:16:28.101680299Z", + "customerId": 10101010, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "sysdig custom", + "description": "updated network rules with network tag", + "severity": 0, + "agentId": 111111, + "containerId": "1010101010", + "machineId": "11:11:1b:11:11:11", + "content": { + "falsePositive": "false", + "fields": { + "container.id": "1010101010", + "container.image.repository": "docker.io", + "container.image.tag": "curl", + "container.name": "curl-sample-app1", + "evt.res": "EINPROGRESS", + "evt.type": "connect", + "falco.rule": "Contact EC2", + "fd.name": "111.111.11.111:10000->101.101.101.101:10", + "group.gid": "0", + "group.name": "root", + "proc.aname[2]": "containerd-shim", + "proc.aname[3]": "systemd", + "proc.aname[4]": "", + "proc.args": "-s http://101.101.101.101:10/iam/security-credentials", + "proc.cmdline": "curl -s http://101.101.101.101:10/iam/security-credentials", + "proc.cwd": "/", + "proc.exepath": "/usr/bin/curl", + "proc.name": "curl", + "proc.pcmdline": "sh", + "proc.pid": "12345", + "proc.pname": "sh", + "proc.ppid": "11111", + "proc.sid": "1", + "user.loginname": "", + "user.loginuid": "-1", + "user.name": "root", + "user.uid": "0", + "proc.anames": [ + "containerd-shim", + "systemd" + ] + }, + "internalRuleName": "Contact", + "matchedOnDefault": "false", + "origin": "Secure UI", + "policyId": 100100, + "ruleName": "Contact EC2", + "ruleSubType": 0, + "ruleTags": [ + "container", + "network", + "aws", + "SOC2", + "SOC2_CC6.8", + "SOC2_CC6.1", + "NIST", + "NIST_800-171", + "NIST_800-171_3.1.1", + "NIST_800-171_3.1.2", + "NIST_800-171_3.1.3", + "NIST_800-171_3.14.6", + "NIST_800-171_3.14.7", + "NIST_800-171_3.4.6", + "NIST_800-53", + "NIST_800-53_AC-4", + "NIST_800-53_AC-17", + "NIST_800-53_SI-4(18)", + "NIST_800-53_SI-4", + "NIST_800-53_CM-7", + "FedRAMP", + "FedRAMP_CM-7", + "ISO", + "ISO_27001", + "ISO_27001_A.9.1.2", + "HIPAA", + "HIPAA_164.308(a)", + "HIPAA_164.310(b)", + "HITRUST", + "HITRUST_CSF", + "HITRUST_CSF_01.c", + "HITRUST_CSF_01.i", + "HITRUST_CSF_01.j", + "HITRUST_CSF_01.l", + "HITRUST_CSF_01.n", + "HITRUST_CSF_01.x", + "HITRUST_CSF_01.y", + "HITRUST_CSF_09.ab", + "HITRUST_CSF_09.ac", + "HITRUST_CSF_09.i", + "HITRUST_CSF_09.m", + "HITRUST_CSF_09.s", + "HITRUST_CSF_10.j", + "HITRUST_CSF_10.m", + "HITRUST_CSF_11.a", + "HITRUST_CSF_11.b", + "GDPR", + "GDPR_32.1", + "GDPR_32.2", + "MITRE", + "MITRE_T1552_unsecured_credentials", + "MITRE_T1552.005_unsecured_credentials_cloud_instance_metadata_api", + "MITRE_TA0006_credential_access", + "MITRE_TA0007_discovery", + "MITRE_T1552.007_unsecured_credentials_container_api", + "MITRE_T1033_system_owner_user_discovery", + "MITRE_T119_automated-collection", + "MITRE_TA0009_collection" + ], + "ruleType": 6 + }, + "labels": { + "aws.accountId": "111111111111", + "aws.instanceId": "i-1111111111", + "aws.region": "us-east-1", + "cloudProvider.account.id": "111111111111", + "cloudProvider.name": "aws", + "cloudProvider.region": "us-east-1", + "container.image.digest": "sha256:12345", + "container.image.id": "10101010", + "container.image.repo": "docker.io/radial/busyboxplus", + "container.image.tag": "curl", + "container.label.io.kubernetes.container.name": "curl-sample-app1", + "container.label.io.kubernetes.pod.name": "curl-sample-app1", + "container.label.io.kubernetes.pod.namespace": "default", + "container.name": "curl-sample-app1", + "host.hostName": "ip-111-111-11-111.ec2.internal", + "host.mac": "11:11:11:11:11:11", + "kubernetes.cluster.name": "sysdig", + "kubernetes.namespace.name": "default", + "kubernetes.node.name": "ip-111-111-11-111.ec2.internal", + "kubernetes.pod.name": "curl-sample-app1", + "process.name": "curl -s http://101.101.101.101/iam/security-credentials" + }, + "finding_type": "threat", + "direction": "out", + "clientIpv4": "111.111.11.111", + "clientPort": "10000", + "serverIpv4": "101.101.101.101", + "serverPort": "10", + "l4protocol": "tcp" + } + ] + } + + replicated_data = [] + for _ in range(1000): + replicated_data += mocked_response['data'] + mocked_response['data'] = replicated_data + network_mocked_response = {'page': {'total': 1, 'prev': 'ABC3ODI4OFHIwM2EzZTY1ODJkYzMzYWEwODBhMTVmMGM123', + 'next': 'ABCzE3ODI4OGYwM2EzZTY1ODJkYzMzYWEwODBhMTVmMGM123'}, + 'data': [ + { + "id": "12345678910", + "cursor": "ABCDEFGHIJKLMN", + "timestamp": "2023-11-22T11:16:28.101680299Z", + "customerId": 10101010, + "originator": "policy", + "category": "runtime", + "source": "syscall", + "name": "sysdig custom", + "description": "updated network rules with network tag", + "severity": 0, + "agentId": 111111, + "containerId": "1010101010", + "machineId": "11:11:1b:11:11:11", + "content": { + "falsePositive": "false", + "fields": { + "container.id": "1010101010", + "container.image.repository": "docker.io", + "container.image.tag": "curl", + "container.name": "curl-sample-app1", + "evt.res": "EINPROGRESS", + "evt.type": "connect", + "falco.rule": "Contact EC2", + "fd.name": "111.111.11.111:10000<-101.101.101.101:10", + "group.gid": "0", + "group.name": "root", + "proc.aname[2]": "containerd-shim", + "proc.aname[3]": "systemd", + "proc.aname[4]": "", + "proc.args": "-s http://101.101.101.101:10/iam/security-credentials", + "proc.cmdline": "curl -s http://101.101.101.101:10/" + "iam/security-credentials", + "proc.cwd": "/", + "proc.exepath": "/usr/bin/curl", + "proc.name": "curl", + "proc.pcmdline": "sh", + "proc.pid": "12345", + "proc.pname": "sh", + "proc.ppid": "11111", + "proc.sid": "1", + "user.loginname": "", + "user.loginuid": "-1", + "user.name": "root", + "user.uid": "0", + "proc.anames": [ + "containerd-shim", + "systemd" + ] + }, + "internalRuleName": "Contact", + "matchedOnDefault": "false", + "origin": "Secure UI", + "policyId": 100100, + "ruleName": "Contact EC2", + "ruleSubType": 0, + "ruleTags": [ + "container", + "network", + "aws", + "SOC2", + "SOC2_CC6.8", + "SOC2_CC6.1", + "NIST", + "NIST_800-171", + "NIST_800-171_3.1.1", + "NIST_800-171_3.1.2", + "NIST_800-171_3.1.3", + "NIST_800-171_3.14.6", + "NIST_800-171_3.14.7", + "NIST_800-171_3.4.6", + "NIST_800-53", + "NIST_800-53_AC-4", + "NIST_800-53_AC-17", + "NIST_800-53_SI-4(18)", + "NIST_800-53_SI-4", + "NIST_800-53_CM-7", + "FedRAMP", + "FedRAMP_CM-7", + "ISO", + "ISO_27001", + "ISO_27001_A.9.1.2", + "HIPAA", + "HIPAA_164.308(a)", + "HIPAA_164.310(b)", + "HITRUST", + "HITRUST_CSF", + "HITRUST_CSF_01.c", + "HITRUST_CSF_01.i", + "HITRUST_CSF_01.j", + "HITRUST_CSF_01.l", + "HITRUST_CSF_01.n", + "HITRUST_CSF_01.x", + "HITRUST_CSF_01.y", + "HITRUST_CSF_09.ab", + "HITRUST_CSF_09.ac", + "HITRUST_CSF_09.i", + "HITRUST_CSF_09.m", + "HITRUST_CSF_09.s", + "HITRUST_CSF_10.j", + "HITRUST_CSF_10.m", + "HITRUST_CSF_11.a", + "HITRUST_CSF_11.b", + "GDPR", + "GDPR_32.1", + "GDPR_32.2", + "MITRE", + "MITRE_T1552_unsecured_credentials", + "MITRE_T1552.005_unsecured_credentials_cloud_instance_metadata_api", + "MITRE_TA0006_credential_access", + "MITRE_TA0007_discovery", + "MITRE_T1552.007_unsecured_credentials_container_api", + "MITRE_T1033_system_owner_user_discovery", + "MITRE_T119_automated-collection", + "MITRE_TA0009_collection" + ], + "ruleType": 6 + }, + "labels": { + "aws.accountId": "111111111111", + "aws.instanceId": "i-1111111111", + "aws.region": "us-east-1", + "cloudProvider.account.id": "111111111111", + "cloudProvider.name": "aws", + "cloudProvider.region": "us-east-1", + "container.image.digest": "sha256:12345", + "container.image.id": "10101010", + "container.image.repo": "docker.io/radial/busyboxplus", + "container.image.tag": "curl", + "container.label.io.kubernetes.container.name": "curl-sample-app1", + "container.label.io.kubernetes.pod.name": "curl-sample-app1", + "container.label.io.kubernetes.pod.namespace": "default", + "container.name": "curl-sample-app1", + "host.hostName": "ip-111-111-11-111.ec2.internal", + "host.mac": "11:11:11:11:11:11", + "kubernetes.cluster.name": "sysdig", + "kubernetes.namespace.name": "default", + "kubernetes.node.name": "ip-111-111-11-111.ec2.internal", + "kubernetes.pod.name": "curl-sample-app1", + "process.name": "curl -s http://101.101.101.101/iam/security-credentials", + }, + "finding_type": "threat", + "direction": "in", + "clientIpv4": "101.101.101.101", + "clientPort": "10", + "serverIpv4": "111.111.111.111", + "serverPort": "10000", + "l4protocol": "tcp" + } + ] + } + + @staticmethod + def connection(): + """format for connection""" + return { + "host": "testhost", + "port": 123 + } + + @staticmethod + def configuration(): + """format for configuration""" + return { + "auth": { + "token": "test" + + } + } + + def test_is_async(self): + """check for synchronous or asynchronous""" + entry_point = EntryPoint(self.connection(), self.configuration()) + check_async = entry_point.is_async() + assert check_async is False + + @patch('stix_shifter_modules.sysdig.stix_transmission.api_client.APIClient.ping_data_source') + def test_get_ping_results(self, mock_ping_response): + """test ping connection""" + mock_ping_response.return_value = get_mock_response(200, json.dumps(TestsysdigConnection.mocked_response), + 'byte') + entry_point = EntryPoint(self.connection(), self.configuration()) + ping_response = run_in_thread(entry_point.ping_connection) + assert ping_response is not None + assert ping_response['success'] is True + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_ping_timeout_error(self, mock_result_response): + """Test timeout error for ping""" + mock_result_response.side_effect = Exception(" server timeout_error") + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + ping_response = transmission.ping() + assert ping_response is not None + assert ping_response['success'] is False + assert "server timeout_error" in ping_response['error'] + assert ping_response['code'] == "service_unavailable" + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_ping_invalid_host(self, mock_result_response): + """Test Invalid host for ping""" + mock_result_response.side_effect = Exception("client_connector_error") + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + ping_response = transmission.ping() + assert ping_response is not None + assert ping_response['success'] is False + assert "client_connector_error" in ping_response['error'] + assert ping_response['code'] == "service_unavailable" + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_ping_invalid_token_results(self, mock_result_response): + """ test invalid token results response""" + mocked_return_value = json.dumps({ + "success": 'false', + "connector": "sysdig", + "error": "sysdig connector error => cannot verify credentials'", + "code": "authentication_fail" + }) + mock_result_response.return_value = sysdigMockResponse(401, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + ping_response = transmission.ping() + assert ping_response is not None + assert ping_response['success'] is False + assert ping_response['code'] == 'authentication_fail' + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_success_query_results(self, mock_result_response): + """ test success result response""" + mocked_return_value = json.dumps(TestsysdigConnection.mocked_response) + query = "from=1693526400000000000&to=1694512800000000000&filter=kubernetes.cluster.name=\"cp5s-cluster\"" + mock_result_response.return_value = sysdigMockResponse(200, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is True + assert 'data' in result_response + assert result_response['data'] is not None + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_success_query_results_with_pagination(self, mock_result_response): + """ test success pagination result response""" + mocked_return_value = json.dumps(TestsysdigConnection.mocked_response) + query = "from=1697388206000000000&to=1698511406003000064&filter=(source=\"syscall\"andoriginator=\"policy\"" \ + "andcategory=\"runtime\")andsource!=\"auditTrail\"" + mock_result_response.return_value = sysdigMockResponse(200, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 2000 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is True + assert 'data' in result_response + assert result_response['data'] is not None + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_success_query_results_with_network_data(self, mock_result_response): + """ test success network fields result response""" + mocked_return_value = json.dumps(TestsysdigConnection.network_mocked_response) + query = "from=1697388206000000000&to=1698511406003000064&filter=(source=\"syscall\"andoriginator=\"policy\"" \ + "andcategory=\"runtime\")andsource!=\"auditTrail\"" + mock_result_response.return_value = sysdigMockResponse(200, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 2000 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is True + assert 'data' in result_response + assert result_response['data'] is not None + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_success_query_with_metadata_parameter(self, mock_result_response): + """ test success result response with metadata parameter""" + mocked_return_value = json.dumps(TestsysdigConnection.mocked_response) + metadata = {"result_count": 1, "prev_page_token": "a12b3c"} + query = "from=1693526400000000000&to=1694512800000000000&filter=kubernetes.cluster.name=\"ap4s-cluster\"" + mock_result_response.return_value = sysdigMockResponse(200, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length, metadata) + assert result_response is not None + assert result_response['success'] is True + assert 'data' in result_response + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_success_with_invalid_metadata_parameter(self, mock_result_response): + """ test invalid metadata parameter""" + mocked_return_value = json.dumps(TestsysdigConnection.mocked_response) + metadata = {"prev_page_token": "a12b3c"} + query = "from=1693526400000000000&to=1694512800000000000&filter=kubernetes.cluster.name=\"ap4s-cluster\"" + mock_result_response.return_value = sysdigMockResponse(200, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length, metadata) + assert result_response is not None + assert result_response['success'] is False + assert "Invalid metadata" in result_response['error'] + assert result_response['code'] == "invalid_parameter" + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_query_results_with_invalid_host(self, mock_result_response): + """ test invalid host """ + mock_result_response.side_effect = Exception("client_connector_error") + query = "from=1693526400000000000&to=1694512800000000000&filter=kubernetes.cluster.name=\"ap5s-cluster\"" + mock_result_response.return_value = sysdigMockResponse(200, mock_result_response) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is False + assert 'client_connector_error' in result_response['error'] + assert result_response['code'] == 'service_unavailable' + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_query_server_timeout_error(self, mock_result_response): + """ test query server timeout error""" + mock_result_response.side_effect = Exception("server timeout_error") + query = "from=1693526400000000000&to=1694512800000000000&filter=kubernetes.cluster.name=\"cp5s-cluster\"" + mock_result_response.return_value = sysdigMockResponse(200, mock_result_response) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is False + assert 'server timeout_error' in result_response['error'] + assert result_response['code'] == 'service_unavailable' + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_query_timeout_error(self, mock_result_response): + """ test query timeout error""" + mock_result_response.side_effect = Exception("timeout_error") + query = "from=1697388206000000000&to=1698511406003000064&filter=(source=\"syscall\"andoriginator=\"policy\"" \ + "andcategory=\"runtime\")andsource!=\"auditTrail\"" + mock_result_response.return_value = sysdigMockResponse(200, mock_result_response) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is False + assert 'timeout_error' in result_response['error'] + assert result_response['code'] == 'service_unavailable' + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_invalid_query_results(self, mock_result_response): + """ test error result response""" + mocked_return_value = json.dumps({ + "success": 'false', + "connector": "sysdig", + "error": "sysdig connector error => bad request", + "code": "invalid_parameter"}) + + query = "from=1693526400000000000&to=1694512800000000000&filter=kubernetes.cluster.name=\"cp5s-cluster\"" + mock_result_response.return_value = sysdigMockResponse(400, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is False + assert result_response['code'] == 'invalid_parameter' + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_invalid_token_results(self, mock_result_response): + """ test invalid token response""" + mocked_return_value = json.dumps({ + "success": 'false', + "connector": "sysdig", + "error": "sysdig connector error => cannot verify credentials", + "code": "authentication_fail" + }) + query = "from=1693526400000000000&to=1694512800000000000&filter=kubernetes.cluster.name=\"cp5s-cluster\"" + mock_result_response.return_value = sysdigMockResponse(401, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is False + assert result_response['code'] == 'authentication_fail' + + @patch('stix_shifter_utils.stix_transmission.utils.RestApiClientAsync.RestApiClientAsync.call_api') + def test_exception_query_check(self, mock_result_response): + """ test error result response""" + mocked_return_value = json.dumps({ + "success": 'false', + "connector": "sysdig", + "error": "sysdig connector error => unsupported metric", + "code": "invalid_query" + }) + + query = "from=1693526400000000000&to=1694512800000000000&filter=kubernetes.cluster.name=\"cp5s-cluster\"" + mock_result_response.return_value = sysdigMockResponse(422, mocked_return_value) + transmission = stix_transmission.StixTransmission('sysdig', self.connection(), self.configuration()) + offset = 0 + length = 1 + result_response = transmission.results(query, offset, length) + assert result_response is not None + assert result_response['success'] is False + assert result_response['code'] == 'invalid_query'