├── .circleci
└── config.yml
├── .drone.yml
├── .flake8
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── auto-set-labels.yml
│ └── codeql-analysis.yml
├── .gitignore
├── .grenrc.js
├── .isort.cfg
├── .pre-commit-config.yaml
├── .readthedocs.yml
├── .yamllint.yaml
├── LICENSE
├── Makefile
├── README.md
├── docs
├── Makefile
├── client_usage
│ └── getting_started.rst
├── conf.py
├── index.rst
├── make.bat
├── pycti
│ ├── pycti.api.opencti_api_client.rst
│ ├── pycti.api.opencti_api_connector.rst
│ ├── pycti.api.opencti_api_job.rst
│ ├── pycti.api.rst
│ ├── pycti.connector.opencti_connector.rst
│ ├── pycti.connector.opencti_connector_helper.rst
│ ├── pycti.connector.rst
│ ├── pycti.entities.opencti_attack_pattern.rst
│ ├── pycti.entities.opencti_campaign.rst
│ ├── pycti.entities.opencti_capability.rst
│ ├── pycti.entities.opencti_course_of_action.rst
│ ├── pycti.entities.opencti_external_reference.rst
│ ├── pycti.entities.opencti_group.rst
│ ├── pycti.entities.opencti_identity.rst
│ ├── pycti.entities.opencti_incident.rst
│ ├── pycti.entities.opencti_indicator.rst
│ ├── pycti.entities.opencti_intrusion_set.rst
│ ├── pycti.entities.opencti_kill_chain_phase.rst
│ ├── pycti.entities.opencti_malware.rst
│ ├── pycti.entities.opencti_marking_definition.rst
│ ├── pycti.entities.opencti_note.rst
│ ├── pycti.entities.opencti_opinion.rst
│ ├── pycti.entities.opencti_report.rst
│ ├── pycti.entities.opencti_role.rst
│ ├── pycti.entities.opencti_settings.rst
│ ├── pycti.entities.opencti_stix_domain_entity.rst
│ ├── pycti.entities.opencti_stix_entity.rst
│ ├── pycti.entities.opencti_stix_observable.rst
│ ├── pycti.entities.opencti_stix_observable_relation.rst
│ ├── pycti.entities.opencti_stix_relation.rst
│ ├── pycti.entities.opencti_stix_sighting.rst
│ ├── pycti.entities.opencti_tag.rst
│ ├── pycti.entities.opencti_threat_actor.rst
│ ├── pycti.entities.opencti_tool.rst
│ ├── pycti.entities.opencti_user.rst
│ ├── pycti.entities.opencti_vulnerability.rst
│ ├── pycti.entities.rst
│ ├── pycti.rst
│ ├── pycti.utils.constants.rst
│ ├── pycti.utils.opencti_stix2.rst
│ └── pycti.utils.rst
└── requirements.txt
├── examples
├── add_external_reference_to_report.py
├── add_label_to_malware.py
├── add_label_to_observable.py
├── add_organization_to_sector.py
├── add_tool_usage_to_intrusion-set.py
├── ask_enrichment_of_observable.py
├── cmd_line_tag_latest_indicators_of_threat.py
├── create_campaign_attributed-to_intrusion_set.py
├── create_file_with_hashes.py
├── create_incident_with_ttps_and_indicators.py
├── create_indicator_of_campaign.py
├── create_intrusion_set.py
├── create_ip_domain_resolution.py
├── create_marking_definition.py
├── create_observable_relationships.py
├── create_process_observable.py
├── create_report_with_author.py
├── delete_intrusion_set.py
├── delete_relation.py
├── export_async_of_indicators.py
├── export_async_of_malware.py
├── export_incident_stix2.py
├── export_incidents_stix2.py
├── export_intrusion_set_stix2.py
├── export_report_stix2.py
├── get_all_indicators_using_pagination.py
├── get_all_reports_using_pagination.py
├── get_attack_pattern_by_mitre_id.py
├── get_entity_by_name_or_alias.py
├── get_malwares_of_intrusion_set.py
├── get_marking_definitions.py
├── get_observable_exact_match.py
├── get_observables_search.py
├── get_reports_about_intrusion_set.py
├── import_stix2_file.py
├── promote_observable_to_indicator.py
├── run_all.sh
├── search_attack_pattern.py
├── search_malware.py
├── update_entity_attribute.py
├── update_observable_attributes.py
├── upload_artifacts.py
├── upload_file.py
├── upload_file_example.pdf
└── upload_file_to_intrusion_set.py
├── pycti
├── __init__.py
├── api
│ ├── __init__.py
│ ├── opencti_api_client.py
│ ├── opencti_api_connector.py
│ ├── opencti_api_playbook.py
│ └── opencti_api_work.py
├── connector
│ ├── __init__.py
│ ├── opencti_connector.py
│ ├── opencti_connector_helper.py
│ └── opencti_metric_handler.py
├── entities
│ ├── __init__.py
│ ├── indicator
│ │ ├── __init__.py
│ │ └── opencti_indicator_properties.py
│ ├── opencti_attack_pattern.py
│ ├── opencti_campaign.py
│ ├── opencti_capability.py
│ ├── opencti_case_incident.py
│ ├── opencti_case_rfi.py
│ ├── opencti_case_rft.py
│ ├── opencti_channel.py
│ ├── opencti_course_of_action.py
│ ├── opencti_data_component.py
│ ├── opencti_data_source.py
│ ├── opencti_event.py
│ ├── opencti_external_reference.py
│ ├── opencti_feedback.py
│ ├── opencti_group.py
│ ├── opencti_grouping.py
│ ├── opencti_identity.py
│ ├── opencti_incident.py
│ ├── opencti_indicator.py
│ ├── opencti_infrastructure.py
│ ├── opencti_intrusion_set.py
│ ├── opencti_kill_chain_phase.py
│ ├── opencti_label.py
│ ├── opencti_language.py
│ ├── opencti_location.py
│ ├── opencti_malware.py
│ ├── opencti_malware_analysis.py
│ ├── opencti_marking_definition.py
│ ├── opencti_narrative.py
│ ├── opencti_note.py
│ ├── opencti_observed_data.py
│ ├── opencti_opinion.py
│ ├── opencti_report.py
│ ├── opencti_role.py
│ ├── opencti_settings.py
│ ├── opencti_stix.py
│ ├── opencti_stix_core_object.py
│ ├── opencti_stix_core_relationship.py
│ ├── opencti_stix_cyber_observable.py
│ ├── opencti_stix_domain_object.py
│ ├── opencti_stix_nested_ref_relationship.py
│ ├── opencti_stix_object_or_stix_relationship.py
│ ├── opencti_stix_sighting_relationship.py
│ ├── opencti_task.py
│ ├── opencti_threat_actor.py
│ ├── opencti_threat_actor_group.py
│ ├── opencti_threat_actor_individual.py
│ ├── opencti_tool.py
│ ├── opencti_user.py
│ ├── opencti_vocabulary.py
│ ├── opencti_vulnerability.py
│ └── stix_cyber_observable
│ │ ├── __init__.py
│ │ ├── opencti_stix_cyber_observable_deprecated.py
│ │ └── opencti_stix_cyber_observable_properties.py
└── utils
│ ├── __init__.py
│ ├── constants.py
│ ├── opencti_logger.py
│ ├── opencti_stix2.py
│ ├── opencti_stix2_identifier.py
│ ├── opencti_stix2_splitter.py
│ ├── opencti_stix2_update.py
│ └── opencti_stix2_utils.py
├── pyproject.toml
├── renovate.json
├── requirements.txt
├── scripts
└── clone-opencti.sh
├── setup.cfg
├── test-requirements.txt
├── test.json
└── tests
├── 01-unit
├── metric
│ ├── __init__.py
│ └── test_opencti_metric_handler.py
├── stix
│ ├── __init__.py
│ └── test_bundle_ids_rewrite.py
└── utils
│ ├── __init__.py
│ ├── test_constants.py
│ ├── test_opencti_stix2.py
│ └── test_opencti_stix2_splitter.py
├── 02-integration
├── __init__.py
├── connector
│ ├── __init__.py
│ └── test_connector.py
├── entities
│ ├── __init__.py
│ ├── test_entity_crud.py
│ ├── test_entity_filter.py
│ ├── test_group.py
│ ├── test_indicators.py
│ ├── test_malware.py
│ ├── test_observables.py
│ ├── test_role.py
│ ├── test_settings.py
│ └── test_user.py
└── utils
│ └── test_stix_crud.py
├── __init__.py
├── cases
├── __init__.py
├── connectors.py
└── entities.py
├── conftest.py
├── data
├── DATA-TEST-STIX2_v2.json
├── basicMalwareWithSample.json
├── bundle_ids_sample.json
├── bundle_with_internal_ids.json
├── cyclic-bundle.json
├── enterprise-attack.json
├── external_import_config.yml
├── indicator_stix.json
├── internal_enrichment_config.yml
├── internal_import_config.yml
├── internal_import_data.txt
├── missing_refs.json
├── mitre_att_capec.json
├── mono-bundle-entity.json
├── mono-bundle-relationship.json
└── sectors.json
└── utils.py
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2.1
3 | orbs:
4 | slack: circleci/slack@4.15.0
5 | jobs:
6 | ensure_formatting:
7 | docker:
8 | - image: cimg/python:3.13
9 | working_directory: ~/repo
10 | steps:
11 | - checkout
12 | - run:
13 | name: install dependencies
14 | command: pip3 install -r requirements.txt --user
15 | - run:
16 | name: install test-dependencies
17 | command: pip3 install -r test-requirements.txt --user
18 | - run:
19 | name: confirm black version
20 | command: black --version
21 | - run:
22 | name: run isort check
23 | command: isort --check-only .
24 | - run:
25 | name: run black check
26 | command: black --check .
27 | - slack/notify:
28 | branch_pattern: master
29 | event: fail
30 | template: basic_fail_1
31 | linter:
32 | docker:
33 | - image: alpine/flake8
34 | working_directory: ~/repo
35 | steps:
36 | - checkout
37 | - run:
38 | name: flake8
39 | command: flake8 --ignore=E,W ~/repo
40 | - slack/notify:
41 | branch_pattern: master
42 | event: fail
43 | template: basic_fail_1
44 | build:
45 | working_directory: ~/opencti-client
46 | docker:
47 | - image: cimg/python:3.13
48 | steps:
49 | - checkout
50 | - run:
51 | name: install dependencies
52 | command: >
53 | pip3 install --user -r requirements.txt -r test-requirements.txt
54 | - run:
55 | name: check version
56 | command: |
57 | package_version="$(
58 | python3 -c 'import pycti; print(pycti.__version__)'
59 | )"
60 | [ "${CIRCLE_TAG}" = "${package_version}" ] \
61 | || printf "Version mismatch: %s is not equal to %s\n" \
62 | "${CIRCLE_TAG}" "${package_version}"
63 | - run:
64 | name: build
65 | command: >
66 | SOURCE_DATE_EPOCH="$(git log -1 --pretty=%ct)" python3 -m build
67 | - slack/notify:
68 | branch_pattern: master
69 | event: fail
70 | template: basic_fail_1
71 | - persist_to_workspace:
72 | root: ~/opencti-client
73 | paths:
74 | - dist
75 | deploy:
76 | working_directory: ~/opencti-client
77 | docker:
78 | - image: cimg/python:3.13
79 | steps:
80 | - checkout
81 | - attach_workspace:
82 | at: ~/opencti-client
83 | - run:
84 | name: install dependencies
85 | command: pip3 install -r requirements.txt --user
86 | - run:
87 | name: install twine
88 | command: pip3 install twine
89 | - run:
90 | name: init .pypirc
91 | command: >
92 | printf "[pypi]\nusername = __token__\npassword = %s\n"
93 | "${PYPI_PASSWORD}" >> ~/.pypirc
94 | - run:
95 | name: upload to pypi
96 | command: twine upload dist/*
97 | - slack/notify:
98 | branch_pattern: master
99 | event: fail
100 | template: basic_fail_1
101 | notify_rolling:
102 | docker:
103 | - image: "cimg/base:stable"
104 | steps:
105 | - slack/notify:
106 | event: pass
107 | template: basic_success_1
108 | notify:
109 | docker:
110 | - image: "cimg/base:stable"
111 | steps:
112 | - slack/notify:
113 | event: pass
114 | template: basic_success_1
115 |
116 | workflows:
117 | version: 2
118 | opencti_client_python:
119 | jobs:
120 | - ensure_formatting:
121 | filters:
122 | tags:
123 | only: /.*/
124 | - linter:
125 | filters:
126 | tags:
127 | only: /.*/
128 | - build:
129 | filters:
130 | tags:
131 | only: /[0-9]+(\.[0-9]+)+(\.[0-9]+)?\.?(\w)*/
132 | branches:
133 | ignore: /.*/
134 | requires:
135 | - ensure_formatting
136 | - linter
137 | - deploy:
138 | requires:
139 | - build
140 | filters:
141 | tags:
142 | only: /[0-9]+(\.[0-9]+)+(\.[0-9]+)?\.?(\w)*/
143 | branches:
144 | ignore: /.*/
145 | - notify_rolling:
146 | requires:
147 | - build
148 | - notify:
149 | requires:
150 | - deploy
151 | filters:
152 | tags:
153 | only: /[0-9]+(\.[0-9]+)+(\.[0-9]+)?\.?(\w)*/
154 | branches:
155 | ignore: /.*/
156 |
--------------------------------------------------------------------------------
/.drone.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | name: client-python-tests
4 |
5 | steps:
6 | - name: sleep-for-opencti
7 | image: python:3.11
8 | commands:
9 | - sleep 180
10 | - name: client-test-39
11 | image: python:3.9
12 | commands:
13 | - pip3 install -r requirements.txt --user
14 | - pip3 install -r test-requirements.txt --user
15 | - python3 -m pytest --no-header -vv --disable-warnings --cov=pycti --drone
16 | - name: client-test-310
17 | image: python:3.10
18 | commands:
19 | - pip3 install -r requirements.txt --user
20 | - pip3 install -r test-requirements.txt --user
21 | - python3 -m pytest --no-header -vv --disable-warnings --cov=pycti --drone
22 | - name: client-test-311
23 | image: python:3.11
24 | commands:
25 | - pip3 install -r requirements.txt --user
26 | - pip3 install -r test-requirements.txt --user
27 | - python3 -m pytest --no-header -vv --disable-warnings --cov=pycti --drone
28 | - name: client-test-312
29 | image: python:3.12
30 | commands:
31 | - pip3 install -r requirements.txt --user
32 | - pip3 install -r test-requirements.txt --user
33 | - python3 -m pytest --no-header -vv --disable-warnings --cov=pycti --drone
34 |
35 | # always run the examples last since they don't clean up
36 | - name: example-tests
37 | image: python:3.12
38 | commands:
39 | - pip3 install -r requirements.txt --user
40 | - pip3 install .
41 | - cd examples/
42 | - /bin/bash run_all.sh
43 |
44 | - name: slack
45 | image: plugins/slack
46 | settings:
47 | webhook:
48 | from_secret: slack_webhook
49 | username: drone
50 | icon_url: https://avatars.githubusercontent.com/oa/1284929
51 | channel: notifications
52 | when:
53 | status: [ success, failure ]
54 | event:
55 | exclude:
56 | - pull_request
57 |
58 | services:
59 | - name: redis
60 | image: redis:7.4.3
61 | - name: elastic
62 | image: docker.elastic.co/elasticsearch/elasticsearch:8.18.1
63 | environment:
64 | discovery.type: single-node
65 | xpack.security.enabled: false
66 | ES_JAVA_OPTS: -Xms2g -Xmx2g
67 | - name: minio
68 | image: minio/minio:RELEASE.2022-11-11T03-44-20Z
69 | environment:
70 | MINIO_ROOT_USER: ChangeMe
71 | MINIO_ROOT_PASSWORD: ChangeMe
72 | command: [ server, /data ]
73 | - name: rabbitmq
74 | image: rabbitmq:4.1-management
75 | - name: opencti
76 | image: nikolaik/python-nodejs:python3.10-nodejs18-alpine
77 | environment:
78 | APP__ADMIN__PASSWORD: admin
79 | APP__ADMIN__TOKEN: bfa014e0-e02e-4aa6-a42b-603b19dcf159
80 | REDIS__HOSTNAME: redis
81 | REDIS__NAMESPACE: raw-start
82 | ELASTICSEARCH__URL: http://elastic:9200
83 | ELASTICSEARCH__INDEX_PREFIX: raw-start
84 | MINIO__ENDPOINT: minio
85 | MINIO__BUCKET_NAME: raw-start-bucket
86 | RABBITMQ__HOSTNAME: rabbitmq
87 | EXPIRATION_SCHEDULER__ENABLED: false
88 | SUBSCRIPTION_SCHEDULER__ENABLED: false
89 | GITHUB_TOKEN:
90 | from_secret: github_token
91 | commands:
92 | - apk add build-base git libffi-dev cargo github-cli
93 | - chmod 777 scripts/*
94 | - pip3 install . --upgrade --force
95 | - echo "DRONE_SOURCE_BRANCH:${DRONE_SOURCE_BRANCH}, DRONE_PULL_REQUEST:${DRONE_PULL_REQUEST}"
96 | - ./scripts/clone-opencti.sh "${DRONE_SOURCE_BRANCH}" "${DRONE_TARGET_BRANCH}" "$(pwd)" "${DRONE_PULL_REQUEST}"
97 | - ls -lart
98 | - cd opencti/opencti-platform/opencti-graphql
99 | - yarn install
100 | - sed -i '/^pycti==/d' src/python/requirements.txt
101 | - yarn install:python
102 | - NODE_OPTIONS=--max_old_space_size=8192 yarn start
103 |
104 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore = E203, E266, E501, W503, F403, F401
3 | max-line-length = 89
4 | select = B,C,E,F,W,T4,B9
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve OpenCTI
4 | title: ''
5 | labels: bug, needs triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | Please replace every line in curly brackets { like this } with an appropriate answer, and remove this line.
11 |
12 | ## Description
13 |
14 | { Please provide a clear and concise description of the bug. }
15 |
16 | ## Environment
17 |
18 | 1. OS (where OpenCTI server runs): { e.g. Mac OS 10, Windows 10, Ubuntu 16.4, etc. }
19 | 2. OpenCTI version: { e.g. OpenCTI 1.0.2 }
20 | 3. Other environment details:
21 |
22 | ## Reproducible Steps
23 |
24 | Steps to create the smallest reproducible scenario:
25 | 1. { e.g. Run ... }
26 | 2. { e.g. Click ... }
27 | 3. { e.g. Error ... }
28 |
29 | ## Expected Output
30 |
31 | { Please describe what you expected to happen. }
32 |
33 | ## Actual Output
34 |
35 | { Please describe what actually happened. }
36 |
37 | ## Additional information
38 |
39 | { Any additional information, including logs or screenshots if you have any. }
40 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Ask for a new feature to be implemented in OpenCTI
4 | title: ''
5 | labels: feature, needs triage
6 | assignees: ''
7 |
8 | ---
9 |
10 | Please replace every line in curly brackets { like this } with appropriate answers, and remove this line.
11 |
12 | ## Problem to Solve
13 |
14 | { Please describe the problem you would like to solve. }
15 |
16 | ## Current Workaround
17 |
18 | { Please describe how you currently solve or work around this problem, given OpenCTI's limitation. }
19 |
20 | ## Proposed Solution
21 |
22 | { Please describe the solution you would like OpenCTI to provide, to solve the problem above. }
23 |
24 | ## Additional Information
25 |
26 | { Any additional information, including logs or screenshots if you have any. }
27 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | ### Proposed changes
9 |
10 | *
11 | *
12 |
13 | ### Related issues
14 |
15 | *
16 | *
17 |
18 | ### Checklist
19 |
20 |
25 |
26 | - [ ] I consider the submitted work as finished
27 | - [ ] I tested the code for its functionality
28 | - [ ] I wrote test cases for the relevant uses case
29 | - [ ] I added/update the relevant documentation (either on github or on notion)
30 | - [ ] Where necessary I refactored code to improve the overall quality
31 |
32 |
33 |
34 |
35 | ### Further comments
36 |
37 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc...
38 |
--------------------------------------------------------------------------------
/.github/workflows/auto-set-labels.yml:
--------------------------------------------------------------------------------
1 | name: Assign label Filigran team on PR from an organization
2 | on: pull_request
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v4
8 | - name: Setting labels
9 | uses: FiligranHQ/auto-label@1.0.0
10 | with:
11 | labels_by_organization: "{\"FiligranHQ\":[\"filigran team\"]}"
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # For most projects, this workflow file will not need changing; you simply need
3 | # to commit it to your repository.
4 | #
5 | # You may wish to alter this file to override the set of languages analyzed,
6 | # or to provide custom queries or build logic.
7 | #
8 | # ******** NOTE ********
9 | # We have attempted to detect the languages in your repository. Please check
10 | # the `language` matrix defined below to confirm you have the correct set of
11 | # supported CodeQL languages.
12 | # ******** NOTE ********
13 |
14 | name: "CodeQL"
15 |
16 | on:
17 | push:
18 | branches:
19 | - master
20 | pull_request:
21 | # The branches below must be a subset of the branches above
22 | branches:
23 | - master
24 | schedule:
25 | - cron: "21 11 * * 0"
26 |
27 | jobs:
28 | analyze:
29 | name: Analyze
30 | runs-on: ubuntu-latest
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language:
36 | - python
37 |
38 | steps:
39 | - name: Checkout repository
40 | uses: actions/checkout@v4
41 |
42 | # Initializes the CodeQL tools for scanning.
43 | - name: Initialize CodeQL
44 | uses: github/codeql-action/init@v3
45 | with:
46 | languages: ${{ matrix.language }}
47 | # If you wish to specify custom queries, you can do so here or in a
48 | # config file. By default, queries listed here will override any
49 | # specified in a config file. Prefix the list here with "+" to use
50 | # these queries and those in the config file. queries:
51 | # ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or
54 | # Java). If this step fails, then you should remove it and run the build
55 | # manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v3
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following
63 | # three lines and modify them (or add more) to build your code if your
64 | # project uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v3
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # pycti
2 | config.yml
3 | exports
4 | logs
5 | test.py
6 | examples/*.json
7 | examples/*.pdf
8 | Pipfile*
9 | .DS_Store
10 |
11 | # IntelliJ Platform) | JetBrains
12 | .idea
13 | *.iml
14 |
15 | # VScode
16 | .vscode/*
17 | # Local History for Visual Studio Code
18 | .history/
19 |
20 | # Byte-compiled / optimized / DLL files
21 | __pycache__/
22 | *.py[cod]
23 | *$py.class
24 |
25 | # C extensions
26 | *.so
27 |
28 | # Distribution / packaging
29 | .Python
30 | build/
31 | develop-eggs/
32 | dist/
33 | downloads/
34 | eggs/
35 | .eggs/
36 | lib/
37 | lib64/
38 | parts/
39 | sdist/
40 | var/
41 | wheels/
42 | share/python-wheels/
43 | *.egg-info/
44 | .installed.cfg
45 | *.egg
46 | MANIFEST
47 |
48 | # PyInstaller
49 | # Usually these files are written by a python script from a template
50 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
51 | *.manifest
52 | *.spec
53 |
54 | # Installer logs
55 | pip-log.txt
56 | pip-delete-this-directory.txt
57 |
58 | # Unit test / coverage reports
59 | htmlcov/
60 | .tox/
61 | .nox/
62 | .coverage
63 | .coverage.*
64 | .cache
65 | nosetests.xml
66 | coverage.xml
67 | *.cover
68 | *.py,cover
69 | .hypothesis/
70 | .pytest_cache/
71 | cover/
72 |
73 | # Translations
74 | *.mo
75 | *.pot
76 |
77 |
78 | # Sphinx documentation
79 | docs/_build/
80 |
81 | # PyBuilder
82 | .pybuilder/
83 | target/
84 |
85 | # Jupyter Notebook
86 | .ipynb_checkpoints
87 |
88 | # IPython
89 | profile_default/
90 | ipython_config.py
91 |
92 | # pyenv
93 | # For a library or package, you might want to ignore these files since the code is
94 | # intended to run in multiple environments; otherwise, check them in:
95 | # .python-version
96 |
97 | # pipenv
98 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
99 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
100 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
101 | # install all needed dependencies.
102 | #Pipfile.lock
103 |
104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
105 | __pypackages__/
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | # pytype static type analyzer
132 | .pytype/
133 |
134 | # Cython debug symbols
135 | cython_debug/
136 |
--------------------------------------------------------------------------------
/.grenrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "prefix": "Version ",
3 | "data-source": "milestones",
4 | "milestone-match": "Release {{tag_name}}",
5 | "ignoreIssuesWith": [
6 | "duplicate",
7 | "wontfix",
8 | "invalid",
9 | "help wanted"
10 | ],
11 | "template": {
12 | "issue": "- [{{text}}]({{url}}) {{name}}"
13 | },
14 | "groupBy": {
15 | "Enhancements:": ["feature", "internal", "build", "documentation"],
16 | "Bug Fixes:": ["bug"]
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/.isort.cfg:
--------------------------------------------------------------------------------
1 | [settings]
2 | multi_line_output = 3
3 | include_trailing_comma = True
4 | force_grid_wrap = 0
5 | use_parentheses = True
6 | ensure_newline_before_comments = True
7 | line_length = 88
8 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | repos:
3 | - repo: https://github.com/PyCQA/isort
4 | rev: 5.12.0
5 | hooks:
6 | - id: isort
7 | args:
8 | - "--profile"
9 | - "black"
10 | - repo: https://github.com/psf/black
11 | rev: 22.3.0
12 | hooks:
13 | - id: black
14 | - repo: https://github.com/adrienverge/yamllint
15 | rev: v1.26.3
16 | hooks:
17 | - id: yamllint
18 | - repo: https://github.com/pre-commit/pre-commit-hooks
19 | rev: v4.1.0
20 | hooks:
21 | - id: check-ast
22 | - id: check-json
23 | - id: check-toml
24 | - id: check-xml
25 | - id: check-yaml
26 | - id: end-of-file-fixer
27 | - id: requirements-txt-fixer
28 | - id: trailing-whitespace
29 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # .readthedocs.yml
3 | # Read the Docs configuration file
4 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
5 |
6 | # Required
7 | version: 2
8 |
9 | build:
10 | os: ubuntu-22.04
11 | tools:
12 | python: "3.12"
13 |
14 | # Build documentation in the docs/ directory with Sphinx
15 | sphinx:
16 | configuration: docs/conf.py
17 |
18 | # Build documentation with MkDocs
19 | #mkdocs:
20 | # configuration: mkdocs.yml
21 |
22 | # Optionally build your docs in additional formats such as PDF and ePub
23 | formats: all
24 |
25 | # Optionally set the version of Python and requirements required to build your docs
26 | python:
27 | install:
28 | - requirements: requirements.txt
29 | - requirements: docs/requirements.txt
30 |
--------------------------------------------------------------------------------
/.yamllint.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | extends: default
3 | rules:
4 | indentation:
5 | spaces: 2
6 | indent-sequences: consistent
7 | line-length:
8 | level: warning
9 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | SOURCE_DATE_EPOCH=$(shell git log -1 --pretty=%ct) python3 -m build
3 |
4 | clean:
5 | rm -rf *.egg-info/ build/ dist/
6 |
7 | clean.all: clean
8 | find . -type d -name .mypy_cache -o -name __pycache__ -exec rm -rf "{}" \+
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OpenCTI client for Python
2 |
3 | [](https://opencti.io)
4 | [](https://circleci.com/gh/OpenCTI-Platform/client-python/tree/master)
5 | [](https://opencti-client-for-python.readthedocs.io/en/latest/)
6 | [](https://github.com/OpenCTI-Platform/client-python/releases/latest)
7 | [](https://pypi.python.org/pypi/pycti/)
8 | [](https://community.filigran.io)
9 |
10 | The official OpenCTI Python client helps developers to use the OpenCTI API by providing easy to use methods and utils.
11 | This client is also used by some OpenCTI components.
12 |
13 | ## Install
14 |
15 | To install the latest Python client library, please use `pip`:
16 |
17 | ```bash
18 | $ pip3 install pycti
19 | ```
20 |
21 | ## Local development
22 |
23 | ```bash
24 | # Fork the current repository, then clone your fork
25 | $ git clone https://github.com/YOUR-USERNAME/client-python
26 | $ cd client-python
27 | $ git remote add upstream https://github.com/OpenCTI-Platform/client-python.git
28 | # Create a branch for your feature/fix
29 | $ git checkout -b [branch-name]
30 | # Create a virtualenv
31 | $ python3 -m venv .venv
32 | $ source .venv/bin/activate
33 | # Install the client-python and dependencies for the development and the documentation
34 | $ python3 -m pip install -e .[dev,doc]
35 | # Set up the git hook scripts
36 | $ pre-commit install
37 | # Create your feature/fix
38 | # Create tests for your changes
39 | $ pytest
40 | # Push you feature/fix on Github
41 | $ git add [file(s)]
42 | $ git commit -m "[descriptive message]"
43 | $ git push origin [branch-name]
44 | # Open a pull request
45 | ```
46 |
47 | ### Install the package locally
48 |
49 | ```bash
50 | $ pip install -e .
51 | ```
52 |
53 | ## Documentation
54 |
55 | ### Client usage
56 |
57 | To learn about how to use the OpenCTI Python client and read some examples and cases, refer to [the client documentation](https://opencti-client-for-python.readthedocs.io/en/latest/client_usage/getting_started.html).
58 |
59 | ### API reference
60 |
61 | To learn about the methods available for executing queries and retrieving their answers, refer to [the client API Reference](https://opencti-client-for-python.readthedocs.io/en/latest/pycti/pycti.html).
62 |
63 | ## Tests
64 |
65 | ### Install dependencies
66 |
67 | ```bash
68 | $ pip install -r ./test-requirements.txt
69 | ```
70 |
71 | [pytest](https://docs.pytest.org/en/7.2.x/) is used to launch the tests.
72 |
73 | ### Launch tests
74 |
75 | #### Prerequisite
76 |
77 | Your OpenCTI API should be running.
78 | Your conftest.py should be configured with your API url, your token, and if applicable, your mTLS cert/key.
79 |
80 | #### Launching
81 |
82 | Unit tests
83 | ```bash
84 | $ pytest ./tests/01-unit/
85 | ```
86 |
87 | Integration testing
88 | ```bash
89 | $ pytest ./tests/02-integration/
90 | ```
91 |
92 | ## About
93 |
94 | OpenCTI is a product designed and developed by the company [Filigran](https://filigran.io).
95 |
96 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/client_usage/getting_started.rst:
--------------------------------------------------------------------------------
1 | Getting Started
2 | ===============
3 |
4 | Installation
5 | ************
6 |
7 | Please install the latest pycti version available from PyPI::
8 |
9 | $ pip3 install pycti
10 |
11 | Using the helper functions
12 | **************************
13 |
14 | The main class :class:`OpenCTIApiClient` contains all what you need to interact
15 | with the platform, you just have to initialize it. If your OpenCTI instance requires mTLS,
16 | you can specify either the file path of the SSL .pem file, or provide a key-value pair representing
17 | the file paths to your cert and private key in a tuple with the `cert` parameter when
18 | initializing the `OpenCTIApiClient`. If you need to require the verification of the TLS certificate at the server,
19 | you can provide a boolean value for the `ssl_verify` parameter.
20 |
21 | The following example shows how you create an indicator in OpenCTI using the python library
22 | with TLP marking and OpenCTI compatible date format.
23 |
24 | .. code-block:: python
25 |
26 | from dateutil.parser import parse
27 | from pycti import OpenCTIApiClient
28 | from stix2 import TLP_GREEN
29 |
30 | # OpenCTI API client initialization
31 | opencti_api_client = OpenCTIApiClient("https://myopencti.server", "mysupersecrettoken")
32 |
33 | # Define an OpenCTI compatible date
34 | date = parse("2019-12-01").strftime("%Y-%m-%dT%H:%M:%SZ")
35 |
36 | # Get the OpenCTI marking for stix2 TLP_GREEN
37 | TLP_GREEN_CTI = opencti_api_client.marking_definition.read(id=TLP_GREEN["id"])
38 |
39 | # Use the client to create an indicator in OpenCTI
40 | indicator = opencti_api_client.indicator.create(
41 | name="C2 server of the new campaign",
42 | description="This is the C2 server of the campaign",
43 | pattern_type="stix",
44 | pattern="[domain-name:value = 'www.5z8.info']",
45 | x_opencti_main_observable_type="IPv4-Addr",
46 | valid_from=date,
47 | update=True,
48 | markingDefinitions=[TLP_GREEN_CTI["id"]],
49 | )
50 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | import os
14 | import sys
15 |
16 | sys.path.insert(0, os.path.abspath(".."))
17 |
18 |
19 | # -- Project information -----------------------------------------------------
20 |
21 | project = "OpenCTI client for Python"
22 | copyright = "2025, Filigran"
23 | author = "OpenCTI Project"
24 |
25 | # The full version, including alpha/beta/rc tags
26 | release = "6.0.7"
27 |
28 | master_doc = "index"
29 |
30 | autoapi_modules = {"pycti": {"prune": True}}
31 |
32 | pygments_style = "sphinx"
33 |
34 | # -- General configuration ---------------------------------------------------
35 |
36 | # Add any Sphinx extension module names here, as strings. They can be
37 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
38 | # ones.
39 | extensions = [
40 | "sphinx.ext.autodoc",
41 | "sphinx.ext.inheritance_diagram",
42 | "autoapi.sphinx",
43 | "sphinx_autodoc_typehints",
44 | ]
45 |
46 | # Add any paths that contain templates here, relative to this directory.
47 | templates_path = ["_templates"]
48 |
49 | # List of patterns, relative to source directory, that match files and
50 | # directories to ignore when looking for source files.
51 | # This pattern also affects html_static_path and html_extra_path.
52 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
53 |
54 |
55 | # -- Options for HTML output -------------------------------------------------
56 |
57 | # The theme to use for HTML and HTML Help pages. See the documentation for
58 | # a list of builtin themes.
59 | #
60 | html_theme = "sphinx_rtd_theme"
61 |
62 | # Add any paths that contain custom static files (such as style sheets) here,
63 | # relative to this directory. They are copied after the builtin static files,
64 | # so a file named "default.css" will overwrite the builtin "default.css".
65 | html_static_path = ["_static"]
66 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | OpenCTI client for Python
2 | =========================
3 |
4 | The pycti library is designed to help OpenCTI users and developers to interact
5 | with the OpenCTI platform GraphQL API.
6 |
7 | The Python library requires Python >= 3.
8 |
9 | .. toctree::
10 | :maxdepth: 2
11 | :caption: Contents:
12 |
13 | client_usage/getting_started.rst
14 | pycti/pycti
15 |
16 |
17 | Indices and tables
18 | ==================
19 |
20 | * :ref:`genindex`
21 | * :ref:`modindex`
22 | * :ref:`search`
23 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.api.opencti_api_client.rst:
--------------------------------------------------------------------------------
1 | ================================
2 | ``pycti.api.opencti_api_client``
3 | ================================
4 |
5 | .. automodule:: pycti.api.opencti_api_client
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.api.opencti_api_client
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.api.opencti_api_connector.rst:
--------------------------------------------------------------------------------
1 | ===================================
2 | ``pycti.api.opencti_api_connector``
3 | ===================================
4 |
5 | .. automodule:: pycti.api.opencti_api_connector
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.api.opencti_api_connector
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.api.opencti_api_job.rst:
--------------------------------------------------------------------------------
1 | =============================
2 | ``pycti.api.opencti_api_job``
3 | =============================
4 |
5 | .. automodule:: pycti.api.opencti_api_job
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.api.opencti_api_job
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.api.rst:
--------------------------------------------------------------------------------
1 | =============
2 | ``pycti.api``
3 | =============
4 |
5 | .. automodule:: pycti.api
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 |
12 | Submodules
13 | ==========
14 |
15 | .. toctree::
16 |
17 | pycti.api.opencti_api_client
18 | pycti.api.opencti_api_connector
19 | pycti.api.opencti_api_job
20 |
21 | .. currentmodule:: pycti.api
22 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.connector.opencti_connector.rst:
--------------------------------------------------------------------------------
1 | =====================================
2 | ``pycti.connector.opencti_connector``
3 | =====================================
4 |
5 | .. automodule:: pycti.connector.opencti_connector
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.connector.opencti_connector
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.connector.opencti_connector_helper.rst:
--------------------------------------------------------------------------------
1 | ============================================
2 | ``pycti.connector.opencti_connector_helper``
3 | ============================================
4 |
5 | .. automodule:: pycti.connector.opencti_connector_helper
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.connector.opencti_connector_helper
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.connector.rst:
--------------------------------------------------------------------------------
1 | ===================
2 | ``pycti.connector``
3 | ===================
4 |
5 | .. automodule:: pycti.connector
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 |
12 | Submodules
13 | ==========
14 |
15 | .. toctree::
16 |
17 | pycti.connector.opencti_connector
18 | pycti.connector.opencti_connector_helper
19 |
20 | .. currentmodule:: pycti.connector
21 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_attack_pattern.rst:
--------------------------------------------------------------------------------
1 | =========================================
2 | ``pycti.entities.opencti_attack_pattern``
3 | =========================================
4 |
5 | .. automodule:: pycti.entities.opencti_attack_pattern
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_attack_pattern
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_campaign.rst:
--------------------------------------------------------------------------------
1 | ===================================
2 | ``pycti.entities.opencti_campaign``
3 | ===================================
4 |
5 | .. automodule:: pycti.entities.opencti_campaign
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_campaign
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_capability.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | ``pycti.entities.opencti_capability``
3 | ====================================
4 |
5 | .. automodule:: pycti.entities.opencti_capability
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_capability
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_course_of_action.rst:
--------------------------------------------------------------------------------
1 | ===========================================
2 | ``pycti.entities.opencti_course_of_action``
3 | ===========================================
4 |
5 | .. automodule:: pycti.entities.opencti_course_of_action
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_course_of_action
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_external_reference.rst:
--------------------------------------------------------------------------------
1 | =============================================
2 | ``pycti.entities.opencti_external_reference``
3 | =============================================
4 |
5 | .. automodule:: pycti.entities.opencti_external_reference
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_external_reference
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_group.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | ``pycti.entities.opencti_group``
3 | ====================================
4 |
5 | .. automodule:: pycti.entities.opencti_group
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_group
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_identity.rst:
--------------------------------------------------------------------------------
1 | ===================================
2 | ``pycti.entities.opencti_identity``
3 | ===================================
4 |
5 | .. automodule:: pycti.entities.opencti_identity
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_identity
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_incident.rst:
--------------------------------------------------------------------------------
1 | ===================================
2 | ``pycti.entities.opencti_incident``
3 | ===================================
4 |
5 | .. automodule:: pycti.entities.opencti_incident
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_incident
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_indicator.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | ``pycti.entities.opencti_indicator``
3 | ====================================
4 |
5 | .. automodule:: pycti.entities.opencti_indicator
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_indicator
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_intrusion_set.rst:
--------------------------------------------------------------------------------
1 | ========================================
2 | ``pycti.entities.opencti_intrusion_set``
3 | ========================================
4 |
5 | .. automodule:: pycti.entities.opencti_intrusion_set
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_intrusion_set
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_kill_chain_phase.rst:
--------------------------------------------------------------------------------
1 | ===========================================
2 | ``pycti.entities.opencti_kill_chain_phase``
3 | ===========================================
4 |
5 | .. automodule:: pycti.entities.opencti_kill_chain_phase
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_kill_chain_phase
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_malware.rst:
--------------------------------------------------------------------------------
1 | ==================================
2 | ``pycti.entities.opencti_malware``
3 | ==================================
4 |
5 | .. automodule:: pycti.entities.opencti_malware
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_malware
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_marking_definition.rst:
--------------------------------------------------------------------------------
1 | =============================================
2 | ``pycti.entities.opencti_marking_definition``
3 | =============================================
4 |
5 | .. automodule:: pycti.entities.opencti_marking_definition
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_marking_definition
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_note.rst:
--------------------------------------------------------------------------------
1 | ===============================
2 | ``pycti.entities.opencti_note``
3 | ===============================
4 |
5 | .. automodule:: pycti.entities.opencti_note
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_note
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_opinion.rst:
--------------------------------------------------------------------------------
1 | ==================================
2 | ``pycti.entities.opencti_opinion``
3 | ==================================
4 |
5 | .. automodule:: pycti.entities.opencti_opinion
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_opinion
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_report.rst:
--------------------------------------------------------------------------------
1 | =================================
2 | ``pycti.entities.opencti_report``
3 | =================================
4 |
5 | .. automodule:: pycti.entities.opencti_report
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_report
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_role.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | ``pycti.entities.opencti_role``
3 | ====================================
4 |
5 | .. automodule:: pycti.entities.opencti_role
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_role
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_settings.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | ``pycti.entities.opencti_settings``
3 | ====================================
4 |
5 | .. automodule:: pycti.entities.opencti_settings
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_settings
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_stix_domain_entity.rst:
--------------------------------------------------------------------------------
1 | =============================================
2 | ``pycti.entities.opencti_stix_domain_object``
3 | =============================================
4 |
5 | .. automodule:: pycti.entities.opencti_stix_domain_object
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_stix_domain_object
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_stix_entity.rst:
--------------------------------------------------------------------------------
1 | ======================================
2 | ``pycti.entities.opencti_stix_entity``
3 | ======================================
4 |
5 | .. automodule:: pycti.entities.opencti_stix_entity
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_stix_entity
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_stix_observable.rst:
--------------------------------------------------------------------------------
1 | ==========================================
2 | ``pycti.entities.opencti_stix_observable``
3 | ==========================================
4 |
5 | .. automodule:: pycti.entities.opencti_stix_observable
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_stix_observable
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_stix_observable_relation.rst:
--------------------------------------------------------------------------------
1 | ===================================================
2 | ``pycti.entities.opencti_stix_observable_relationship``
3 | ===================================================
4 |
5 | .. automodule:: pycti.entities.opencti_stix_observable_relationship
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_stix_observable_relationship
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_stix_relation.rst:
--------------------------------------------------------------------------------
1 | ========================================
2 | ``pycti.entities.opencti_stix_relation``
3 | ========================================
4 |
5 | .. automodule:: pycti.entities.opencti_stix_relation
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_stix_relation
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_stix_sighting.rst:
--------------------------------------------------------------------------------
1 | ========================================
2 | ``pycti.entities.opencti_stix_sighting``
3 | ========================================
4 |
5 | .. automodule:: pycti.entities.opencti_stix_sighting
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_stix_sighting
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_tag.rst:
--------------------------------------------------------------------------------
1 | ==============================
2 | ``pycti.entities.opencti_tag``
3 | ==============================
4 |
5 | .. automodule:: pycti.entities.opencti_tag
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_tag
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_threat_actor.rst:
--------------------------------------------------------------------------------
1 | =======================================
2 | ``pycti.entities.opencti_threat_actor``
3 | =======================================
4 |
5 | .. automodule:: pycti.entities.opencti_threat_actor
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_threat_actor
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_tool.rst:
--------------------------------------------------------------------------------
1 | ===============================
2 | ``pycti.entities.opencti_tool``
3 | ===============================
4 |
5 | .. automodule:: pycti.entities.opencti_tool
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_tool
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_user.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | ``pycti.entities.opencti_user``
3 | ====================================
4 |
5 | .. automodule:: pycti.entities.opencti_user
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_user
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.opencti_vulnerability.rst:
--------------------------------------------------------------------------------
1 | ========================================
2 | ``pycti.entities.opencti_vulnerability``
3 | ========================================
4 |
5 | .. automodule:: pycti.entities.opencti_vulnerability
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.entities.opencti_vulnerability
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.entities.rst:
--------------------------------------------------------------------------------
1 | ==================
2 | ``pycti.entities``
3 | ==================
4 |
5 | .. automodule:: pycti.entities
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 |
12 | Submodules
13 | ==========
14 |
15 | .. toctree::
16 |
17 | pycti.entities.opencti_attack_pattern
18 | pycti.entities.opencti_campaign
19 | pycti.entities.opencti_course_of_action
20 | pycti.entities.opencti_external_reference
21 | pycti.entities.opencti_identity
22 | pycti.entities.opencti_incident
23 | pycti.entities.opencti_indicator
24 | pycti.entities.opencti_intrusion_set
25 | pycti.entities.opencti_kill_chain_phase
26 | pycti.entities.opencti_malware
27 | pycti.entities.opencti_marking_definition
28 | pycti.entities.opencti_note
29 | pycti.entities.opencti_opinion
30 | pycti.entities.opencti_report
31 | pycti.entities.opencti_stix_domain_object
32 | pycti.entities.opencti_stix_entity
33 | pycti.entities.opencti_stix_observable
34 | pycti.entities.opencti_stix_observable_relationship
35 | pycti.entities.opencti_stix_relation
36 | pycti.entities.opencti_stix_sighting
37 | pycti.entities.opencti_tag
38 | pycti.entities.opencti_threat_actor
39 | pycti.entities.opencti_tool
40 | pycti.entities.opencti_vulnerability
41 | pycti.entities.opencti_capability
42 | pycti.entities.opencti_role
43 | pycti.entities.opencti_group
44 | pycti.entities.opencti_user
45 | pycti.entities.opencti_settings
46 |
47 | .. currentmodule:: pycti.entities
48 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.utils.constants.rst:
--------------------------------------------------------------------------------
1 | =========================
2 | ``pycti.utils.constants``
3 | =========================
4 |
5 | .. automodule:: pycti.utils.constants
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.utils.constants
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.utils.opencti_stix2.rst:
--------------------------------------------------------------------------------
1 | =============================
2 | ``pycti.utils.opencti_stix2``
3 | =============================
4 |
5 | .. automodule:: pycti.utils.opencti_stix2
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 | .. currentmodule:: pycti.utils.opencti_stix2
12 |
--------------------------------------------------------------------------------
/docs/pycti/pycti.utils.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | ``pycti.utils``
3 | ===============
4 |
5 | .. automodule:: pycti.utils
6 | :members:
7 |
8 | .. contents::
9 | :local:
10 |
11 |
12 | Submodules
13 | ==========
14 |
15 | .. toctree::
16 |
17 | pycti.utils.constants
18 | pycti.utils.opencti_stix2
19 |
20 | .. currentmodule:: pycti.utils
21 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | autoapi==2.0.1
2 | sphinx==8.2.3
3 | sphinx-autodoc-typehints==3.2.0
4 | sphinx_rtd_theme==3.0.2
5 |
--------------------------------------------------------------------------------
/examples/add_external_reference_to_report.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from dateutil.parser import parse
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Define the date
15 | date = parse("2019-12-01").strftime("%Y-%m-%dT%H:%M:%SZ")
16 |
17 | # Create the report
18 | report = opencti_api_client.report.create(
19 | name="My test report",
20 | description="A new threat report.",
21 | published=date,
22 | report_class="Threat Report",
23 | )
24 |
25 | # Create the external reference
26 | external_reference = opencti_api_client.external_reference.create(
27 | source_name="Wikipedia", url="https://en.wikipedia.org/wiki/Fancy_Bear"
28 | )
29 |
30 | # Add the external reference to the report
31 | opencti_api_client.stix_domain_object.add_external_reference(
32 | id=report["id"], external_reference_id=external_reference["id"]
33 | )
34 |
35 | # Get the report
36 | report = opencti_api_client.report.read(id=report["id"])
37 |
38 | # Print
39 | print(report)
40 |
--------------------------------------------------------------------------------
/examples/add_label_to_malware.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Create the malware
13 | malware = opencti_api_client.malware.create(
14 | name="My new malware", description="A new evil tool."
15 | )
16 |
17 | # Create the tag (if not exists)
18 | label = opencti_api_client.label.create(
19 | value="Ransomware",
20 | color="#ffa500",
21 | )
22 |
23 | # Add the tag
24 | opencti_api_client.stix_domain_object.add_label(id=malware["id"], label_id=label["id"])
25 |
26 | # Print
27 | malware = opencti_api_client.malware.read(id=malware["id"])
28 | print(malware)
29 |
--------------------------------------------------------------------------------
/examples/add_label_to_observable.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Create the observable
13 | url = opencti_api_client.stix_cyber_observable.create(
14 | observableData={"type": "url", "value": "http://johndoe.com"}
15 | )
16 | # Create the tag (if not exists)
17 | label = opencti_api_client.label.create(
18 | value="Suspicious",
19 | color="#ffa500",
20 | )
21 |
22 | # Add the tag
23 | opencti_api_client.stix_cyber_observable.add_label(id=url["id"], label_id=label["id"])
24 |
25 | # Read the observable
26 | obs = opencti_api_client.stix_cyber_observable.read(id=url["id"])
27 | print(obs)
28 |
--------------------------------------------------------------------------------
/examples/add_organization_to_sector.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Get the sector
13 | sector = opencti_api_client.identity.create(
14 | type="Sector", name="Banking institutions", description="Banks"
15 | )
16 |
17 | # Create the organization
18 | organization = opencti_api_client.identity.create(
19 | type="Organization", name="BNP Paribas", description="A french bank."
20 | )
21 |
22 | # Create the relation
23 | relation = opencti_api_client.stix_core_relationship.create(
24 | fromType="Organization",
25 | fromId=organization["id"],
26 | toType="Sector",
27 | toId=sector["id"],
28 | relationship_type="part-of",
29 | description="BNP Paribas is part of the sector Banking institutions.",
30 | )
31 |
32 | # Print
33 | print(relation)
34 |
--------------------------------------------------------------------------------
/examples/add_tool_usage_to_intrusion-set.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | tool = opencti_api_client.tool.create(
13 | name="powashell.exe", description="A new evil tool."
14 | )
15 |
16 | print(tool)
17 |
18 | intrusion_set = opencti_api_client.intrusion_set.create(name="APT_EVIL")
19 |
20 | print(intrusion_set)
21 |
22 | # Create the relation
23 | relation = opencti_api_client.stix_core_relationship.create(
24 | fromType="IntrusionSet",
25 | fromId=intrusion_set["id"],
26 | toType="Tool",
27 | toId=tool["id"],
28 | relationship_type="uses",
29 | description="APT_EVIL uses the tool powashell.exe",
30 | )
31 |
32 | print(relation)
33 |
--------------------------------------------------------------------------------
/examples/ask_enrichment_of_observable.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from pycti import OpenCTIApiClient
3 |
4 | # Variables
5 | api_url = "http://opencti:4000"
6 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
7 | # Define name of INTERNAL_ENRICHMENT Connector which can enrich IPv4 addresses
8 | connector_name = "AbuseIPDB"
9 |
10 | # OpenCTI initialization
11 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
12 |
13 | # Create the observable
14 | observable = opencti_api_client.stix_cyber_observable.create(
15 | **{
16 | "simple_observable_key": "IPv4-Addr.value",
17 | "simple_observable_value": "8.8.4.4",
18 | }
19 | )
20 |
21 | # Get connector id for defined connector name
22 | connector_list = opencti_api_client.connector.list()
23 | connector_names = []
24 | connector_id = ""
25 | for connector in connector_list:
26 | connector_names.append(connector["name"])
27 | if connector["name"] == connector_name:
28 | connector_id = connector["id"]
29 |
30 | if connector_id == "":
31 | print(f"Connector with name '{connector_name}' could not be found")
32 | print(f"Running connectors: {connector_names}")
33 | exit(0)
34 |
35 | print("Asking for enrichment... (this might take a bit to finish)")
36 | # Ask for enrichment
37 | work_id = opencti_api_client.stix_cyber_observable.ask_for_enrichment(
38 | id=observable["id"], connector_id=connector_id
39 | )
40 | # Wait for connector to finish
41 | opencti_api_client.work.wait_for_work_to_finish(work_id)
42 |
43 | # Read the observable
44 | obs = opencti_api_client.stix_cyber_observable.read(id=observable["id"])
45 | print(obs)
46 |
--------------------------------------------------------------------------------
/examples/cmd_line_tag_latest_indicators_of_threat.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import argparse
3 |
4 | from dateutil.parser import parse
5 |
6 | from pycti import OpenCTIApiClient
7 |
8 | # Variables
9 | api_url = "http://opencti:4000"
10 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
11 |
12 | # OpenCTI initialization
13 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
14 |
15 |
16 | def main():
17 | # Parameters
18 | parser = argparse.ArgumentParser(description="Mandatory arguments.")
19 | parser.add_argument(
20 | "--entity-type",
21 | dest="entity_type",
22 | default="Intrusion-Set",
23 | required=True,
24 | help="Type of the threat (Threat-Actor, Intrusion-Set, Campaign, X-OpenCTI,-Incident, Malware, Tool, Attack-Pattern)",
25 | )
26 | parser.add_argument(
27 | "--name",
28 | dest="name",
29 | required=True,
30 | help="Name of the threat",
31 | )
32 | parser.add_argument(
33 | "--created-after",
34 | dest="createdAfter",
35 | help="Indicator created before (ISO date)",
36 | )
37 | parser.add_argument(
38 | "--created-before",
39 | dest="createdBefore",
40 | help="Indicator created after (ISO date)",
41 | )
42 | parser.add_argument(
43 | "--tags",
44 | dest="tags",
45 | required=True,
46 | help="Tags to add or remove (separated by ,)",
47 | )
48 | parser.add_argument(
49 | "--operation",
50 | dest="operation",
51 | required=True,
52 | default="add",
53 | help="Operation (add/remove)",
54 | )
55 | args = parser.parse_args()
56 |
57 | entity_type = args.entity_type
58 | name = args.name
59 | created_after = parse(args.createdAfter).strftime("%Y-%m-%dT%H:%M:%SZ")
60 | created_before = parse(args.createdBefore).strftime("%Y-%m-%dT%H:%M:%SZ")
61 | tags = args.tags.split(",")
62 | operation = args.operation
63 |
64 | # Resolve the entity
65 | threat = opencti_api_client.stix_domain_object.read(
66 | types=[entity_type],
67 | filters={
68 | "mode": "and",
69 | "filters": [{"key": "name", "values": [name]}],
70 | "filterGroups": [],
71 | },
72 | )
73 |
74 | if not threat:
75 | raise ValueError("Cannot find the entity with the name " + name)
76 |
77 | # Resolve all tags
78 | labels = []
79 | for tag in tags:
80 | labels.append(opencti_api_client.label.create(value=tag))
81 |
82 | # Get indicators
83 | custom_attributes = """
84 | id
85 | created_at
86 | """
87 |
88 | data = {"pagination": {"hasNextPage": True, "endCursor": None}}
89 | while data["pagination"]["hasNextPage"]:
90 | after = data["pagination"]["endCursor"]
91 | data = opencti_api_client.indicator.list(
92 | first=50,
93 | after=after,
94 | customAttributes=custom_attributes,
95 | filters={
96 | "mode": "and",
97 | "filters": [
98 | {"key": "indicates", "values": [threat["id"]]},
99 | {"key": "created_at", "values": [created_after], "operator": "gt"},
100 | {"key": "created_at", "values": [created_before], "operator": "lt"},
101 | ],
102 | "filterGroups": [],
103 | },
104 | orderBy="created_at",
105 | orderMode="asc",
106 | withPagination=True,
107 | )
108 | for indicator in data["entities"]:
109 | print("[" + indicator["created_at"] + "] " + indicator["id"])
110 | if operation == "add":
111 | for label in labels:
112 | opencti_api_client.stix_domain_object.add_label(
113 | id=indicator["id"], label_id=label["id"]
114 | )
115 | elif operation == "remove":
116 | for label in labels:
117 | opencti_api_client.stix_domain_object.remove_label(
118 | id=indicator["id"], label_id=label["id"]
119 | )
120 |
121 |
122 | if __name__ == "__main__":
123 | main()
124 |
--------------------------------------------------------------------------------
/examples/create_campaign_attributed-to_intrusion_set.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from dateutil.parser import parse
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Define the date
15 | date = parse("2019-12-01").strftime("%Y-%m-%dT%H:%M:%SZ")
16 |
17 | # Create the Intrusion Set
18 | intrusion_set = opencti_api_client.intrusion_set.create(
19 | name="My new Intrusion Set",
20 | description="Evil Cluster",
21 | first_seen=date,
22 | last_seen=date,
23 | update=True,
24 | )
25 | print(intrusion_set)
26 |
27 | # Create the Campaign
28 | campaign = opencti_api_client.campaign.create(
29 | name="My new Campaign",
30 | description="Large SpearPhishing and intrusions followed by ransomware",
31 | objective="Financial gain",
32 | first_seen=date,
33 | last_seen=date,
34 | update=True,
35 | )
36 | print(campaign)
37 |
38 | # Attribute the Campaign to the Intrusion Set
39 | relation = opencti_api_client.stix_core_relationship.create(
40 | fromType="Campaign",
41 | fromId=campaign["id"],
42 | toType="Intrusion-Set",
43 | toId=intrusion_set["id"],
44 | relationship_type="attributed-to",
45 | first_seen=date,
46 | last_seen=date,
47 | description="My new campaign is attributed to my new Intrusion Set, the evil cluster.",
48 | )
49 |
50 | # Print
51 | print(relation)
52 |
--------------------------------------------------------------------------------
/examples/create_file_with_hashes.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Create observable
13 | observable = opencti_api_client.stix_cyber_observable.create(
14 | observableData={
15 | "type": "file",
16 | "hashes": {
17 | "md5": "fcd76de79819813b631d949b18b1e996",
18 | "sha-1": "e08b42d92fa579c095834909b893d49259b158be",
19 | "sha-256": "7cca822e0fdfeca033762213bf16a3f04d7cac8c345f84a0d740324d97f671c0",
20 | },
21 | }
22 | )
23 |
24 | print(observable)
25 |
--------------------------------------------------------------------------------
/examples/create_indicator_of_campaign.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from dateutil.parser import parse
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Define the date
15 | date = parse("2019-12-01").strftime("%Y-%m-%dT%H:%M:%SZ")
16 |
17 | # Create the Campaign
18 | campaign = opencti_api_client.campaign.create(
19 | name="My new Campaign",
20 | description="Large SpearPhishing and intrusions followed by ransomware",
21 | objective="Financial gain",
22 | first_seen=date,
23 | last_seen=date,
24 | update=True,
25 | )
26 | print(campaign)
27 |
28 | # Create the indicator
29 | indicator = opencti_api_client.indicator.create(
30 | name="C2 server of the new campaign",
31 | description="This is the C2 server of the campaign",
32 | pattern_type="stix",
33 | pattern="[domain-name:value = 'www.5z8.info' AND domain-name:resolves_to_refs[*].value = '198.51.100.1/32']",
34 | x_opencti_main_observable_type="IPv4-Addr",
35 | valid_from=date,
36 | )
37 | print(indicator)
38 |
39 | # Create the relation
40 | relation = opencti_api_client.stix_core_relationship.create(
41 | fromType="Indicator",
42 | fromId=indicator["id"],
43 | toType="Campaign",
44 | toId=campaign["id"],
45 | relationship_type="indicates",
46 | first_seen=date,
47 | last_seen=date,
48 | description="This is the C2 server of the campaign.",
49 | )
50 | print(relation)
51 |
52 | # Create the observables (optional)
53 | observable_1 = opencti_api_client.stix_cyber_observable.create(
54 | simple_observable_key="Domain-Name.value", simple_observable_value="www.5z8.info"
55 | )
56 | observable_2 = opencti_api_client.stix_cyber_observable.create(
57 | simple_observable_key="IPv4-Addr.value", simple_observable_value="198.51.100.1"
58 | )
59 | # Create the relation between observables and the indicator
60 | opencti_api_client.indicator.add_stix_cyber_observable(
61 | id=indicator["id"], stix_cyber_observable_id=observable_1["id"]
62 | )
63 | opencti_api_client.indicator.add_stix_cyber_observable(
64 | id=indicator["id"], stix_cyber_observable_id=observable_2["id"]
65 | )
66 |
--------------------------------------------------------------------------------
/examples/create_intrusion_set.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import datetime
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Create the Intrusion Set
15 | intrusion_set = opencti_api_client.intrusion_set.create(
16 | name="My new Intrusion Set",
17 | description="Evil Cluster",
18 | first_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
19 | last_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
20 | update=True,
21 | )
22 |
23 | # Print
24 | print(intrusion_set)
25 |
--------------------------------------------------------------------------------
/examples/create_ip_domain_resolution.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | observable_domain = opencti_api_client.stix_cyber_observable.create(
13 | observableData={
14 | "type": "domain-name",
15 | "value": "dns.google",
16 | }
17 | )
18 |
19 | observable_ip = opencti_api_client.stix_cyber_observable.create(
20 | observableData={
21 | "type": "ipv4-addr",
22 | "value": "8.8.8.8",
23 | }
24 | )
25 |
26 | opencti_api_client.stix_nested_ref_relationship.create(
27 | fromId=observable_domain["id"],
28 | toId=observable_ip["id"],
29 | relationship_type="resolves-to",
30 | )
31 |
32 | relationships = opencti_api_client.stix_nested_ref_relationship.list(
33 | elementId=observable_domain["id"]
34 | )
35 |
36 | print(relationships)
37 |
--------------------------------------------------------------------------------
/examples/create_marking_definition.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Create the marking definition
13 | marking_definition = opencti_api_client.marking_definition.create(
14 | definition_type="TLP",
15 | definition="TLP:BLACK",
16 | x_opencti_order=10,
17 | x_opencti_color="#000000",
18 | )
19 |
20 | # Print
21 | print(marking_definition)
22 |
--------------------------------------------------------------------------------
/examples/create_observable_relationships.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | observable = opencti_api_client.stix_cyber_observable.create(
13 | observableData={
14 | "type": "file",
15 | "hashes": {
16 | "md5": "16b3f663d0f0371a4706642c6ac04e42",
17 | "sha-1": "3a1f908941311fc357051b5c35fd2a4e0c834e37",
18 | "sha-256": "bcc70a49fab005b4cdbe0cbd87863ec622c6b2c656987d201adbb0e05ec03e56",
19 | },
20 | }
21 | )
22 |
23 | process = opencti_api_client.stix_cyber_observable.create(
24 | observableData={
25 | "type": "Process",
26 | "x_opencti_description": "A process",
27 | "cwd": "C:\Process.exe",
28 | "pid": 19000,
29 | "command_line": "--run exe",
30 | "x_opencti_score": 90,
31 | }
32 | )
33 |
34 | author = opencti_api_client.identity.create(
35 | name="John's Work",
36 | description="Automated Toolkit",
37 | type="Organization",
38 | )
39 |
40 | opencti_api_client.stix_core_relationship.create(
41 | toId=observable["id"],
42 | fromId=process["id"],
43 | confidence=90,
44 | createdBy=author["id"],
45 | relationship_type="related-to",
46 | description="Relation between the File and Process objects",
47 | )
48 |
--------------------------------------------------------------------------------
/examples/create_process_observable.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | process = opencti_api_client.stix_cyber_observable.create(
13 | observableData={
14 | "type": "Process",
15 | "x_opencti_description": "A process",
16 | "cwd": "C:\Process.exe",
17 | "pid": 19000,
18 | "command_line": "--run exe",
19 | "x_opencti_score": 90,
20 | }
21 | )
22 |
23 | print(process)
24 |
--------------------------------------------------------------------------------
/examples/create_report_with_author.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from dateutil.parser import parse
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Define the date
15 | date = parse("2019-12-01").strftime("%Y-%m-%dT%H:%M:%SZ")
16 |
17 | # Create the author (if not exists)
18 | organization = opencti_api_client.identity.create(
19 | type="Organization",
20 | name="My organization",
21 | alias=["my-organization"],
22 | description="A new organization.",
23 | )
24 |
25 | # Create the report
26 | report = opencti_api_client.report.create(
27 | name="My new report of my organization",
28 | description="A report wrote by my organization",
29 | published=date,
30 | report_types=["internal-report"],
31 | createdBy=organization["id"],
32 | )
33 |
34 | # Print
35 | print(report)
36 |
--------------------------------------------------------------------------------
/examples/delete_intrusion_set.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import datetime
3 |
4 | from pycti import OpenCTIApiClient
5 |
6 | # Variables
7 | api_url = "http://opencti:4000"
8 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
9 |
10 | # OpenCTI initialization
11 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
12 |
13 | opencti_api_client.intrusion_set.create(
14 | name="EvilSET123",
15 | description="Evil Cluster",
16 | first_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
17 | last_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
18 | update=True,
19 | )
20 |
21 | # Get the intrusion set APT28
22 | intrusion_set = opencti_api_client.intrusion_set.read(
23 | filters={
24 | "mode": "and",
25 | "filters": [{"key": "name", "values": ["EvilSET123"]}],
26 | "filterGroups": [],
27 | }
28 | )
29 |
30 | # Delete the intrusion set
31 | opencti_api_client.stix_domain_object.delete(id=intrusion_set["id"])
32 |
--------------------------------------------------------------------------------
/examples/delete_relation.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | intrusion_set = opencti_api_client.intrusion_set.create(name="EvilSET123")
13 |
14 | malware = opencti_api_client.malware.create(
15 | name="TheWorm", description="A new evil worm."
16 | )
17 |
18 | opencti_api_client.stix_core_relationship.create(
19 | fromId=intrusion_set["id"],
20 | fromTypes=["Intrusion-Set"],
21 | toId=malware["id"],
22 | toTypes=["Malware"],
23 | relationship_type="uses",
24 | )
25 |
26 | # Get the relations between APT28 and DealersChoice
27 | relations = opencti_api_client.stix_core_relationship.list(
28 | fromId=intrusion_set["id"],
29 | fromTypes=["Intrusion-Set"],
30 | toId=malware["id"],
31 | toTypes=["Malware"],
32 | relationship_type="uses",
33 | )
34 |
35 | # Delete the relations
36 | for relation in relations:
37 | opencti_api_client.stix_core_relationship.delete(id=relation["id"])
38 |
--------------------------------------------------------------------------------
/examples/export_async_of_indicators.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
--------------------------------------------------------------------------------
/examples/export_async_of_malware.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
--------------------------------------------------------------------------------
/examples/export_incident_stix2.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import json
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | opencti_api_client.incident.create(name="My new incident")
15 |
16 | # Get the incident created in the create_incident_with_ttps_and_indicators.py
17 | incident = opencti_api_client.incident.read(
18 | filters={
19 | "mode": "and",
20 | "filters": [{"key": "name", "values": ["My new incident"]}],
21 | "filterGroups": [],
22 | }
23 | )
24 |
25 | # Create the bundle
26 | bundle = opencti_api_client.stix2.get_stix_bundle_or_object_from_entity_id(
27 | entity_type="Incident", entity_id=incident["id"], mode="full"
28 | )
29 | json_bundle = json.dumps(bundle, indent=4)
30 |
31 | # Write the bundle
32 | f = open("My new incident.json", "w")
33 | f.write(json_bundle)
34 | f.close()
35 |
--------------------------------------------------------------------------------
/examples/export_incidents_stix2.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import json
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Create the bundle
15 | bundle = opencti_api_client.stix2.export_list("Incident")
16 | json_bundle = json.dumps(bundle, indent=4)
17 |
18 | # Write the bundle
19 | f = open("Incidents.json", "w")
20 | f.write(json_bundle)
21 | f.close()
22 |
--------------------------------------------------------------------------------
/examples/export_intrusion_set_stix2.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import json
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Create the bundle
15 | bundle = opencti_api_client.stix2.get_stix_bundle_or_object_from_entity_id(
16 | entity_type="Intrusion-Set",
17 | entity_id="4ecc2f52-d10a-4e10-bb9b-1ab5df2b282e",
18 | mode="full",
19 | )
20 | json_bundle = json.dumps(bundle, indent=4)
21 |
22 | # Write the bundle
23 | f = open("intrusion-set.json", "w")
24 | f.write(json_bundle)
25 | f.close()
26 |
--------------------------------------------------------------------------------
/examples/export_report_stix2.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import json
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Create the bundle
15 | bundle = opencti_api_client.stix2.get_stix_bundle_or_object_from_entity_id(
16 | entity_type="Report",
17 | entity_id="report--2dc2b918-a0a3-569e-a305-f784486003c2",
18 | mode="full",
19 | )
20 | json_bundle = json.dumps(bundle, indent=4)
21 |
22 | # Write the bundle
23 | f = open("report.json", "w")
24 | f.write(json_bundle)
25 | f.close()
26 |
--------------------------------------------------------------------------------
/examples/get_all_indicators_using_pagination.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Get all reports using the pagination
13 | custom_attributes = """
14 | id
15 | pattern_type
16 | created
17 | """
18 |
19 | final_indicators = []
20 | data = {"pagination": {"hasNextPage": True, "endCursor": None}}
21 | while data["pagination"]["hasNextPage"]:
22 | after = data["pagination"]["endCursor"]
23 | if after:
24 | print("Listing indicators after " + after)
25 | data = opencti_api_client.indicator.list(
26 | first=50,
27 | after=after,
28 | customAttributes=custom_attributes,
29 | withPagination=True,
30 | orderBy="created_at",
31 | orderMode="asc",
32 | )
33 | final_indicators += data["entities"]
34 |
35 | for indicator in final_indicators:
36 | print("[" + indicator["created"] + "] " + indicator["id"])
37 |
--------------------------------------------------------------------------------
/examples/get_all_reports_using_pagination.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Get all reports using the pagination
13 | custom_attributes = """
14 | id
15 | name
16 | published
17 | description
18 | """
19 |
20 | final_reports = []
21 | data = {"pagination": {"hasNextPage": True, "endCursor": None}}
22 | while data["pagination"]["hasNextPage"]:
23 | after = data["pagination"]["endCursor"]
24 | if after:
25 | print("Listing reports after " + after)
26 | data = opencti_api_client.report.list(
27 | first=50,
28 | after=after,
29 | customAttributes=custom_attributes,
30 | withPagination=True,
31 | orderBy="created_at",
32 | orderMode="asc",
33 | )
34 | final_reports = final_reports + data["entities"]
35 |
36 | # Print
37 | for report in final_reports:
38 | print("[" + report["published"] + "] " + report["name"])
39 |
--------------------------------------------------------------------------------
/examples/get_attack_pattern_by_mitre_id.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Get the Attack-Pattern T1514
13 | attack_pattern = opencti_api_client.attack_pattern.read(
14 | filters={
15 | "mode": "and",
16 | "filters": [{"key": "x_mitre_id", "values": ["T1514"]}],
17 | "filterGroups": [],
18 | }
19 | )
20 |
21 | # Print
22 | print(attack_pattern)
23 |
--------------------------------------------------------------------------------
/examples/get_entity_by_name_or_alias.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Get the ANSSI entity
13 | anssi = opencti_api_client.stix_domain_object.get_by_stix_id_or_name(name="ANSSI")
14 |
15 | # Print
16 | print(anssi)
17 |
--------------------------------------------------------------------------------
/examples/get_malwares_of_intrusion_set.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import datetime
3 |
4 | from pycti import OpenCTIApiClient
5 |
6 | # Variables
7 | api_url = "http://opencti:4000"
8 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
9 |
10 | # OpenCTI initialization
11 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
12 |
13 | # Create the Intrusion Set
14 | opencti_api_client.intrusion_set.create(
15 | name="APT28",
16 | description="Evil hackers",
17 | first_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
18 | last_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
19 | update=True,
20 | )
21 |
22 | # Get the intrusion set APT28
23 | intrusion_set = opencti_api_client.intrusion_set.read(
24 | filters={
25 | "mode": "and",
26 | "filters": [{"key": "name", "values": ["APT28"]}],
27 | "filterGroups": [],
28 | }
29 | )
30 |
31 | # Get the relations from APT28 to malwares
32 | stix_relations = opencti_api_client.stix_core_relationship.list(
33 | fromId=intrusion_set["id"], toTypes=["Malware"]
34 | )
35 |
36 | # Print
37 | for stix_relation in stix_relations:
38 | print("[" + stix_relation["to"]["stix_id"] + "] " + stix_relation["to"]["name"])
39 |
--------------------------------------------------------------------------------
/examples/get_marking_definitions.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Get all marking definitions
13 | marking_definitions = opencti_api_client.marking_definition.list()
14 |
15 | # Print
16 | for marking_definition in marking_definitions:
17 | print(
18 | "["
19 | + marking_definition["definition_type"]
20 | + "] "
21 | + marking_definition["definition"]
22 | )
23 |
--------------------------------------------------------------------------------
/examples/get_observable_exact_match.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Exact IP match
13 | opencti_api_client.stix_cyber_observable.create(
14 | simple_observable_key="IPv4-Addr.value", simple_observable_value="110.172.180.180"
15 | )
16 | print("IP ADDRESS")
17 | observable = opencti_api_client.stix_cyber_observable.read(
18 | filters={
19 | "mode": "and",
20 | "filters": [{"key": "value", "values": ["110.172.180.180"]}],
21 | "filterGroups": [],
22 | }
23 | )
24 | print(observable)
25 |
26 | # Exact File name match
27 | opencti_api_client.stix_cyber_observable.create(
28 | simple_observable_key="File.name", simple_observable_value="activeds.dll"
29 | )
30 | print("FILE NAME")
31 | observable = opencti_api_client.stix_cyber_observable.read(
32 | filters={
33 | "mode": "and",
34 | "filters": [{"key": "name", "values": ["activeds.dll"]}],
35 | "filterGroups": [],
36 | }
37 | )
38 | print(observable)
39 |
40 | # Exact File name match
41 | opencti_api_client.stix_cyber_observable.create(
42 | simple_observable_key="File.hashes.MD5",
43 | simple_observable_value="3aad33e025303dbae12c12b4ec5258c1",
44 | )
45 | print("FILE MD5")
46 | observable = opencti_api_client.stix_cyber_observable.read(
47 | filters={
48 | "mode": "and",
49 | "filters": [
50 | {"key": "hashes.MD5", "values": ["3aad33e025303dbae12c12b4ec5258c1"]}
51 | ],
52 | "filterGroups": [],
53 | }
54 | )
55 | print(observable)
56 |
--------------------------------------------------------------------------------
/examples/get_observables_search.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Search
13 | opencti_api_client.stix_cyber_observable.create(
14 | simple_observable_key="IPv4-Addr.value", simple_observable_value="65.89.87.4"
15 | )
16 | observables = opencti_api_client.stix_cyber_observable.list(search="65.89.87.4")
17 |
18 | print(observables)
19 |
--------------------------------------------------------------------------------
/examples/get_reports_about_intrusion_set.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import datetime
3 |
4 | from pycti import OpenCTIApiClient
5 |
6 | # Variables
7 | api_url = "http://opencti:4000"
8 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
9 |
10 | # OpenCTI initialization
11 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
12 |
13 |
14 | # Create the Intrusion Set
15 | opencti_api_client.intrusion_set.create(
16 | name="Sandworm Team",
17 | description="Evil hackers",
18 | first_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
19 | last_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
20 | update=True,
21 | )
22 |
23 | # Get the intrusion set Sandworm
24 | intrusion_set = opencti_api_client.intrusion_set.read(
25 | filters={
26 | "mode": "and",
27 | "filters": [{"key": "name", "values": ["Sandworm Team"]}],
28 | "filterGroups": [],
29 | }
30 | )
31 |
32 | # Get all reports
33 | reports = opencti_api_client.report.list(
34 | filters={
35 | "mode": "and",
36 | "filters": [{"key": "objects", "values": [intrusion_set["id"]]}],
37 | "filterGroups": [],
38 | },
39 | orderBy="published",
40 | orderMode="asc",
41 | )
42 |
43 | # Print
44 | if not reports:
45 | print(f"No {intrusion_set['name']} reports available")
46 | else:
47 | for report in reports:
48 | print(
49 | "["
50 | + report["standard_id"]
51 | + "] "
52 | + report["name"]
53 | + " ("
54 | + report["published"]
55 | + ")"
56 | )
57 |
--------------------------------------------------------------------------------
/examples/import_stix2_file.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # File to import
13 | file_to_import = "./test.json"
14 |
15 | # Import the bundle
16 | opencti_api_client.stix2.import_bundle_from_file(file_to_import, True)
17 |
--------------------------------------------------------------------------------
/examples/promote_observable_to_indicator.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | obs = opencti_api_client.stix_cyber_observable.create(
13 | simple_observable_key="IPv4-Addr.value", simple_observable_value="55.55.55.99"
14 | )
15 | indicator = opencti_api_client.stix_cyber_observable.promote_to_indicator_v2(
16 | id=obs.get("id")
17 | )
18 | print("promoted observable, new indicator is", indicator)
19 |
--------------------------------------------------------------------------------
/examples/run_all.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | for script in ./*.py; do
3 | if [[ $script == *"cmd_line_tag_latest_indicators_of_threat.py"* || $script == *"upload_artifacts.py"* ]]; then
4 | # TODO special execution for cmd tools
5 | continue
6 | fi
7 | python3 $script;
8 | done
--------------------------------------------------------------------------------
/examples/search_attack_pattern.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Search
13 | attack_patterns = opencti_api_client.attack_pattern.list(search="localgroup")
14 |
15 | # Print
16 | print(attack_patterns)
17 |
--------------------------------------------------------------------------------
/examples/search_malware.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Search
13 | malwares = opencti_api_client.malware.list(search="windows")
14 |
15 | # Print
16 | print(malwares)
17 |
--------------------------------------------------------------------------------
/examples/update_entity_attribute.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import datetime
3 |
4 | from pycti import OpenCTIApiClient
5 |
6 | # Variables
7 | api_url = "http://opencti:4000"
8 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
9 |
10 | # OpenCTI initialization
11 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
12 |
13 | # Create the Intrusion Set
14 | opencti_api_client.intrusion_set.create(
15 | name="APT28",
16 | description="Evil hackers",
17 | first_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
18 | last_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
19 | update=True,
20 | )
21 |
22 | # Get the intrusion set APT28
23 | intrusion_set = opencti_api_client.intrusion_set.read(
24 | filters={
25 | "mode": "and",
26 | "filters": [{"key": "name", "values": ["APT28"]}],
27 | "filterGroups": [],
28 | }
29 | )
30 |
31 | # Update the description
32 | opencti_api_client.stix_domain_object.update_field(
33 | id=intrusion_set["id"], input={"key": "description", "value": "This is APT28!"}
34 | )
35 |
--------------------------------------------------------------------------------
/examples/update_observable_attributes.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from pycti import OpenCTIApiClient
4 |
5 | # Variables
6 | api_url = "http://opencti:4000"
7 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
8 |
9 | # OpenCTI initialization
10 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
11 |
12 | # Create an observable
13 | observable = opencti_api_client.stix_cyber_observable.create(
14 | observableData={
15 | "type": "file",
16 | "x_opencti_description": "A malicious file",
17 | "hashes": {
18 | "MD5": "348aefbb6142d4fff8cf26fc5dc97f8a",
19 | "SHA-1": "486e7e66c3a098c1c8f42e26c78f259d6b3108a6",
20 | "SHA-256": "42c5e1fe01e689e550ba700b3c5dd4a04a84798c1868ba53c02abcbe21491515",
21 | },
22 | # "x_opencti_score": "90",
23 | }
24 | )
25 |
26 | # Update the fields
27 |
28 | reference = opencti_api_client.external_reference.create(
29 | source_name="Jen", url="https://janedoe.com", description="Sample Report"
30 | )
31 |
32 | opencti_api_client.stix_cyber_observable.add_external_reference(
33 | id=observable["id"], external_reference_id=reference["id"]
34 | )
35 |
36 | label = opencti_api_client.label.create(
37 | value="Suspicious",
38 | color="#ffa500",
39 | )
40 |
41 | opencti_api_client.stix_cyber_observable.add_label(
42 | id=observable["id"], marking_definition_id=label["id"]
43 | )
44 |
45 | author = opencti_api_client.identity.create(
46 | name="John's Work",
47 | description="Automated Toolkit",
48 | type="Organization",
49 | )
50 |
51 | opencti_api_client.stix_cyber_observable.update_created_by(
52 | id=observable["id"], identity_id=author["id"]
53 | )
54 |
--------------------------------------------------------------------------------
/examples/upload_artifacts.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | import os
3 |
4 | import magic
5 |
6 | from pycti import OpenCTIApiClient
7 |
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI instantiation
12 | OPENCTI_API_CLIENT = OpenCTIApiClient(api_url, api_token)
13 |
14 |
15 | def main():
16 | parser = argparse.ArgumentParser()
17 | parser.add_argument(
18 | "-f", "--file", required=True, help="The path of the Artifact(s) to upload."
19 | )
20 | parser.add_argument(
21 | "-d", "--description", default="", help="The description for the Artifact."
22 | )
23 | parser.add_argument(
24 | "-l", "--label", default="", help="Comma separated labels for the Artifact."
25 | )
26 | parser.add_argument(
27 | "-r",
28 | "--related",
29 | default=None,
30 | help="Standard id of an object related to the Artifact.",
31 | )
32 |
33 | args = parser.parse_args()
34 |
35 | if os.path.isdir(args.file):
36 | for currentpath, folders, files in os.walk(args.file):
37 | for filep in files:
38 | upload(
39 | os.path.join(currentpath, filep),
40 | args.description,
41 | args.label,
42 | args.related,
43 | )
44 |
45 | else:
46 | upload(args.file, args.description, args.label, args.related)
47 |
48 |
49 | def upload(file_path, description, labels, related_standard_id):
50 | file_data = b""
51 | with open(file_path, "rb") as f:
52 | file_data = f.read()
53 |
54 | mime_type = magic.from_buffer(file_data, mime=True)
55 |
56 | # Upload the file, returns the query response for the file upload
57 | kwargs = {
58 | "file_name": os.path.basename(file_path),
59 | "data": file_data,
60 | "mime_type": mime_type,
61 | "x_opencti_description": "",
62 | }
63 |
64 | if description:
65 | kwargs["x_opencti_description"] = description
66 |
67 | response = OPENCTI_API_CLIENT.stix_cyber_observable.upload_artifact(**kwargs)
68 | print(response)
69 |
70 | for label_str in labels.split(","):
71 | if label_str:
72 | label = OPENCTI_API_CLIENT.label.create(value=label_str)
73 | OPENCTI_API_CLIENT.stix_cyber_observable.add_label(
74 | id=response["id"], label_id=label["id"]
75 | )
76 |
77 | if related_standard_id:
78 | OPENCTI_API_CLIENT.stix_core_relationship.create(
79 | fromId=related_standard_id,
80 | toId=response["standard_id"],
81 | relationship_type="related-to",
82 | description=f"Related to {related_standard_id}",
83 | )
84 |
85 |
86 | if __name__ == "__main__":
87 | main()
88 |
--------------------------------------------------------------------------------
/examples/upload_file.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | from stix2 import TLP_GREEN
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Get a marking
15 | TLP_GREEN_CTI = opencti_api_client.marking_definition.read(id=TLP_GREEN["id"])
16 |
17 | # Upload the file (note that markings are optional)
18 | file = opencti_api_client.upload_file(
19 | file_name="./upload_file_example.pdf", file_markings=[TLP_GREEN["id"]]
20 | )
21 | print(file)
22 |
--------------------------------------------------------------------------------
/examples/upload_file_example.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/examples/upload_file_example.pdf
--------------------------------------------------------------------------------
/examples/upload_file_to_intrusion_set.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import datetime
4 |
5 | from pycti import OpenCTIApiClient
6 |
7 | # Variables
8 | api_url = "http://opencti:4000"
9 | api_token = "bfa014e0-e02e-4aa6-a42b-603b19dcf159"
10 |
11 | # OpenCTI initialization
12 | opencti_api_client = OpenCTIApiClient(api_url, api_token)
13 |
14 | # Create the Intrusion Set
15 | intrusion_set = opencti_api_client.intrusion_set.create(
16 | name="My new Intrusion Set",
17 | description="Evil Cluster",
18 | first_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
19 | last_seen=datetime.date.today().strftime("%Y-%m-%dT%H:%M:%S+00:00"),
20 | update=True,
21 | )
22 |
23 | # Print
24 | print(intrusion_set)
25 |
26 | # Upload the file
27 | file = opencti_api_client.stix_domain_object.add_file(
28 | id=intrusion_set["id"],
29 | file_name="./upload_file_example.pdf",
30 | )
31 | print(file)
32 |
--------------------------------------------------------------------------------
/pycti/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | __version__ = "6.6.14"
3 |
4 | from .api.opencti_api_client import OpenCTIApiClient
5 | from .api.opencti_api_connector import OpenCTIApiConnector
6 | from .api.opencti_api_work import OpenCTIApiWork
7 | from .connector.opencti_connector import ConnectorType, OpenCTIConnector
8 | from .connector.opencti_connector_helper import (
9 | OpenCTIConnectorHelper,
10 | get_config_variable,
11 | )
12 | from .connector.opencti_metric_handler import OpenCTIMetricHandler
13 | from .entities.opencti_attack_pattern import AttackPattern
14 | from .entities.opencti_campaign import Campaign
15 |
16 | # Administrative entities
17 | from .entities.opencti_capability import Capability
18 | from .entities.opencti_case_incident import CaseIncident
19 | from .entities.opencti_case_rfi import CaseRfi
20 | from .entities.opencti_case_rft import CaseRft
21 | from .entities.opencti_channel import Channel
22 | from .entities.opencti_course_of_action import CourseOfAction
23 | from .entities.opencti_data_component import DataComponent
24 | from .entities.opencti_data_source import DataSource
25 | from .entities.opencti_external_reference import ExternalReference
26 | from .entities.opencti_feedback import Feedback
27 | from .entities.opencti_group import Group
28 | from .entities.opencti_grouping import Grouping
29 | from .entities.opencti_identity import Identity
30 | from .entities.opencti_incident import Incident
31 | from .entities.opencti_indicator import Indicator
32 | from .entities.opencti_infrastructure import Infrastructure
33 | from .entities.opencti_intrusion_set import IntrusionSet
34 | from .entities.opencti_kill_chain_phase import KillChainPhase
35 | from .entities.opencti_label import Label
36 | from .entities.opencti_location import Location
37 | from .entities.opencti_malware import Malware
38 | from .entities.opencti_malware_analysis import MalwareAnalysis
39 | from .entities.opencti_marking_definition import MarkingDefinition
40 | from .entities.opencti_note import Note
41 | from .entities.opencti_observed_data import ObservedData
42 | from .entities.opencti_opinion import Opinion
43 | from .entities.opencti_report import Report
44 | from .entities.opencti_role import Role
45 | from .entities.opencti_settings import Settings
46 | from .entities.opencti_stix_core_relationship import StixCoreRelationship
47 | from .entities.opencti_stix_cyber_observable import StixCyberObservable
48 | from .entities.opencti_stix_domain_object import StixDomainObject
49 | from .entities.opencti_stix_nested_ref_relationship import StixNestedRefRelationship
50 | from .entities.opencti_stix_object_or_stix_relationship import (
51 | StixObjectOrStixRelationship,
52 | )
53 | from .entities.opencti_stix_sighting_relationship import StixSightingRelationship
54 | from .entities.opencti_task import Task
55 | from .entities.opencti_threat_actor import ThreatActor
56 | from .entities.opencti_threat_actor_group import ThreatActorGroup
57 | from .entities.opencti_threat_actor_individual import ThreatActorIndividual
58 | from .entities.opencti_tool import Tool
59 | from .entities.opencti_user import User
60 | from .entities.opencti_vulnerability import Vulnerability
61 | from .utils.constants import (
62 | CustomObjectCaseIncident,
63 | CustomObjectChannel,
64 | CustomObjectTask,
65 | CustomObservableBankAccount,
66 | CustomObservableCredential,
67 | CustomObservableCryptocurrencyWallet,
68 | CustomObservableCryptographicKey,
69 | CustomObservableHostname,
70 | CustomObservableMediaContent,
71 | CustomObservablePaymentCard,
72 | CustomObservablePersona,
73 | CustomObservablePhoneNumber,
74 | CustomObservableText,
75 | CustomObservableTrackingNumber,
76 | CustomObservableUserAgent,
77 | MultipleRefRelationship,
78 | StixCyberObservableTypes,
79 | StixMetaTypes,
80 | )
81 | from .utils.opencti_stix2 import (
82 | STIX_EXT_MITRE,
83 | STIX_EXT_OCTI,
84 | STIX_EXT_OCTI_SCO,
85 | OpenCTIStix2,
86 | )
87 | from .utils.opencti_stix2_splitter import OpenCTIStix2Splitter
88 | from .utils.opencti_stix2_update import OpenCTIStix2Update
89 | from .utils.opencti_stix2_utils import OpenCTIStix2Utils
90 |
91 | __all__ = [
92 | "AttackPattern",
93 | "Campaign",
94 | "CaseIncident",
95 | "CaseRfi",
96 | "CaseRft",
97 | "Channel",
98 | "Task",
99 | "ConnectorType",
100 | "CourseOfAction",
101 | "DataComponent",
102 | "DataSource",
103 | "ExternalReference",
104 | "Feedback",
105 | "Grouping",
106 | "Identity",
107 | "Incident",
108 | "Indicator",
109 | "Infrastructure",
110 | "IntrusionSet",
111 | "KillChainPhase",
112 | "Label",
113 | "Location",
114 | "Malware",
115 | "MalwareAnalysis",
116 | "MarkingDefinition",
117 | "Note",
118 | "ObservedData",
119 | "OpenCTIApiClient",
120 | "OpenCTIApiConnector",
121 | "OpenCTIApiWork",
122 | "OpenCTIConnector",
123 | "OpenCTIConnectorHelper",
124 | "OpenCTIMetricHandler",
125 | "OpenCTIStix2",
126 | "OpenCTIStix2Splitter",
127 | "OpenCTIStix2Update",
128 | "OpenCTIStix2Utils",
129 | "Opinion",
130 | "Report",
131 | "StixCoreRelationship",
132 | "StixCyberObservable",
133 | "StixNestedRefRelationship",
134 | "StixCyberObservableTypes",
135 | "StixDomainObject",
136 | "StixMetaTypes",
137 | "MultipleRefRelationship",
138 | "StixObjectOrStixRelationship",
139 | "StixSightingRelationship",
140 | "ThreatActor",
141 | "ThreatActorGroup",
142 | "ThreatActorIndividual",
143 | "Tool",
144 | "Vulnerability",
145 | "get_config_variable",
146 | "CustomObjectCaseIncident",
147 | "CustomObjectTask",
148 | "CustomObjectChannel",
149 | "StixCyberObservableTypes",
150 | "CustomObservableCredential",
151 | "CustomObservableHostname",
152 | "CustomObservableUserAgent",
153 | "CustomObservableBankAccount",
154 | "CustomObservableCryptographicKey",
155 | "CustomObservableCryptocurrencyWallet",
156 | "CustomObservablePaymentCard",
157 | "CustomObservablePersona",
158 | "CustomObservablePhoneNumber",
159 | "CustomObservableTrackingNumber",
160 | "CustomObservableText",
161 | "CustomObservableMediaContent",
162 | "STIX_EXT_MITRE",
163 | "STIX_EXT_OCTI_SCO",
164 | "STIX_EXT_OCTI",
165 | "Capability",
166 | "Role",
167 | "Group",
168 | "User",
169 | "Settings",
170 | ]
171 |
--------------------------------------------------------------------------------
/pycti/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/pycti/api/__init__.py
--------------------------------------------------------------------------------
/pycti/api/opencti_api_connector.py:
--------------------------------------------------------------------------------
1 | import json
2 | from typing import Any, Dict
3 |
4 | from pycti.connector.opencti_connector import OpenCTIConnector
5 |
6 |
7 | class OpenCTIApiConnector:
8 | """OpenCTIApiConnector"""
9 |
10 | def __init__(self, api):
11 | self.api = api
12 |
13 | def read(self, connector_id: str) -> Dict:
14 | """Reading the connector and its details
15 |
16 | :return: return all the connector details
17 | :rtype: dict
18 | """
19 | self.api.app_logger.info("[INFO] Getting connector details ...")
20 | query = """
21 | query GetConnector($id: String!) {
22 | connector(id: $id) {
23 | id
24 | name
25 | active
26 | auto
27 | only_contextual
28 | connector_type
29 | connector_scope
30 | connector_state
31 | connector_queue_details {
32 | messages_number
33 | messages_size
34 | }
35 | updated_at
36 | created_at
37 | config {
38 | listen
39 | listen_exchange
40 | push
41 | push_exchange
42 | push_routing
43 | }
44 | built_in
45 | }
46 | }
47 | """
48 | result = self.api.query(query, {"id": connector_id})
49 | return result["data"]["connector"]
50 |
51 | def list(self) -> Dict:
52 | """list available connectors
53 |
54 | :return: return dict with connectors
55 | :rtype: dict
56 | """
57 |
58 | self.api.app_logger.info("Getting connectors ...")
59 | query = """
60 | query GetConnectors {
61 | connectorsForWorker {
62 | id
63 | name
64 | connector_user {
65 | api_token
66 | }
67 | config {
68 | connection {
69 | host
70 | vhost
71 | use_ssl
72 | port
73 | user
74 | pass
75 | }
76 | listen
77 | listen_exchange
78 | listen_callback_uri
79 | push
80 | push_exchange
81 | push_routing
82 | }
83 | }
84 | }
85 | """
86 | result = self.api.query(query)
87 | return result["data"]["connectorsForWorker"]
88 |
89 | def ping(
90 | self, connector_id: str, connector_state: Any, connector_info: Dict
91 | ) -> Dict:
92 | """pings a connector by id and state
93 |
94 | :param connector_id: the connectors id
95 | :type connector_id: str
96 | :param connector_state: state for the connector
97 | :type connector_state:
98 | :param connector_info: all details connector
99 | :type connector_info: Dict
100 | :return: the response pingConnector data dict
101 | :rtype: dict
102 | """
103 |
104 | query = """
105 | mutation PingConnector($id: ID!, $state: String, $connectorInfo: ConnectorInfoInput ) {
106 | pingConnector(id: $id, state: $state, connectorInfo: $connectorInfo) {
107 | id
108 | connector_state
109 | connector_info {
110 | run_and_terminate
111 | buffering
112 | queue_threshold
113 | queue_messages_size
114 | next_run_datetime
115 | last_run_datetime
116 | }
117 | }
118 | }
119 | """
120 | result = self.api.query(
121 | query,
122 | {
123 | "id": connector_id,
124 | "state": json.dumps(connector_state),
125 | "connectorInfo": connector_info,
126 | },
127 | )
128 | return result["data"]["pingConnector"]
129 |
130 | def register(self, connector: OpenCTIConnector) -> Dict:
131 | """register a connector with OpenCTI
132 |
133 | :param connector: `OpenCTIConnector` connector object
134 | :type connector: OpenCTIConnector
135 | :return: the response registerConnector data dict
136 | :rtype: dict
137 | """
138 |
139 | query = """
140 | mutation RegisterConnector($input: RegisterConnectorInput) {
141 | registerConnector(input: $input) {
142 | id
143 | connector_state
144 | config {
145 | connection {
146 | host
147 | vhost
148 | use_ssl
149 | port
150 | user
151 | pass
152 | }
153 | listen
154 | listen_routing
155 | listen_exchange
156 | push
157 | push_routing
158 | push_exchange
159 | }
160 | connector_user_id
161 | }
162 | }
163 | """
164 | result = self.api.query(query, connector.to_input())
165 | return result["data"]["registerConnector"]
166 |
167 | def unregister(self, _id: str) -> Dict:
168 | """unregister a connector with OpenCTI
169 |
170 | :param _id: `OpenCTIConnector` connector id
171 | :type _id: string
172 | :return: the response registerConnector data dict
173 | :rtype: dict
174 | """
175 | query = """
176 | mutation ConnectorDeletionMutation($id: ID!) {
177 | deleteConnector(id: $id)
178 | }
179 | """
180 | return self.api.query(query, {"id": _id})
181 |
--------------------------------------------------------------------------------
/pycti/api/opencti_api_playbook.py:
--------------------------------------------------------------------------------
1 | class OpenCTIApiPlaybook:
2 | """OpenCTIApiPlaybook"""
3 |
4 | def __init__(self, api):
5 | self.api = api
6 |
7 | def playbook_step_execution(self, playbook: dict, bundle: str):
8 | self.api.app_logger.info(
9 | "Executing playbook step",
10 | {
11 | "playbook_id": playbook["playbook_id"],
12 | "step_id": playbook["step_id"],
13 | "data_instance_id": playbook["data_instance_id"],
14 | },
15 | )
16 | query = """
17 | mutation PlaybookStepExecution($execution_id: ID!, $event_id: ID!, $execution_start: DateTime!, $data_instance_id: ID!, $playbook_id: ID!, $previous_step_id: ID!, $step_id: ID!, $previous_bundle: String!, $bundle: String!) {
18 | playbookStepExecution(execution_id: $execution_id, event_id: $event_id, execution_start: $execution_start, data_instance_id: $data_instance_id, playbook_id: $playbook_id, previous_step_id: $previous_step_id, step_id: $step_id, previous_bundle: $previous_bundle, bundle: $bundle)
19 | }
20 | """
21 | self.api.query(
22 | query,
23 | {
24 | "execution_id": playbook["execution_id"],
25 | "event_id": playbook["event_id"],
26 | "execution_start": playbook["execution_start"],
27 | "playbook_id": playbook["playbook_id"],
28 | "data_instance_id": playbook["data_instance_id"],
29 | "step_id": playbook["step_id"],
30 | "previous_step_id": playbook["previous_step_id"],
31 | "previous_bundle": playbook["previous_bundle"],
32 | "bundle": bundle,
33 | },
34 | )
35 |
--------------------------------------------------------------------------------
/pycti/connector/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/pycti/connector/__init__.py
--------------------------------------------------------------------------------
/pycti/connector/opencti_connector.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 | # Scope definition
4 | # EXTERNAL_IMPORT = None
5 | # INTERNAL_IMPORT_FILE = Files mime types to support (application/json, ...)
6 | # INTERNAL_ENRICHMENT = Entity types to support (Report, Hash, ...)
7 | # INTERNAL_EXPORT_FILE = Files mime types to generate (application/pdf, ...)
8 |
9 |
10 | class ConnectorType(Enum):
11 | EXTERNAL_IMPORT = "EXTERNAL_IMPORT" # From remote sources to OpenCTI stix2
12 | INTERNAL_IMPORT_FILE = (
13 | "INTERNAL_IMPORT_FILE" # From OpenCTI file system to OpenCTI stix2
14 | )
15 | INTERNAL_ENRICHMENT = "INTERNAL_ENRICHMENT" # From OpenCTI stix2 to OpenCTI stix2
16 | INTERNAL_ANALYSIS = "INTERNAL_ANALYSIS" # From OpenCTI file system or OpenCTI stix2 to OpenCTI file system
17 | INTERNAL_EXPORT_FILE = (
18 | "INTERNAL_EXPORT_FILE" # From OpenCTI stix2 to OpenCTI file system
19 | )
20 | STREAM = "STREAM" # Read the stream and do something
21 |
22 |
23 | class OpenCTIConnector:
24 | """Main class for OpenCTI connector
25 |
26 | :param connector_id: id for the connector (valid uuid4)
27 | :type connector_id: str
28 | :param connector_name: name for the connector
29 | :type connector_name: str
30 | :param connector_type: valid OpenCTI connector type (see `ConnectorType`)
31 | :type connector_type: str
32 | :param scope: connector scope
33 | :type scope: str
34 | :raises ValueError: if the connector type is not valid
35 | """
36 |
37 | def __init__(
38 | self,
39 | connector_id: str,
40 | connector_name: str,
41 | connector_type: str,
42 | scope: str,
43 | auto: bool,
44 | only_contextual: bool,
45 | playbook_compatible: bool,
46 | listen_callback_uri=None,
47 | ):
48 | self.id = connector_id
49 | self.name = connector_name
50 | self.type = ConnectorType(connector_type)
51 | if self.type is None:
52 | raise ValueError("Invalid connector type: " + connector_type)
53 | if scope and len(scope) > 0:
54 | self.scope = scope.split(",")
55 | else:
56 | self.scope = []
57 | self.auto = auto
58 | self.only_contextual = only_contextual
59 | self.playbook_compatible = playbook_compatible
60 | self.listen_callback_uri = listen_callback_uri
61 |
62 | def to_input(self) -> dict:
63 | """connector input to use in API query
64 |
65 | :return: dict with connector data
66 | :rtype: dict
67 | """
68 | return {
69 | "input": {
70 | "id": self.id,
71 | "name": self.name,
72 | "type": self.type.name,
73 | "scope": self.scope,
74 | "auto": self.auto,
75 | "only_contextual": self.only_contextual,
76 | "playbook_compatible": self.playbook_compatible,
77 | "listen_callback_uri": self.listen_callback_uri,
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/pycti/connector/opencti_metric_handler.py:
--------------------------------------------------------------------------------
1 | from typing import Type, Union
2 |
3 | from prometheus_client import Counter, Enum, start_http_server
4 |
5 |
6 | class OpenCTIMetricHandler:
7 | def __init__(self, connector_logger, activated: bool = False, port: int = 9095):
8 | """
9 | Init of OpenCTIMetricHandler class.
10 |
11 | Parameters
12 | ----------
13 | activated : bool, default False
14 | If True use metrics in client and connectors.
15 | port : int, default 9095
16 | Port for prometheus server.
17 | """
18 | self.activated = activated
19 | self.connector_logger = connector_logger
20 | if self.activated:
21 | self.connector_logger.info("Exposing metrics on port", {"port": port})
22 | start_http_server(port)
23 | self._metrics = {
24 | "bundle_send": Counter(
25 | "bundle_send",
26 | "Number of bundle send",
27 | ),
28 | "record_send": Counter(
29 | "record_send",
30 | "Number of record (objects per bundle) send",
31 | ),
32 | "run_count": Counter(
33 | "run_count",
34 | "Number of run",
35 | ),
36 | "ping_api_count": Counter(
37 | "ping_api_count",
38 | "Number of ping to the api",
39 | ),
40 | "ping_api_error": Counter(
41 | "ping_api_error",
42 | "Number of error when pinging the api",
43 | ),
44 | "error_count": Counter(
45 | "error_count",
46 | "Number of error",
47 | ),
48 | "client_error_count": Counter(
49 | "client_error_count",
50 | "Number of client error",
51 | ),
52 | "state": Enum(
53 | "state", "State of connector", states=["idle", "running", "stopped"]
54 | ),
55 | }
56 |
57 | def _metric_exists(
58 | self, name: str, expected_type: Union[Type[Counter], Type[Enum]]
59 | ) -> bool:
60 | """
61 | Check if a metric exists and has the correct type.
62 |
63 | If it does not, log an error and return False.
64 |
65 | Parameters
66 | ----------
67 | name : str
68 | Name of the metric to check.
69 | expected_type : Counter or Enum
70 | Expected type of the metric.
71 |
72 | Returns
73 | -------
74 | bool
75 | True if the metric exists and is of the correct type else False.
76 | """
77 | if name not in self._metrics:
78 | self.connector_logger.error("Metric does not exist.", {"name": name})
79 | return False
80 | if not isinstance(self._metrics[name], expected_type):
81 | self.connector_logger.error(
82 | "Metric not of expected type",
83 | {"name": name, "expected_type": expected_type},
84 | )
85 | return False
86 | return True
87 |
88 | def inc(self, name: str, n: int = 1):
89 | """
90 | Increment the metric (counter) `name` by `n`.
91 |
92 | Parameters
93 | ----------
94 | name : str
95 | Name of the metric to increment.
96 | n : int, default 1
97 | Increment the counter by `n`.
98 | """
99 | if self.activated:
100 | if self._metric_exists(name, Counter):
101 | self._metrics[name].inc(n)
102 |
103 | def state(self, state: str, name: str = "state"):
104 | """
105 | Set the state `state` for metric `name`.
106 |
107 | Parameters
108 | ----------
109 | state : str
110 | State to set.
111 | name : str, default = "state"
112 | Name of the metric to set.
113 | """
114 | if self.activated:
115 | if self._metric_exists(name, Enum):
116 | self._metrics[name].state(state)
117 |
--------------------------------------------------------------------------------
/pycti/entities/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/pycti/entities/__init__.py
--------------------------------------------------------------------------------
/pycti/entities/indicator/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/pycti/entities/indicator/__init__.py
--------------------------------------------------------------------------------
/pycti/entities/indicator/opencti_indicator_properties.py:
--------------------------------------------------------------------------------
1 | INDICATOR_PROPERTIES = """
2 | id
3 | standard_id
4 | entity_type
5 | parent_types
6 | spec_version
7 | created_at
8 | updated_at
9 | creators {
10 | id
11 | name
12 | }
13 | createdBy {
14 | ... on Identity {
15 | id
16 | standard_id
17 | entity_type
18 | parent_types
19 | spec_version
20 | identity_class
21 | name
22 | description
23 | roles
24 | contact_information
25 | x_opencti_aliases
26 | created
27 | modified
28 | objectLabel {
29 | id
30 | value
31 | color
32 | }
33 | }
34 | ... on Organization {
35 | x_opencti_organization_type
36 | x_opencti_reliability
37 | }
38 | ... on Individual {
39 | x_opencti_firstname
40 | x_opencti_lastname
41 | }
42 | }
43 | objectOrganization {
44 | id
45 | standard_id
46 | name
47 | }
48 | objectMarking {
49 | id
50 | standard_id
51 | entity_type
52 | definition_type
53 | definition
54 | created
55 | modified
56 | x_opencti_order
57 | x_opencti_color
58 | }
59 | objectLabel {
60 | id
61 | value
62 | color
63 | }
64 | externalReferences {
65 | edges {
66 | node {
67 | id
68 | standard_id
69 | entity_type
70 | source_name
71 | description
72 | url
73 | hash
74 | external_id
75 | created
76 | modified
77 | }
78 | }
79 | }
80 | revoked
81 | confidence
82 | created
83 | modified
84 | pattern_type
85 | pattern_version
86 | pattern
87 | name
88 | description
89 | indicator_types
90 | valid_from
91 | valid_until
92 | x_opencti_score
93 | x_opencti_detection
94 | x_opencti_main_observable_type
95 | x_opencti_observable_values {
96 | type
97 | value
98 | hashes {
99 | algorithm
100 | hash
101 | }
102 | }
103 | x_mitre_platforms
104 | observables {
105 | edges {
106 | node {
107 | id
108 | entity_type
109 | observable_value
110 | }
111 | }
112 | }
113 | killChainPhases {
114 | id
115 | standard_id
116 | entity_type
117 | kill_chain_name
118 | phase_name
119 | x_opencti_order
120 | created
121 | modified
122 | }
123 | """
124 | INDICATOR_PROPERTIES_WITH_FILES = """
125 | id
126 | standard_id
127 | entity_type
128 | parent_types
129 | spec_version
130 | created_at
131 | updated_at
132 | creators {
133 | id
134 | name
135 | }
136 | createdBy {
137 | ... on Identity {
138 | id
139 | standard_id
140 | entity_type
141 | parent_types
142 | spec_version
143 | identity_class
144 | name
145 | description
146 | roles
147 | contact_information
148 | x_opencti_aliases
149 | created
150 | modified
151 | objectLabel {
152 | id
153 | value
154 | color
155 | }
156 | }
157 | ... on Organization {
158 | x_opencti_organization_type
159 | x_opencti_reliability
160 | }
161 | ... on Individual {
162 | x_opencti_firstname
163 | x_opencti_lastname
164 | }
165 | }
166 | objectOrganization {
167 | id
168 | standard_id
169 | name
170 | }
171 | objectMarking {
172 | id
173 | standard_id
174 | entity_type
175 | definition_type
176 | definition
177 | created
178 | modified
179 | x_opencti_order
180 | x_opencti_color
181 | }
182 | objectLabel {
183 | id
184 | value
185 | color
186 | }
187 | externalReferences {
188 | edges {
189 | node {
190 | id
191 | standard_id
192 | entity_type
193 | source_name
194 | description
195 | url
196 | hash
197 | external_id
198 | created
199 | modified
200 | importFiles {
201 | edges {
202 | node {
203 | id
204 | name
205 | size
206 | metaData {
207 | mimetype
208 | version
209 | }
210 | }
211 | }
212 | }
213 | }
214 | }
215 | }
216 | revoked
217 | confidence
218 | created
219 | modified
220 | pattern_type
221 | pattern_version
222 | pattern
223 | name
224 | description
225 | indicator_types
226 | valid_from
227 | valid_until
228 | x_opencti_score
229 | x_opencti_detection
230 | x_opencti_main_observable_type
231 | x_opencti_observable_values {
232 | type
233 | value
234 | hashes {
235 | algorithm
236 | hash
237 | }
238 | }
239 | x_mitre_platforms
240 | observables {
241 | edges {
242 | node {
243 | id
244 | entity_type
245 | observable_value
246 | }
247 | }
248 | }
249 | killChainPhases {
250 | id
251 | standard_id
252 | entity_type
253 | kill_chain_name
254 | phase_name
255 | x_opencti_order
256 | created
257 | modified
258 | }
259 | importFiles {
260 | edges {
261 | node {
262 | id
263 | name
264 | size
265 | metaData {
266 | mimetype
267 | version
268 | }
269 | }
270 | }
271 | }
272 | """
273 |
--------------------------------------------------------------------------------
/pycti/entities/opencti_capability.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, List
2 |
3 |
4 | class Capability:
5 | """Represents a role capability on the OpenCTI platform
6 |
7 | See the properties attribute to understand which properties are fetched by
8 | default from the graphql queries.
9 | """
10 |
11 | def __init__(self, opencti):
12 | self.opencti = opencti
13 | self.properties = """
14 | id
15 | standard_id
16 | entity_type
17 | parent_types
18 | name
19 | description
20 | attribute_order
21 | created_at
22 | updated_at
23 | """
24 |
25 | def list(self, **kwargs) -> List[Dict]:
26 | """Lists all capabilities available on the platform
27 |
28 | :param customAttributes: Custom attributes to retrieve from the GraphQL
29 | query.
30 | :type customAttributes: str, optional
31 | :return: List of capabilities
32 | :rtype: List[Dict]
33 | """
34 | custom_attributes = kwargs.get("customAttributes")
35 | self.opencti.admin_logger.info("Listing capabilities")
36 | query = (
37 | """
38 | query CapabilityList {
39 | capabilities {
40 | edges {
41 | node {
42 | """
43 | + (self.properties if custom_attributes is None else custom_attributes)
44 | + """
45 | }
46 | }
47 | }
48 | }
49 | """
50 | )
51 | result = self.opencti.query(query)
52 | return self.opencti.process_multiple(result["data"]["capabilities"])
53 |
--------------------------------------------------------------------------------
/pycti/entities/opencti_stix.py:
--------------------------------------------------------------------------------
1 | class Stix:
2 | def __init__(self, opencti):
3 | self.opencti = opencti
4 |
5 | """
6 | Delete a Stix element
7 |
8 | :param id: the Stix element id
9 | :return void
10 | """
11 |
12 | def delete(self, **kwargs):
13 | id = kwargs.get("id", None)
14 | if id is not None:
15 | self.opencti.app_logger.info("Deleting Stix element", {"id": id})
16 | query = """
17 | mutation StixEdit($id: ID!) {
18 | stixEdit(id: $id) {
19 | delete
20 | }
21 | }
22 | """
23 | self.opencti.query(query, {"id": id})
24 | else:
25 | self.opencti.app_logger.error("[opencti_stix] Missing parameters: id")
26 | return None
27 |
28 | """
29 | Merge a Stix-Object object field
30 |
31 | :param id: the Stix-Object id
32 | :param key: the key of the field
33 | :param value: the value of the field
34 | :return The updated Stix-Object object
35 | """
36 |
37 | def merge(self, **kwargs):
38 | id = kwargs.get("id")
39 | stix_objects_ids = kwargs.get("object_ids")
40 | if id is not None and stix_objects_ids is not None:
41 | self.opencti.app_logger.info(
42 | "Merging Stix object", {"id": id, "sources": ",".join(stix_objects_ids)}
43 | )
44 | query = """
45 | mutation StixEdit($id: ID!, $stixObjectsIds: [String]!) {
46 | stixEdit(id: $id) {
47 | merge(stixObjectsIds: $stixObjectsIds) {
48 | id
49 | standard_id
50 | entity_type
51 | }
52 | }
53 | }
54 | """
55 | result = self.opencti.query(
56 | query,
57 | {
58 | "id": id,
59 | "stixObjectsIds": stix_objects_ids,
60 | },
61 | )
62 | return self.opencti.process_multiple_fields(
63 | result["data"]["stixEdit"]["merge"]
64 | )
65 | else:
66 | self.opencti.app_logger.error(
67 | "[opencti_stix] Missing parameters: id and object_ids"
68 | )
69 | return None
70 |
--------------------------------------------------------------------------------
/pycti/entities/stix_cyber_observable/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/pycti/entities/stix_cyber_observable/__init__.py
--------------------------------------------------------------------------------
/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py:
--------------------------------------------------------------------------------
1 | import deprecation
2 |
3 |
4 | class StixCyberObservableDeprecatedMixin:
5 | """
6 | deprecated [>=6.2 & <6.8]`
7 | Promote a Stix-Observable to an Indicator
8 |
9 | :param id: the Stix-Observable id
10 | :return the observable
11 | """
12 |
13 | @deprecation.deprecated(
14 | deprecated_in="6.2",
15 | removed_in="6.5",
16 | details="Use promote_to_indicator_v2 instead.",
17 | )
18 | def promote_to_indicator(self, **kwargs):
19 | id = kwargs.get("id", None)
20 | custom_attributes = kwargs.get("customAttributes", None)
21 | with_files = kwargs.get("withFiles", False)
22 | if id is not None:
23 | self.opencti.app_logger.info(
24 | "Promoting Stix-Observable",
25 | {
26 | "id": id,
27 | "withFiles": with_files,
28 | "customAttributes": custom_attributes,
29 | },
30 | )
31 | query = (
32 | """
33 | mutation StixCyberObservableEdit($id: ID!) {
34 | stixCyberObservableEdit(id: $id) {
35 | promote {
36 | """
37 | + (
38 | custom_attributes
39 | if custom_attributes is not None
40 | else (self.properties_with_files if with_files else self.properties)
41 | )
42 | + """
43 | }
44 | }
45 | }
46 | """
47 | )
48 | result = self.opencti.query(query, {"id": id})
49 | return self.opencti.process_multiple_fields(
50 | result["data"]["stixCyberObservableEdit"]["promote"]
51 | )
52 | else:
53 | self.opencti.app_logger.error(
54 | "[opencti_stix_cyber_observable_promote] Missing parameters: id"
55 | )
56 | return None
57 |
--------------------------------------------------------------------------------
/pycti/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/pycti/utils/__init__.py
--------------------------------------------------------------------------------
/pycti/utils/opencti_logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from datetime import datetime, timezone
3 |
4 | from pythonjsonlogger import jsonlogger
5 |
6 |
7 | class CustomJsonFormatter(jsonlogger.JsonFormatter):
8 | def add_fields(self, log_record, record, message_dict):
9 | super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
10 | if not log_record.get("timestamp"):
11 | # This doesn't use record.created, so it is slightly off
12 | now = datetime.now(tz=timezone.utc)
13 | log_record["timestamp"] = now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
14 | if log_record.get("level"):
15 | log_record["level"] = log_record["level"].upper()
16 | else:
17 | log_record["level"] = record.levelname
18 |
19 |
20 | def logger(level, json_logging=True):
21 | # Exceptions
22 | logging.getLogger("urllib3").setLevel(logging.WARNING)
23 | logging.getLogger("pika").setLevel(logging.ERROR)
24 | # Exceptions
25 | if json_logging:
26 | log_handler = logging.StreamHandler()
27 | log_handler.setLevel(level)
28 | formatter = CustomJsonFormatter("%(timestamp)s %(level)s %(name)s %(message)s")
29 | log_handler.setFormatter(formatter)
30 | logging.basicConfig(handlers=[log_handler], level=level, force=True)
31 | else:
32 | logging.basicConfig(level=level)
33 |
34 | class AppLogger:
35 | def __init__(self, name):
36 | self.local_logger = logging.getLogger(name)
37 |
38 | @staticmethod
39 | def prepare_meta(meta=None):
40 | return None if meta is None else {"attributes": meta}
41 |
42 | @staticmethod
43 | def setup_logger_level(lib, log_level):
44 | logging.getLogger(lib).setLevel(log_level)
45 |
46 | def debug(self, message, meta=None):
47 | self.local_logger.debug(message, extra=AppLogger.prepare_meta(meta))
48 |
49 | def info(self, message, meta=None):
50 | self.local_logger.info(message, extra=AppLogger.prepare_meta(meta))
51 |
52 | def warning(self, message, meta=None):
53 | self.local_logger.warning(message, extra=AppLogger.prepare_meta(meta))
54 |
55 | def error(self, message, meta=None):
56 | # noinspection PyTypeChecker
57 | self.local_logger.error(
58 | message, exc_info=1, extra=AppLogger.prepare_meta(meta)
59 | )
60 |
61 | return AppLogger
62 |
--------------------------------------------------------------------------------
/pycti/utils/opencti_stix2_identifier.py:
--------------------------------------------------------------------------------
1 | import uuid
2 |
3 | from stix2.canonicalization.Canonicalize import canonicalize
4 |
5 |
6 | def external_reference_generate_id(url=None, source_name=None, external_id=None):
7 | if url is not None:
8 | data = {"url": url}
9 | elif source_name is not None and external_id is not None:
10 | data = {"source_name": source_name, "external_id": external_id}
11 | else:
12 | return None
13 | data = canonicalize(data, utf8=False)
14 | id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
15 | return "external-reference--" + id
16 |
17 |
18 | def kill_chain_phase_generate_id(phase_name, kill_chain_name):
19 | data = {"phase_name": phase_name, "kill_chain_name": kill_chain_name}
20 | data = canonicalize(data, utf8=False)
21 | id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
22 | return "kill-chain-phase--" + id
23 |
--------------------------------------------------------------------------------
/pycti/utils/opencti_stix2_utils.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Dict
2 |
3 | from stix2 import EqualityComparisonExpression, ObjectPath, ObservationExpression
4 |
5 | SUPPORTED_STIX_ENTITY_OBJECTS = [
6 | "attack-pattern",
7 | "campaign",
8 | "case-incident",
9 | "x-opencti-case-incident",
10 | "case-rfi",
11 | "x-opencti-case-rfi",
12 | "case-rft",
13 | "x-opencti-case-rft",
14 | "channel",
15 | "course-of-action",
16 | "data-component",
17 | "x-mitre-data-component",
18 | "data-source",
19 | "x-mitre-data-source",
20 | "event",
21 | "external-reference",
22 | "feedback",
23 | "x-opencti-feedback",
24 | "grouping",
25 | "identity",
26 | "incident",
27 | "indicator",
28 | "infrastructure",
29 | "intrusion-set",
30 | "kill-chain-phase",
31 | "label",
32 | "language",
33 | "location",
34 | "malware",
35 | "malware-analysis",
36 | "marking-definition",
37 | "narrative",
38 | "note",
39 | "observed-data",
40 | "opinion",
41 | "report",
42 | "task",
43 | "x-opencti-task",
44 | "threat-actor",
45 | "tool",
46 | "vocabulary",
47 | "vulnerability",
48 | ]
49 |
50 | STIX_CYBER_OBSERVABLE_MAPPING = {
51 | "autonomous-system": "Autonomous-System",
52 | "directory": "Directory",
53 | "domain-name": "Domain-Name",
54 | "email-addr": "Email-Addr",
55 | "email-message": "Email-Message",
56 | "email-mime-part-type": "Email-Mime-Part-Type",
57 | "artifact": "Artifact",
58 | "file": "StixFile",
59 | "x509-certificate": "X509-Certificate",
60 | "ipv4-addr": "IPv4-Addr",
61 | "ipv6-addr": "IPv6-Addr",
62 | "mac-addr": "Mac-Addr",
63 | "mutex": "Mutex",
64 | "network-traffic": "Network-Traffic",
65 | "process": "Process",
66 | "software": "Software",
67 | "url": "Url",
68 | "user-account": "User-Account",
69 | "windows-registry-key": "Windows-Registry-Key",
70 | "windows-registry-value-type": "Windows-Registry-Value-Type",
71 | "hostname": "Hostname",
72 | "cryptographic-key": "Cryptographic-Key",
73 | "cryptocurrency-wallet": "Cryptocurrency-Wallet",
74 | "text": "Text",
75 | "user-agent": "User-Agent",
76 | "bank-account": "Bank-Account",
77 | "phone-number": "Phone-Number",
78 | "credential": "Credential",
79 | "tracking-number": "Tracking-Number",
80 | "payment-card": "Payment-Card",
81 | "media-content": "Media-Content",
82 | "simple-observable": "Simple-Observable",
83 | "persona": "Persona",
84 | }
85 |
86 | PATTERN_MAPPING = {
87 | "Autonomous-System": ["number"],
88 | "Directory": ["path"],
89 | "Domain-Name": ["value"],
90 | "Email-Addr": ["value"],
91 | "File_md5": ["hashes", "MD5"],
92 | "File_sha1": ["hashes", "SHA-1"],
93 | "File_sha256": ["hashes", "SHA-256"],
94 | "File_sha512": ["hashes", "SHA-512"],
95 | "Email-Message_Body": ["body"],
96 | "Email-Message_Subject": ["subject"],
97 | "Email-Mime-Part-Type": ["body"],
98 | "IPv4-Addr": ["value"],
99 | "IPv6-Addr": ["value"],
100 | "Mac-Addr": ["value"],
101 | "Mutex": ["name"],
102 | "Network-Traffic": ["dst_port"],
103 | "Process": ["pid"],
104 | "Software": ["name"],
105 | "Url": ["value"],
106 | "User-Account": ["acount_login"],
107 | "Windows-Registry-Key": ["key"],
108 | "Windows-Registry-Value-Type": ["name"],
109 | "Hostname": ["value"],
110 | "Bank-Account": ["iban"],
111 | "Phone-Number": ["value"],
112 | "Payment-Card": ["card_number"],
113 | "Tracking-Number": ["value"],
114 | "Credential": ["value"],
115 | "Media-Content": ["url"],
116 | }
117 |
118 | OBSERVABLES_VALUE_INT = [
119 | "Autonomous-System.number",
120 | "Network-Traffic.dst_port",
121 | "Process.pid",
122 | ]
123 |
124 |
125 | class OpenCTIStix2Utils:
126 | @staticmethod
127 | def stix_observable_opencti_type(observable_type):
128 | if observable_type in STIX_CYBER_OBSERVABLE_MAPPING:
129 | return STIX_CYBER_OBSERVABLE_MAPPING[observable_type]
130 | else:
131 | return "Unknown"
132 |
133 | @staticmethod
134 | def create_stix_pattern(observable_type, observable_value):
135 | if observable_type in PATTERN_MAPPING:
136 | lhs = ObjectPath(
137 | (
138 | observable_type.lower()
139 | if "_" not in observable_type
140 | else observable_type.split("_")[0].lower()
141 | ),
142 | PATTERN_MAPPING[observable_type],
143 | )
144 | ece = ObservationExpression(
145 | EqualityComparisonExpression(lhs, observable_value)
146 | )
147 | return str(ece)
148 | else:
149 | return None
150 |
151 | """Generate random stix id (uuid v1)
152 | This id will stored and resolved by openCTI
153 | We will stored only 5 stix of this type to prevent database flooding
154 | :param stix_type: the stix type
155 | """
156 |
157 | @staticmethod
158 | def generate_random_stix_id(stix_type):
159 | raise ValueError(
160 | "This function should not be used anymore, please use the generate_id function for SDO or proper SCO constructor"
161 | )
162 |
163 | @staticmethod
164 | def retrieveClassForMethod(
165 | openCTIApiClient, entity: Dict, type_path: str, method: str
166 | ) -> Any:
167 | if entity is not None and type_path in entity:
168 | attributeName = entity[type_path].lower().replace("-", "_")
169 | if hasattr(openCTIApiClient, attributeName):
170 | attribute = getattr(openCTIApiClient, attributeName)
171 | if hasattr(attribute, method):
172 | return attribute
173 | return None
174 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "wheel"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [tool.isort]
6 | profile = "black"
7 |
8 | [tool.black]
9 | target-version = ["py37"]
10 |
11 | [tool.mypy]
12 | check_untyped_defs = true
13 | disallow_any_generics = true
14 | disallow_incomplete_defs = true
15 | disallow_subclassing_any = true
16 | disallow_untyped_calls = true
17 | disallow_untyped_decorators = true
18 | disallow_untyped_defs = true
19 | no_implicit_optional = true
20 | no_implicit_reexport = true
21 | show_error_context = true
22 | strict_equality = true
23 | warn_redundant_casts = true
24 | warn_return_any = true
25 | warn_unused_configs = true
26 | warn_unused_ignores = true
27 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended"
5 | ],
6 | "labels": [
7 | "dependencies",
8 | "filigran team"
9 | ],
10 | "prConcurrentLimit": 2,
11 | "packageRules": [
12 | {
13 | "matchUpdateTypes": [
14 | "minor"
15 | ],
16 | "prPriority": 5
17 | }
18 | ],
19 | "timezone": "Europe/Paris",
20 | "schedule": [
21 | "after 10pm every weekday",
22 | "every weekend",
23 | "before 5am every weekday"
24 | ],
25 | "updateNotScheduled": false
26 | }
27 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # Filigran
2 | datefinder~=0.7.3
3 | pika~=1.3.0
4 | python-magic~=0.4.27; sys_platform == 'linux' or sys_platform == 'darwin'
5 | python-magic-bin~=0.4.14; sys_platform == 'win32'
6 | python_json_logger~=3.3.0
7 | PyYAML~=6.0
8 | pydantic~=2.11.3
9 | requests~=2.32.3
10 | setuptools~=78.1.0
11 | cachetools~=5.5.0
12 | prometheus-client~=0.21.1
13 | opentelemetry-api~=1.32.0
14 | opentelemetry-sdk~=1.32.0
15 | deprecation~=2.1.0
16 | fastapi>=0.115.8,<0.116.0
17 | uvicorn[standard]>=0.33.0,<0.35.0
18 | # OpenCTI
19 | filigran-sseclient>=1.0.2
20 | stix2~=3.0.1
--------------------------------------------------------------------------------
/scripts/clone-opencti.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [[ -z "$1" ]] || [[ -z "$2" ]] || [[ -z "$3" ]]
4 | then
5 | echo "[CLONE-DEPS] This scripts $0 requires 3 parameters: branch_name:$1, pr_target_branch: $2, workspace:$3 (optional: PR_number:$4)"
6 | exit 0
7 | fi
8 |
9 | PR_BRANCH_NAME=$1
10 | PR_TARGET_BRANCH=$2
11 | WORKSPACE=$3
12 | PR_NUMBER=$4
13 |
14 | OPENCTI_DIR="${WORKSPACE}/opencti"
15 |
16 | clone_for_pr_build() {
17 | cd ${WORKSPACE}
18 | export GH_TOKEN="${GITHUB_TOKEN}"
19 |
20 | gh auth login --hostname github.com --with-token ${GH_TOKEN}
21 | gh auth status
22 | gh repo set-default https://github.com/OpenCTI-Platform/client-python
23 |
24 | #Check current PR to see if label "multi-repository" is set
25 | IS_MULTI_REPO=$(gh pr view ${PR_NUMBER} --json labels | grep -c "multi-repository")
26 | if [[ ${IS_MULTI_REPO} -eq 1 ]]
27 | then
28 |
29 | OPENCTI_BRANCH=${PR_BRANCH_NAME}
30 | echo "[CLONE-DEPS] Multi repository PR, looking for opencti related branch"
31 | if [[ $(echo ${PR_BRANCH_NAME} | cut -d "/" -f 1) == "opencti" ]]
32 | then
33 | #remove opencti prefix when present
34 | OPENCTI_BRANCH=$(echo ${PR_BRANCH_NAME} | cut -d "/" -f2-)
35 | fi
36 | echo "[CLONE-DEPS] OPENCTI_BRANCH is ${OPENCTI_BRANCH}, target branch is ${PR_TARGET_BRANCH}"
37 | gh repo clone https://github.com/OpenCTI-Platform/opencti ${OPENCTI_DIR} -- --branch ${PR_TARGET_BRANCH} --depth=1
38 | cd ${OPENCTI_DIR}
39 |
40 | # search for the first opencti PR that matches OPENCTI_BRANCH
41 | gh repo set-default https://github.com/OpenCTI-Platform/opencti
42 | gh pr list --label "multi-repository" > multi-repo-prs.txt
43 |
44 | cat multi-repo-prs.txt
45 |
46 | OPENCTI_PR_NUMBER=$(cat multi-repo-prs.txt | grep "${OPENCTI_BRANCH}" | head -n 1 | sed 's/#//g' | awk '{print $1}')
47 | echo "OPENCTI_PR_NUMBER=${OPENCTI_PR_NUMBER}"
48 |
49 | if [[ "${OPENCTI_PR_NUMBER}" != "" ]]
50 | then
51 | echo "[CLONE-DEPS] Found a PR in opencti with number ${OPENCTI_PR_NUMBER}, using it."
52 | gh pr checkout ${OPENCTI_PR_NUMBER}
53 | else
54 | echo "[CLONE-DEPS] No PR found in opencti side, keeping opencti:${PR_TARGET_BRANCH}"
55 | # Repository already clone on PR target branch
56 | fi
57 |
58 | else
59 | echo "[CLONE-DEPS] NOT multi repo, cloning opencti:${PR_TARGET_BRANCH}"
60 | gh repo clone https://github.com/OpenCTI-Platform/opencti ${OPENCTI_DIR} -- --branch ${PR_TARGET_BRANCH} --depth=1
61 | fi
62 | }
63 |
64 | clone_for_push_build() {
65 | echo "[CLONE-DEPS] Build from a commit, checking if a dedicated branch is required."
66 | BRANCH_PREFIX=$(echo $PR_BRANCH_NAME | cut -d "/" -f 1 | grep -c "opencti")
67 | if [[ "${BRANCH_PREFIX}" -eq "1" ]]
68 | then
69 | echo "[CLONE-DEPS] Dedicated OpenCTI branch found, using it"
70 | OPENCTI_BRANCH=$(echo $PR_BRANCH_NAME | cut -d "/" -f2-)
71 | git clone -b $OPENCTI_BRANCH https://github.com/OpenCTI-Platform/opencti.git
72 | else
73 | echo "[CLONE-DEPS] No dedicated OpenCTI branch found, using master"
74 | git clone https://github.com/OpenCTI-Platform/opencti.git
75 | fi
76 | }
77 |
78 | echo "[CLONE-DEPS] START; with PR_BRANCH_NAME=${PR_BRANCH_NAME},PR_TARGET_BRANCH=${PR_TARGET_BRANCH}, PR_NUMBER=${PR_NUMBER}, OPENCTI_DIR=${OPENCTI_DIR}."
79 | if [[ -z ${PR_NUMBER} ]] || [[ ${PR_NUMBER} == "" ]]
80 | then
81 | # No PR number from Drone = "Push build". And it's only for repository branch (not fork)
82 | # Using github cli to get PR number anyway
83 | PR_NUMBER=$(gh pr view ${PR_BRANCH_NAME} --json number --jq '.number')
84 | PR_TARGET_BRANCH=$(gh pr view ${PR_BRANCH_NAME} --json baseRefName --jq '.baseRefName')
85 |
86 | if [[ -z ${PR_NUMBER} ]] || [[ ${PR_NUMBER} == "" ]]
87 | then
88 | echo "[CLONE-DEPS] PR is not created on github yet, using clone without PR number and default fallback to master"
89 | clone_for_push_build
90 | else
91 | echo "[CLONE-DEPS] Got data from github cli, continue with: PR_TARGET_BRANCH=${PR_TARGET_BRANCH}, PR_NUMBER=${PR_NUMBER}."
92 | clone_for_pr_build
93 | fi
94 | else
95 | # PR build is trigger from Pull Request coming both from branch and forks.
96 | # We need to have this clone accross repository that works for forks (community PR)
97 | echo "[CLONE-DEPS] Got PR number ${PR_NUMBER} from Drone = "PR build"; Pull Request coming both from branch and forks."
98 | clone_for_pr_build
99 | fi
100 |
101 | cd ${OPENCTI_DIR}
102 | echo "[CLONE-DEPS] END; Using opencti on branch:$(git branch --show-current)"
103 |
104 | cd ${WORKSPACE}
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = pycti
3 | version = attr: pycti.__version__
4 | author = Filigran
5 | author_email = contact@filigran.io
6 | maintainer = Filigran
7 | url = https://github.com/OpenCTI-Platform/client-python
8 | description = Python API client for OpenCTI.
9 | long_description = file: README.md
10 | long_description_content_type = text/markdown
11 | license = Apache
12 | classifiers =
13 | Development Status :: 5 - Production/Stable
14 | Intended Audience :: Developers
15 | Intended Audience :: Information Technology
16 | Intended Audience :: Science/Research
17 | License :: OSI Approved :: Apache Software License
18 | Natural Language :: English
19 | Natural Language :: French
20 | Operating System :: OS Independent
21 | Programming Language :: Python :: 3
22 | Topic :: Security
23 | Topic :: Software Development :: Libraries :: Python Modules
24 |
25 | [options]
26 | python_requires = >=3.7
27 | packages =
28 | pycti
29 | pycti.api
30 | pycti.connector
31 | pycti.entities
32 | pycti.entities.indicator
33 | pycti.entities.stix_cyber_observable
34 | pycti.utils
35 | include_package_data = True
36 | install_requires =
37 | # Filigran
38 | datefinder~=0.7.3
39 | pika~=1.3.0
40 | pydantic~=2.11.3
41 | python-magic~=0.4.27; sys_platform == 'linux' or sys_platform == 'darwin'
42 | python-magic-bin~=0.4.14; sys_platform == 'win32'
43 | python_json_logger~=3.3.0
44 | PyYAML~=6.0
45 | requests~=2.32.3
46 | setuptools~=78.1.0
47 | cachetools~=5.5.0
48 | prometheus-client~=0.21.1
49 | opentelemetry-api~=1.32.0
50 | opentelemetry-sdk~=1.32.0
51 | deprecation~=2.1.0
52 | fastapi>=0.115.8,<0.116.0
53 | uvicorn[standard]>=0.33.0,<0.35.0
54 | # OpenCTI
55 | filigran-sseclient>=1.0.2
56 | stix2~=3.0.1
57 |
58 | [options.extras_require]
59 | dev =
60 | black~=25.1.0
61 | build~=1.2.1
62 | isort~=6.0.0
63 | types-pytz~=2025.2.0.20250326
64 | pre-commit~=4.2.0
65 | pytest-cases~=3.8.0
66 | pytest-cov~=6.1.1
67 | pytest_randomly~=3.16.0
68 | pytest~=8.3.4
69 | types-python-dateutil~=2.9.0
70 | wheel~=0.45.1
71 | doc =
72 | autoapi~=2.0.1
73 | sphinx-autodoc-typehints~=3.2.0
74 | sphinx-rtd-theme~=3.0.2
75 |
--------------------------------------------------------------------------------
/test-requirements.txt:
--------------------------------------------------------------------------------
1 | black==25.1.0
2 | build>=0.7
3 | isort>=5.10
4 | pre-commit~=4.2
5 | pytest~=8.1
6 | pytest-cases~=3.6
7 | pytest-cov~=6.1
8 | pytest_randomly~=3.8
9 | types-python-dateutil>=2.8
10 | types-pytz>=2021.3.5
11 | wheel~=0.38
12 |
--------------------------------------------------------------------------------
/test.json:
--------------------------------------------------------------------------------
1 | {"type": "bundle",
2 | "objects": [{
3 | "id": "software--290d2a0e-f613-5628-a540-e66962f749bd",
4 | "type": "software",
5 | "spec_version": "2.1",
6 | "cpe": "cpe:2.3:a:oracle:graalvm:17.0.14:*:*:*:*:*:*:*",
7 | "name": "graalvm 17.0.14",
8 | "version": "17.0.14",
9 | "vendor": "oracle",
10 | "x_version": "17.0.14",
11 | "x_vendor": "oracle",
12 | "x_product": "graalvm",
13 | "created_by_ref": "identity--be669b67-e9bb-5a5d-900b-c56f5b2a69f7",
14 | "object_marking_refs": [
15 | "marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da"
16 | ]
17 | },
18 | {
19 | "type": "threat-actor",
20 | "id": "threat-actor--4b57918e-f774-58bb-bcbe-31c3c483daed",
21 | "name": "Turla",
22 | "secondary_motivations": ["dominance"]
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/tests/01-unit/metric/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/tests/01-unit/metric/__init__.py
--------------------------------------------------------------------------------
/tests/01-unit/metric/test_opencti_metric_handler.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | from prometheus_client import Counter, Enum
4 |
5 | from pycti import OpenCTIMetricHandler
6 | from pycti.utils.opencti_logger import logger
7 |
8 |
9 | class TestOpenCTIMetricHandler(TestCase):
10 | def test_metric_exists(self):
11 | test_logger = logger("INFO")("test")
12 | metric = OpenCTIMetricHandler(test_logger, activated=True)
13 | self.assertTrue(metric._metric_exists("error_count", Counter))
14 | self.assertFalse(metric._metric_exists("error_count", Enum))
15 | self.assertFalse(metric._metric_exists("best_metric_count", Counter))
16 |
--------------------------------------------------------------------------------
/tests/01-unit/stix/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/tests/01-unit/stix/__init__.py
--------------------------------------------------------------------------------
/tests/01-unit/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/tests/01-unit/utils/__init__.py
--------------------------------------------------------------------------------
/tests/01-unit/utils/test_constants.py:
--------------------------------------------------------------------------------
1 | from pycti.utils.constants import (
2 | ContainerTypes,
3 | IdentityTypes,
4 | LocationTypes,
5 | StixCyberObservableTypes,
6 | )
7 |
8 |
9 | def test_for_enum_typos():
10 | # Test check for typos between enum names and values
11 | enums = [StixCyberObservableTypes, LocationTypes, IdentityTypes, ContainerTypes]
12 | for enum in enums:
13 | for data in enum:
14 | name = data.name.replace("_", "-")
15 | value = data.value.upper()
16 | assert name == value
17 |
18 |
19 | def test_for_enum_has_value_functionality():
20 | # Test if the has_value function works as intended
21 | assert StixCyberObservableTypes.has_value("url") is True
22 | assert StixCyberObservableTypes.has_value("LRU") is False
23 |
24 | assert LocationTypes.has_value("CITY") is True
25 | assert LocationTypes.has_value("YTIC") is False
26 |
27 | assert IdentityTypes.has_value("SECTOR") is True
28 | assert IdentityTypes.has_value("RECTOS") is False
29 |
30 | assert ContainerTypes.has_value("Note") is True
31 | assert ContainerTypes.has_value("ETON") is False
32 |
--------------------------------------------------------------------------------
/tests/01-unit/utils/test_opencti_stix2.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | import pytest
4 |
5 | from pycti.utils.opencti_stix2 import OpenCTIStix2
6 |
7 |
8 | @pytest.fixture
9 | def opencti_stix2(api_client):
10 | return OpenCTIStix2(api_client)
11 |
12 |
13 | def test_unknown_type(opencti_stix2: OpenCTIStix2, caplog):
14 | opencti_stix2.unknown_type({"type": "foo"})
15 | for record in caplog.records:
16 | assert record.levelname == "ERROR"
17 | assert "Unknown object type, doing nothing..." in caplog.text
18 |
19 |
20 | def test_convert_markdown(opencti_stix2: OpenCTIStix2):
21 | result = opencti_stix2.convert_markdown(
22 | " my is very
to me"
23 | )
24 | assert " my ` is very ` to me" == result
25 |
26 |
27 | def test_convert_markdown_typo(opencti_stix2: OpenCTIStix2):
28 | result = opencti_stix2.convert_markdown(
29 | " my
to me"
30 | )
31 | assert " my ` to me" == result
32 |
33 |
34 | def test_format_date_with_tz(opencti_stix2: OpenCTIStix2):
35 | # Test all 4 format_date cases with timestamp + timezone
36 | my_datetime = datetime.datetime(
37 | 2021, 3, 5, 13, 31, 19, 42621, tzinfo=datetime.timezone.utc
38 | )
39 | my_datetime_str = my_datetime.isoformat(timespec="milliseconds").replace(
40 | "+00:00", "Z"
41 | )
42 | assert my_datetime_str == opencti_stix2.format_date(my_datetime)
43 | my_date = my_datetime.date()
44 | my_date_str = "2021-03-05T00:00:00.000Z"
45 | assert my_date_str == opencti_stix2.format_date(my_date)
46 | assert my_datetime_str == opencti_stix2.format_date(my_datetime_str)
47 | assert (
48 | str(
49 | datetime.datetime.now(tz=datetime.timezone.utc)
50 | .isoformat(timespec="seconds")
51 | .replace("+00:00", "")
52 | )
53 | in opencti_stix2.format_date()
54 | )
55 | with pytest.raises(ValueError):
56 | opencti_stix2.format_date("No time")
57 |
58 | # Test all 4 format_date cases with timestamp w/o timezone
59 | my_datetime = datetime.datetime(2021, 3, 5, 13, 31, 19, 42621)
60 | my_datetime_str = (
61 | my_datetime.replace(tzinfo=datetime.timezone.utc)
62 | .isoformat(timespec="milliseconds")
63 | .replace("+00:00", "Z")
64 | )
65 | assert my_datetime_str == opencti_stix2.format_date(my_datetime)
66 | my_date = my_datetime.date()
67 | my_date_str = "2021-03-05T00:00:00.000Z"
68 | assert my_date_str == opencti_stix2.format_date(my_date)
69 | assert my_datetime_str == opencti_stix2.format_date(my_datetime_str)
70 | assert (
71 | str(
72 | datetime.datetime.now(tz=datetime.timezone.utc)
73 | .isoformat(timespec="seconds")
74 | .replace("+00:00", "")
75 | )
76 | in opencti_stix2.format_date()
77 | )
78 | with pytest.raises(ValueError):
79 | opencti_stix2.format_date("No time")
80 |
81 |
82 | def test_filter_objects(opencti_stix2: OpenCTIStix2):
83 | objects = [{"id": "123"}, {"id": "124"}, {"id": "125"}, {"id": "126"}]
84 | result = opencti_stix2.filter_objects(["123", "124", "126"], objects)
85 | assert len(result) == 1
86 | assert "126" not in result
87 |
88 |
89 | def test_pick_aliases(opencti_stix2: OpenCTIStix2) -> None:
90 | stix_object = {}
91 | assert opencti_stix2.pick_aliases(stix_object) is None
92 | stix_object["aliases"] = "alias"
93 | assert opencti_stix2.pick_aliases(stix_object) == "alias"
94 | stix_object["x_amitt_aliases"] = "amitt_alias"
95 | assert opencti_stix2.pick_aliases(stix_object) == "amitt_alias"
96 | stix_object["x_mitre_aliases"] = "mitre_alias"
97 | assert opencti_stix2.pick_aliases(stix_object) == "mitre_alias"
98 | stix_object["x_opencti_aliases"] = "opencti_alias"
99 | assert opencti_stix2.pick_aliases(stix_object) == "opencti_alias"
100 |
101 |
102 | def test_import_bundle_from_file(opencti_stix2: OpenCTIStix2, caplog) -> None:
103 | opencti_stix2.import_bundle_from_file("foo.txt")
104 | for record in caplog.records:
105 | assert record.levelname == "ERROR"
106 | assert "The bundle file does not exists" in caplog.text
107 |
--------------------------------------------------------------------------------
/tests/01-unit/utils/test_opencti_stix2_splitter.py:
--------------------------------------------------------------------------------
1 | import json
2 | import uuid
3 |
4 | from stix2 import Report
5 |
6 | from pycti.utils.opencti_stix2_splitter import OpenCTIStix2Splitter
7 |
8 |
9 | def test_split_bundle():
10 | stix_splitter = OpenCTIStix2Splitter()
11 | with open("./tests/data/enterprise-attack.json") as file:
12 | content = file.read()
13 | expectations, bundles = stix_splitter.split_bundle_with_expectations(content)
14 | assert expectations == 7016
15 |
16 |
17 | def test_split_test_bundle():
18 | stix_splitter = OpenCTIStix2Splitter()
19 | with open("./tests/data/DATA-TEST-STIX2_v2.json") as file:
20 | content = file.read()
21 | expectations, bundles = stix_splitter.split_bundle_with_expectations(content)
22 | assert expectations == 59
23 | base_bundles = json.loads(content)["objects"]
24 | for base in base_bundles:
25 | found = None
26 | for bundle in bundles:
27 | json_bundle = json.loads(bundle)
28 | object_json = json_bundle["objects"][0]
29 | if object_json["id"] == base["id"]:
30 | found = object_json
31 | break
32 | assert found is not None, "Every object of the bundle must be available"
33 | del found["nb_deps"]
34 | assert json.dumps(base) == json.dumps(
35 | found
36 | ), "Splitter must not have change the content"
37 |
38 |
39 | def test_split_mono_entity_bundle():
40 | stix_splitter = OpenCTIStix2Splitter()
41 | with open("./tests/data/mono-bundle-entity.json") as file:
42 | content = file.read()
43 | expectations, bundles = stix_splitter.split_bundle_with_expectations(content)
44 | assert expectations == 1
45 | json_bundle = json.loads(bundles[0])["objects"][0]
46 | assert json_bundle["created_by_ref"] == "fa42a846-8d90-4e51-bc29-71d5b4802168"
47 | # Split with cleanup_inconsistent_bundle
48 | stix_splitter = OpenCTIStix2Splitter()
49 | expectations, bundles = stix_splitter.split_bundle_with_expectations(
50 | bundle=content, cleanup_inconsistent_bundle=True
51 | )
52 | assert expectations == 1
53 | json_bundle = json.loads(bundles[0])["objects"][0]
54 | assert json_bundle["created_by_ref"] is None
55 |
56 |
57 | def test_split_mono_relationship_bundle():
58 | stix_splitter = OpenCTIStix2Splitter()
59 | with open("./tests/data/mono-bundle-relationship.json") as file:
60 | content = file.read()
61 | expectations, bundles = stix_splitter.split_bundle_with_expectations(content)
62 | assert expectations == 1
63 | # Split with cleanup_inconsistent_bundle
64 | stix_splitter = OpenCTIStix2Splitter()
65 | expectations, bundles = stix_splitter.split_bundle_with_expectations(
66 | bundle=content, cleanup_inconsistent_bundle=True
67 | )
68 | assert expectations == 0
69 |
70 |
71 | def test_split_capec_bundle():
72 | stix_splitter = OpenCTIStix2Splitter()
73 | with open("./tests/data/mitre_att_capec.json") as file:
74 | content = file.read()
75 | expectations, bundles = stix_splitter.split_bundle_with_expectations(content)
76 | assert expectations == 2610
77 |
78 |
79 | def test_split_internal_ids_bundle():
80 | stix_splitter = OpenCTIStix2Splitter()
81 | with open("./tests/data/bundle_with_internal_ids.json") as file:
82 | content = file.read()
83 | expectations, bundles = stix_splitter.split_bundle_with_expectations(content)
84 | assert expectations == 4
85 | # Split with cleanup_inconsistent_bundle
86 | stix_splitter = OpenCTIStix2Splitter()
87 | expectations, bundles = stix_splitter.split_bundle_with_expectations(
88 | bundle=content, cleanup_inconsistent_bundle=True
89 | )
90 | assert expectations == 4
91 | for bundle in bundles:
92 | json_bundle = json.loads(bundle)
93 | object_json = json_bundle["objects"][0]
94 | if object_json["id"] == "relationship--10e8c71d-a1b4-4e35-bca8-2e4a3785ea04":
95 | assert (
96 | object_json["created_by_ref"] == "ced3e53e-9663-4c96-9c60-07d2e778d931"
97 | )
98 |
99 |
100 | def test_split_missing_refs_bundle():
101 | stix_splitter = OpenCTIStix2Splitter()
102 | with open("./tests/data/missing_refs.json") as file:
103 | content = file.read()
104 | expectations, bundles = stix_splitter.split_bundle_with_expectations(content)
105 | assert expectations == 4
106 | # Split with cleanup_inconsistent_bundle
107 | stix_splitter = OpenCTIStix2Splitter()
108 | expectations, bundles = stix_splitter.split_bundle_with_expectations(
109 | bundle=content, cleanup_inconsistent_bundle=True
110 | )
111 | assert expectations == 3
112 |
113 |
114 | def test_split_cyclic_bundle():
115 | stix_splitter = OpenCTIStix2Splitter()
116 | with open("./tests/data/cyclic-bundle.json") as file:
117 | content = file.read()
118 | expectations, bundles = stix_splitter.split_bundle_with_expectations(content)
119 | assert expectations == 6
120 | for bundle in bundles:
121 | json_bundle = json.loads(bundle)
122 | object_json = json_bundle["objects"][0]
123 | if object_json["id"] == "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd7":
124 | assert (
125 | len(object_json["external_references"]) == 1
126 | ) # References are duplicated
127 | assert len(object_json["object_refs"]) == 2 # Cleaned cyclic refs
128 | assert len(object_json["object_marking_refs"]) == 1
129 | assert (
130 | object_json["object_marking_refs"][0]
131 | == "marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"
132 | )
133 |
134 |
135 | def test_create_bundle():
136 | stix_splitter = OpenCTIStix2Splitter()
137 | report = Report(
138 | report_types=["campaign"],
139 | name="Bad Cybercrime",
140 | published="2016-04-06T20:03:00.000Z",
141 | object_refs=["indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7"],
142 | ).serialize()
143 | observables = [report]
144 |
145 | bundle = stix_splitter.stix2_create_bundle(
146 | "bundle--" + str(uuid.uuid4()),
147 | 0,
148 | observables,
149 | use_json=False,
150 | event_version=None,
151 | )
152 |
153 | for key in ["type", "id", "spec_version", "objects", "x_opencti_seq"]:
154 | assert key in bundle
155 | assert len(bundle.keys()) == 5
156 |
157 | bundle = stix_splitter.stix2_create_bundle(
158 | "bundle--" + str(uuid.uuid4()), 0, observables, use_json=False, event_version=1
159 | )
160 | for key in [
161 | "type",
162 | "id",
163 | "spec_version",
164 | "objects",
165 | "x_opencti_event_version",
166 | "x_opencti_seq",
167 | ]:
168 | assert key in bundle
169 | assert len(bundle.keys()) == 6
170 |
--------------------------------------------------------------------------------
/tests/02-integration/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/tests/02-integration/__init__.py
--------------------------------------------------------------------------------
/tests/02-integration/connector/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/tests/02-integration/connector/__init__.py
--------------------------------------------------------------------------------
/tests/02-integration/entities/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/tests/02-integration/entities/__init__.py
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_entity_crud.py:
--------------------------------------------------------------------------------
1 | from pytest_cases import fixture, parametrize_with_cases
2 |
3 | from tests.cases.entities import EntityTestCases
4 | from tests.utils import compare_values, is_filters_empty
5 |
6 |
7 | @fixture
8 | @parametrize_with_cases("entity", cases=EntityTestCases)
9 | def entity_class(entity):
10 | entity.setup()
11 | yield entity
12 | entity.teardown()
13 |
14 |
15 | def test_entity_create(entity_class):
16 | class_data = entity_class.data()
17 | test_indicator = entity_class.own_class().create(**class_data)
18 | try:
19 | assert test_indicator is not None, "Response is NoneType"
20 | assert "id" in test_indicator, "No ID on object"
21 | finally:
22 | if test_indicator and "id" in test_indicator:
23 | entity_class.base_class().delete(id=test_indicator["id"])
24 |
25 |
26 | def test_read(entity_class):
27 | class_data = entity_class.data()
28 | test_indicator = entity_class.own_class().create(**class_data)
29 | try:
30 | assert test_indicator is not None, "Response is NoneType"
31 | assert "id" in test_indicator, "No ID on object"
32 | assert "standard_id" in test_indicator, "No standard_id (STIX ID) on object"
33 | test_indicator = entity_class.own_class().read(id=test_indicator["id"])
34 | compare_values(
35 | class_data,
36 | test_indicator,
37 | entity_class.get_compare_exception_keys(),
38 | )
39 |
40 | finally:
41 | if test_indicator and "id" in test_indicator:
42 | entity_class.base_class().delete(id=test_indicator["id"])
43 |
44 |
45 | def test_update(entity_class):
46 | class_data = entity_class.data()
47 | test_indicator = entity_class.own_class().create(**class_data)
48 | try:
49 | assert test_indicator is not None, "Response is NoneType"
50 | assert "id" in test_indicator, "No ID on object"
51 |
52 | if len(entity_class.update_data()) > 0:
53 | function_present = getattr(entity_class.own_class(), "update_field", None)
54 | if function_present:
55 | for update_field, update_value in entity_class.update_data().items():
56 | class_data[update_field] = update_value
57 | input = [{"key": update_field, "value": update_value}]
58 | result = entity_class.own_class().update_field(
59 | id=test_indicator["id"], input=input
60 | )
61 | else:
62 | for update_field, update_value in entity_class.update_data().items():
63 | class_data[update_field] = update_value
64 | class_data["update"] = True
65 | result = entity_class.own_class().create(**class_data)
66 |
67 | result = entity_class.own_class().read(id=result["id"])
68 | assert (
69 | result["id"] == test_indicator["id"]
70 | ), "Updated SDO does not match old ID"
71 | compare_values(
72 | class_data, result, entity_class.get_compare_exception_keys()
73 | )
74 | else:
75 | result = test_indicator
76 |
77 | finally:
78 | if test_indicator and "id" in test_indicator:
79 | entity_class.base_class().delete(id=result["id"])
80 |
81 |
82 | def test_delete(entity_class):
83 | class_data = entity_class.data()
84 | test_indicator = entity_class.own_class().create(**class_data)
85 | try:
86 | assert test_indicator is not None, "Response is NoneType"
87 | assert "id" in test_indicator, "No ID on object"
88 | result = entity_class.base_class().delete(id=test_indicator["id"])
89 | assert result is None, f"Delete returned value '{result}'"
90 | result = entity_class.own_class().read(id=test_indicator["id"])
91 | assert result is None, f"Read returned value '{result}' after delete"
92 | except AssertionError:
93 | if test_indicator and "id" in test_indicator:
94 | entity_class.base_class().delete(id=test_indicator["id"])
95 |
96 |
97 | def test_filter(entity_class):
98 | if is_filters_empty(entity_class.get_filter()):
99 | return
100 |
101 | class_data = entity_class.data()
102 | test_indicator = entity_class.own_class().create(**class_data)
103 | try:
104 | assert test_indicator is not None, "Response is NoneType"
105 | assert "id" in test_indicator, "No ID on object"
106 | test_indicator = entity_class.own_class().read(
107 | filters=entity_class.get_filter()
108 | )
109 | compare_values(
110 | class_data,
111 | test_indicator,
112 | entity_class.get_compare_exception_keys(),
113 | )
114 | finally:
115 | if test_indicator and "id" in test_indicator:
116 | entity_class.base_class().delete(id=test_indicator["id"])
117 |
118 |
119 | def test_search(entity_class):
120 | if not entity_class.get_search():
121 | return
122 |
123 | class_data = entity_class.data()
124 | test_indicator = entity_class.own_class().create(**class_data)
125 | try:
126 | assert test_indicator is not None, "Response is NoneType"
127 | assert "id" in test_indicator, "No ID on object"
128 | test_indicator = entity_class.own_class().read(search=entity_class.get_search())
129 | compare_values(
130 | class_data,
131 | test_indicator,
132 | entity_class.get_compare_exception_keys(),
133 | )
134 | finally:
135 | if test_indicator and "id" in test_indicator:
136 | entity_class.base_class().delete(id=test_indicator["id"])
137 |
138 |
139 | def test_relation(entity_class):
140 | if not entity_class.relation_test():
141 | return
142 | class_data = entity_class.data()
143 | class_data2 = entity_class.relation_test()
144 | test_indicator = entity_class.own_class().create(**class_data)
145 | test_indicator2 = entity_class.own_class().create(**class_data2)
146 | try:
147 | assert test_indicator is not None, "Response is NoneType"
148 | assert "id" in test_indicator, "No ID on object"
149 | entity_class.own_class().add_stix_object_or_stix_relationship(
150 | id=test_indicator["id"],
151 | stixObjectOrStixRelationshipId=test_indicator2["id"],
152 | )
153 | result = entity_class.own_class().read(id=test_indicator["id"])
154 | assert result["objectsIds"][0] == test_indicator2["id"]
155 | entity_class.own_class().remove_stix_object_or_stix_relationship(
156 | id=test_indicator["id"],
157 | stixObjectOrStixRelationshipId=test_indicator2["id"],
158 | )
159 | result = entity_class.own_class().read(id=test_indicator["id"])
160 | assert len(result["objectsIds"]) == 0
161 | finally:
162 | if test_indicator and "id" in test_indicator:
163 | entity_class.base_class().delete(id=test_indicator["id"])
164 | if test_indicator2 and "id" in test_indicator2:
165 | entity_class.base_class().delete(id=test_indicator2["id"])
166 |
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_entity_filter.py:
--------------------------------------------------------------------------------
1 | from pytest_cases import fixture, parametrize_with_cases
2 |
3 | from tests.cases.entities import EntityTestCases
4 | from tests.utils import compare_values, is_filters_empty
5 |
6 |
7 | @fixture
8 | @parametrize_with_cases("entity", cases=EntityTestCases)
9 | def entity_class(entity):
10 | entity.setup()
11 | yield entity
12 | entity.teardown()
13 |
14 |
15 | def test_filter(entity_class):
16 | if is_filters_empty(entity_class.get_filter()):
17 | return
18 |
19 | class_data = entity_class.data()
20 | test_indicator = entity_class.own_class().create(**class_data)
21 | assert test_indicator is not None, "Response is NoneType"
22 | assert "id" in test_indicator, "No ID on object"
23 | test_indicator = entity_class.own_class().read(filters=entity_class.get_filter())
24 | compare_values(
25 | class_data,
26 | test_indicator,
27 | entity_class.get_compare_exception_keys(),
28 | )
29 |
30 | entity_class.base_class().delete(id=test_indicator["id"])
31 |
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_group.py:
--------------------------------------------------------------------------------
1 | from tests.cases.entities import GroupTest, MarkingDefinitionTest, RoleTest, UserTest
2 |
3 |
4 | def test_group_roles(api_client):
5 | group_data = GroupTest(api_client).data()
6 | role_data = RoleTest(api_client).data()
7 | test_group = api_client.group.create(**group_data)
8 | test_role = api_client.role.create(**role_data)
9 | assert test_group is not None, "Create group response is NoneType"
10 | assert test_role is not None, "Create role response is NoneType"
11 |
12 | api_client.group.add_role(id=test_group["id"], role_id=test_role["id"])
13 | result = api_client.group.read(id=test_group["id"])
14 | assert result["rolesIds"][0] == test_role["id"]
15 |
16 | api_client.group.delete_role(id=test_group["id"], role_id=test_role["id"])
17 | result = api_client.group.read(id=test_group["id"])
18 | assert len(result["rolesIds"]) == 0
19 |
20 | api_client.group.delete(id=test_group["id"])
21 | api_client.role.delete(id=test_role["id"])
22 |
23 |
24 | def test_group_default_markings(api_client):
25 | group_data = GroupTest(api_client).data()
26 | marking_test = MarkingDefinitionTest(api_client)
27 | marking_data = marking_test.data()
28 | test_group = api_client.group.create(**group_data)
29 | test_marking = marking_test.own_class().create(**marking_data)
30 | assert test_group is not None, "Create group response is NoneType"
31 | assert test_marking is not None, "Create marking response is NoneType"
32 |
33 | api_client.group.edit_default_marking(
34 | id=test_group["id"], marking_ids=[test_marking["id"]]
35 | )
36 | result = api_client.group.read(id=test_group["id"])
37 | assert result["default_marking"][0]["values"][0]["id"] == test_marking["id"]
38 |
39 | api_client.group.edit_default_marking(id=test_group["id"], marking_ids=[])
40 | result = api_client.group.read(id=test_group["id"])
41 | assert len(result["default_marking"][0]["values"]) == 0
42 |
43 | api_client.group.delete(id=test_group["id"])
44 | marking_test.base_class().delete(id=test_marking["id"])
45 |
46 |
47 | def test_group_membership(api_client):
48 | group_test = GroupTest(api_client)
49 | user_test = UserTest(api_client)
50 |
51 | test_group = group_test.own_class().create(**group_test.data())
52 | test_user = user_test.own_class().create(**user_test.data())
53 |
54 | try:
55 | assert test_group is not None, "Create group response is NoneType"
56 | assert test_user is not None, "Create user response is NoneType"
57 |
58 | group_test.own_class().add_member(id=test_group["id"], user_id=test_user["id"])
59 | result = group_test.own_class().read(id=test_group["id"])
60 | assert result["membersIds"][0] == test_user["id"]
61 |
62 | group_test.own_class().delete_member(
63 | id=test_group["id"], user_id=test_user["id"]
64 | )
65 | result = group_test.own_class().read(id=test_group["id"])
66 | assert len(result["membersIds"]) == 0
67 | finally:
68 | group_test.base_class().delete(id=test_group["id"])
69 | user_test.base_class().delete(id=test_user["id"])
70 |
71 |
72 | def test_group_allowed_markings(api_client):
73 | group_data = GroupTest(api_client).data()
74 | marking_test = MarkingDefinitionTest(api_client)
75 | marking_data = marking_test.data()
76 | test_group = api_client.group.create(**group_data)
77 | test_marking = marking_test.own_class().create(**marking_data)
78 | assert test_group is not None, "Create group response is NoneType"
79 | assert test_marking is not None, "Create marking response is NoneType"
80 |
81 | api_client.group.add_allowed_marking(
82 | id=test_group["id"], marking_id=test_marking["id"]
83 | )
84 | result = api_client.group.read(id=test_group["id"])
85 | assert result["allowed_marking"][0]["id"] == test_marking["id"]
86 |
87 | api_client.group.delete_allowed_marking(
88 | id=test_group["id"], marking_id=test_marking["id"]
89 | )
90 | result = api_client.group.read(id=test_group["id"])
91 | assert len(result["allowed_marking"]) == 0
92 |
93 | api_client.group.delete(id=test_group["id"])
94 | marking_test.base_class().delete(id=test_marking["id"])
95 |
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_indicators.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | import json
4 |
5 | # Commented out since OpenCTI requires at least 100 indicators
6 | # for this test to work
7 | #
8 | # def test_get_100_indicators_with_pagination(api_client):
9 | # # Get 100 Indicators using the pagination
10 | # custom_attributes = """
11 | # id
12 | # revoked
13 | # created
14 | # """
15 | #
16 | # final_indicators = []
17 | # data = api_client.indicator.list(
18 | # first=50, customAttributes=custom_attributes, withPagination=True
19 | # )
20 | # final_indicators = final_indicators + data["entities"]
21 | #
22 | # assert len(final_indicators) == 50
23 | #
24 | # after = data["pagination"]["endCursor"]
25 | # data = api_client.indicator.list(
26 | # first=50,
27 | # after=after,
28 | # customAttributes=custom_attributes,
29 | # withPagination=True,
30 | # )
31 | # final_indicators = final_indicators + data["entities"]
32 | #
33 | # assert len(final_indicators) == 100
34 |
35 |
36 | def test_indicator_stix_marshall(api_client):
37 | with open("tests/data/indicator_stix.json", "r") as content_file:
38 | content = content_file.read()
39 |
40 | json_data = json.loads(content)
41 |
42 | for indic in json_data["objects"]:
43 | imported_indicator = api_client.indicator.import_from_stix2(
44 | stixObject=indic, update=True
45 | )
46 | assert imported_indicator is not None
47 |
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_malware.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 |
4 | def test_malware_import_with_sample_refs(api_client):
5 | with open("tests/data/basicMalwareWithSample.json", "r") as content_file:
6 | content = content_file.read()
7 |
8 | imported_malware_bundle = api_client.stix2.import_bundle_from_json(
9 | json_data=content
10 | )
11 | assert imported_malware_bundle is not None
12 |
13 | for importObject in imported_malware_bundle:
14 | if importObject["type"] == "malware":
15 | assert (
16 | api_client.malware.read(id=importObject["id"]).get("samples") is not []
17 | )
18 |
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_observables.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 |
4 | def test_promote_observable_to_indicator_deprecated(api_client):
5 | # deprecated [>=6.2 & <6.8]
6 | obs1 = api_client.stix_cyber_observable.create(
7 | simple_observable_key="IPv4-Addr.value", simple_observable_value="55.55.55.55"
8 | )
9 | observable = api_client.stix_cyber_observable.promote_to_indicator(
10 | id=obs1.get("id")
11 | )
12 | assert observable is not None, "Returned observable is NoneType"
13 | assert observable.get("id") == obs1.get("id")
14 |
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_role.py:
--------------------------------------------------------------------------------
1 | from tests.cases.entities import RoleTest
2 |
3 |
4 | def test_role_capabilities(api_client):
5 | role_test = RoleTest(api_client)
6 | role_data = role_test.data()
7 | test_role = role_test.own_class().create(**role_data)
8 | assert test_role is not None, "Create role response is NoneType"
9 |
10 | capability_id = api_client.capability.list()[0]["id"]
11 |
12 | role_test.own_class().add_capability(
13 | id=test_role["id"], capability_id=capability_id
14 | )
15 | result = role_test.own_class().read(id=test_role["id"])
16 | assert capability_id in result["capabilitiesIds"]
17 |
18 | role_test.own_class().delete_capability(
19 | id=test_role["id"], capability_id=capability_id
20 | )
21 | result = role_test.own_class().read(id=test_role["id"])
22 | assert capability_id not in result["capabilitiesIds"]
23 |
24 | role_test.base_class().delete(id=test_role["id"])
25 |
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_settings.py:
--------------------------------------------------------------------------------
1 | from tests.cases.entities import SettingsTest
2 |
3 |
4 | def test_settings_messages(api_client):
5 | settings_test = SettingsTest(api_client)
6 | settings_test.setup()
7 | try:
8 | result = settings_test.own_class().read(include_messages=True)
9 | id = result["id"]
10 | num_messages = len(result["platform_messages"])
11 |
12 | # Add message
13 | test_message_data = {
14 | "message": "This is a test message",
15 | "activated": True,
16 | "dismissible": True,
17 | }
18 | result = settings_test.own_class().edit_message(id=id, input=test_message_data)
19 | assert len(result["platform_messages"]) == num_messages + 1
20 |
21 | test_message = result["platform_messages"][-1]
22 | assert test_message["message"] == test_message_data["message"]
23 |
24 | # Update message
25 | result = settings_test.own_class().edit_message(
26 | id=id,
27 | input={
28 | "id": test_message["id"],
29 | "message": "This is an updated test message",
30 | "activated": True,
31 | "dismissible": False,
32 | },
33 | )
34 | assert len(result["platform_messages"]) == num_messages + 1
35 |
36 | updated_message = result["platform_messages"][-1]
37 | assert updated_message["id"] == test_message["id"]
38 | assert updated_message["message"] == "This is an updated test message"
39 |
40 | # Delete message
41 | result = settings_test.own_class().delete_message(
42 | id=id, input=test_message["id"]
43 | )
44 | assert len(result["platform_messages"]) == num_messages
45 | assert test_message["id"] not in result["platform_messages_ids"]
46 | finally:
47 | settings_test.teardown()
48 |
--------------------------------------------------------------------------------
/tests/02-integration/entities/test_user.py:
--------------------------------------------------------------------------------
1 | from tests.cases.entities import GroupTest, IdentityOrganizationTest, UserTest
2 |
3 |
4 | def test_user_membership(api_client):
5 | user_test = UserTest(api_client)
6 | group_test = GroupTest(api_client)
7 |
8 | test_user = user_test.own_class().create(**user_test.data())
9 | test_group = group_test.own_class().create(**group_test.data())
10 |
11 | try:
12 | assert test_user is not None, "User create response returned NoneType"
13 | assert test_group is not None, "Group create response returned NoneType"
14 |
15 | user_test.own_class().add_membership(
16 | id=test_user["id"], group_id=test_group["id"]
17 | )
18 | result = user_test.own_class().read(id=test_user["id"])
19 | assert test_group["id"] in result["groupsIds"]
20 |
21 | user_test.own_class().delete_membership(
22 | id=test_user["id"], group_id=test_group["id"]
23 | )
24 | result = user_test.own_class().read(id=test_user["id"])
25 | assert test_group["id"] not in result["groupsIds"]
26 | finally:
27 | user_test.base_class().delete(id=test_user["id"])
28 | group_test.base_class().delete(id=test_group["id"])
29 |
30 |
31 | def test_user_organization(api_client):
32 | user_test = UserTest(api_client)
33 | org_test = IdentityOrganizationTest(api_client)
34 |
35 | test_user = user_test.own_class().create(**user_test.data())
36 | test_org = org_test.own_class().create(**org_test.data())
37 |
38 | try:
39 | assert test_user is not None, "User create response returned NoneType"
40 | assert test_org is not None, "Organization create response returned NoneType"
41 |
42 | user_test.own_class().add_organization(
43 | id=test_user["id"], organization_id=test_org["id"]
44 | )
45 | result = user_test.own_class().read(id=test_user["id"])
46 | assert test_org["id"] in result["objectOrganizationIds"]
47 |
48 | user_test.own_class().delete_organization(
49 | id=test_user["id"], organization_id=test_org["id"]
50 | )
51 | result = user_test.own_class().read(id=test_user["id"])
52 | assert test_org["id"] not in result["objectOrganizationIds"]
53 | finally:
54 | user_test.base_class().delete(id=test_user["id"])
55 | org_test.base_class().delete(id=test_org["id"])
56 |
57 |
58 | def test_user_token_renew(api_client):
59 | user_test = UserTest(api_client)
60 | test_user = user_test.own_class().create(**user_test.data(), include_token=True)
61 | try:
62 | assert test_user is not None, "User create response returned NoneType"
63 |
64 | old_token = test_user["api_token"]
65 | result = user_test.own_class().token_renew(
66 | id=test_user["id"], include_token=True
67 | )
68 | assert old_token != result["api_token"]
69 | finally:
70 | user_test.own_class().delete(id=test_user["id"])
71 |
--------------------------------------------------------------------------------
/tests/02-integration/utils/test_stix_crud.py:
--------------------------------------------------------------------------------
1 | from pytest_cases import fixture, parametrize_with_cases
2 | from stix2 import Bundle
3 |
4 | from tests.cases.entities import EntityTestCases
5 |
6 |
7 | @fixture
8 | @parametrize_with_cases("entity", cases=EntityTestCases)
9 | def entity_class(entity):
10 | entity.setup()
11 | yield entity
12 | entity.teardown()
13 |
14 |
15 | def test_entity_create(entity_class, api_stix, opencti_splitter):
16 | class_data = entity_class.data()
17 | stix_class = entity_class.stix_class()
18 | if stix_class is None:
19 | return
20 |
21 | stix_object = stix_class(**class_data)
22 | bundle = Bundle(objects=[stix_object]).serialize()
23 | split_bundle = opencti_splitter.split_bundle(bundle, True, None)[0]
24 | bundles_sent = api_stix.import_bundle_from_json(split_bundle, False, None, None)
25 |
26 | assert len(bundles_sent) == 1
27 | assert bundles_sent[0]["id"] == stix_object["id"]
28 | assert bundles_sent[0]["type"] == stix_object["type"]
29 |
30 | entity_class.base_class().delete(id=stix_object["id"])
31 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/tests/__init__.py
--------------------------------------------------------------------------------
/tests/cases/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCTI-Platform/client-python/fee6efbf821a1eb98af5fd09fc039598f52a79ae/tests/cases/__init__.py
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from pytest_cases import fixture
3 |
4 | from pycti import (
5 | OpenCTIApiClient,
6 | OpenCTIApiConnector,
7 | OpenCTIApiWork,
8 | OpenCTIStix2,
9 | OpenCTIStix2Splitter,
10 | )
11 |
12 |
13 | @fixture(scope="session")
14 | def api_client(pytestconfig):
15 | if pytestconfig.getoption("--drone"):
16 | return OpenCTIApiClient(
17 | "http://opencti:4000",
18 | "bfa014e0-e02e-4aa6-a42b-603b19dcf159",
19 | ssl_verify=False,
20 | )
21 | else:
22 | return OpenCTIApiClient(
23 | "http://localhost:4000",
24 | "d434ce02-e58e-4cac-8b4c-42bf16748e84",
25 | ssl_verify=False,
26 | )
27 |
28 |
29 | @fixture(scope="session")
30 | def api_connector(api_client):
31 | return OpenCTIApiConnector(api_client)
32 |
33 |
34 | @fixture(scope="session")
35 | def api_work(api_client):
36 | return OpenCTIApiWork(api_client)
37 |
38 |
39 | @fixture(scope="session")
40 | def api_stix(api_client):
41 | return OpenCTIStix2(api_client)
42 |
43 |
44 | @fixture(scope="session")
45 | def opencti_splitter():
46 | return OpenCTIStix2Splitter()
47 |
48 |
49 | def pytest_addoption(parser):
50 | parser.addoption(
51 | "--connectors", action="store_true", default=False, help="run connector tests"
52 | )
53 | parser.addoption(
54 | "--drone",
55 | action="store_true",
56 | default=False,
57 | help="run connector tests in drone environment",
58 | )
59 |
60 |
61 | def pytest_configure(config):
62 | config.addinivalue_line("markers", "connectors: mark connector tests to run")
63 |
64 |
65 | def pytest_collection_modifyitems(config, items):
66 | if config.getoption("--connectors"):
67 | return
68 | skip_connectors = pytest.mark.skip(reason="need --connectors to run")
69 | for item in items:
70 | if "connectors" in item.keywords:
71 | item.add_marker(skip_connectors)
72 |
--------------------------------------------------------------------------------
/tests/data/basicMalwareWithSample.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "bundle",
3 | "id": "bundle--59860f2d-2245-4901-b40e-46b51856bde4",
4 | "objects": [
5 | {
6 | "id": "malware--d650c5b9-4b43-5781-8576-ea52bd6c7ce0",
7 | "spec_version": "2.1",
8 | "revoked": false,
9 | "confidence": 100,
10 | "created": "2024-03-13T09:56:18.259Z",
11 | "modified": "2024-03-13T09:56:18.259Z",
12 | "name": "BasicMalware",
13 | "is_family": false,
14 | "x_opencti_id": "75f2a512-fcc6-4cbc-a2ef-52ca9c57df46",
15 | "x_opencti_type": "Malware",
16 | "type": "malware",
17 | "sample_refs": [
18 | "file--9fb3f45e-ffce-5525-95a5-906a6695aa85"
19 | ]
20 | },
21 | {
22 | "id": "file--9fb3f45e-ffce-5525-95a5-906a6695aa85",
23 | "spec_version": "2.1",
24 | "x_opencti_score": 50,
25 | "name": "basicFile",
26 | "x_opencti_id": "8c555399-ad2c-4862-8113-0889a3c14116",
27 | "x_opencti_type": "StixFile",
28 | "type": "file"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/tests/data/bundle_ids_sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "bundle",
3 | "id": "bundle--8c939929-688f-4a72-badb-3dd1bd6af0fa",
4 | "objects": [
5 | {
6 | "id": "malware--d650c5b9-4b43-5781-8576-ea52bd6c7ce5",
7 | "spec_version": "2.1",
8 | "revoked": false,
9 | "confidence": 100,
10 | "created": "2024-03-13T09:56:18.259Z",
11 | "modified": "2024-03-13T09:56:18.259Z",
12 | "name": "BasicMalware",
13 | "is_family": false,
14 | "x_opencti_id": "75f2a512-fcc6-4cbc-a2ef-52ca9c57df46",
15 | "x_opencti_type": "Malware",
16 | "type": "malware"
17 | },
18 | {
19 | "id": "identity--7b82b010-b1c0-4dae-981f-7756374a17da",
20 | "type": "identity",
21 | "spec_version": "2.1",
22 | "name": "ANSSI",
23 | "identity_class": "organization",
24 | "labels": ["identity"],
25 | "created": "2020-02-23T23:40:53.575Z",
26 | "modified": "2020-02-27T08:45:39.351Z",
27 | "x_opencti_organization_type": "CSIRT"
28 | },
29 | {
30 | "id": "marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27",
31 | "type": "marking-definition",
32 | "spec_version": "2.1",
33 | "definition_type": "TLP",
34 | "definition": {
35 | "TLP": "TLP:TEST"
36 | },
37 | "created": "2020-02-25T09:02:29.040Z",
38 | "modified": "2020-02-25T09:02:29.040Z",
39 | "created_by_ref": "marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"
40 | },
41 | {
42 | "id": "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd7",
43 | "type": "report",
44 | "spec_version": "2.1",
45 | "name": "A demo report for testing purposes",
46 | "labels": ["report"],
47 | "description": "Report for testing purposes (random data).",
48 | "published": "2020-03-01T14:02:48.111Z",
49 | "created": "2020-03-01T14:02:55.327Z",
50 | "modified": "2020-03-01T14:09:48.078Z",
51 | "report_types": ["threat-report"],
52 | "x_opencti_report_status": 2,
53 | "confidence": 3,
54 | "created_by_ref": "identity--7b82b010-b1c0-4dae-981f-7756374a17da",
55 | "object_marking_refs": ["marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"],
56 | "object_refs": [
57 | "observed-data--7d258c31-9a26-4543-aecb-2abc5ed366be",
58 | "malware--d650c5b9-4b43-5781-8576-ea52bd6c7ce5"
59 | ]
60 | },
61 | {
62 | "id": "relationship--ba52fced-422a-4bee-816a-85aa21c9eacc",
63 | "type": "relationship",
64 | "spec_version": "2.1",
65 | "relationship_type": "related-to",
66 | "source_ref": "malware--d650c5b9-4b43-5781-8576-ea52bd6c7ce5",
67 | "target_ref": "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd7",
68 | "created": "2020-03-01T14:07:14.316Z",
69 | "modified": "2020-03-01T14:07:14.316Z",
70 | "start_time": "1900-01-01T00:00:00.000Z",
71 | "stop_time": "1900-01-01T00:00:00.000Z",
72 | "created_by_ref": "identity--7b82b010-b1c0-4dae-981f-7756374a17da",
73 | "object_marking_refs": ["marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"]
74 | },
75 | {
76 | "type": "sighting",
77 | "spec_version": "2.1",
78 | "id": "sighting--ee20065d-2555-424f-ad9e-0f8428623c75",
79 | "created": "2016-08-06T20:08:31.000Z",
80 | "modified": "2016-09-06T20:08:31.000Z",
81 | "sighting_of_ref": "malware--d650c5b9-4b43-5781-8576-ea52bd6c7ce5",
82 | "where_sighted_refs": ["identity--7b82b010-b1c0-4dae-981f-7756374a17da"],
83 | "first_seen": "2016-08-06T20:08:31.000Z",
84 | "last_seen": "2016-08-07T20:08:31.000Z",
85 | "count": 12,
86 | "x_opencti_negative": true
87 | }
88 | ]
89 | }
90 |
--------------------------------------------------------------------------------
/tests/data/bundle_with_internal_ids.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "bundle",
3 | "id": "bundle--8c939929-688f-4a72-badb-3dd1bd6af0fa",
4 | "objects": [
5 | {
6 | "id": "identity--67ffc076-1fba-5164-9df5-b7523092931e",
7 | "spec_version": "2.1",
8 | "type": "identity",
9 | "extensions": {
10 | "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba": {
11 | "extension_type": "property-extension",
12 | "id": "ced3e53e-9663-4c96-9c60-07d2e778d931",
13 | "type": "Organization",
14 | "created_at": "2024-09-26T17:36:32.107Z",
15 | "updated_at": "2024-09-26T17:36:32.107Z",
16 | "is_inferred": false,
17 | "creator_ids": [
18 | "88ec0c6a-13ce-5e39-b486-354fe4a7084f"
19 | ]
20 | }
21 | },
22 | "created": "2024-09-26T17:36:32.107Z",
23 | "modified": "2024-09-26T17:36:32.107Z",
24 | "revoked": false,
25 | "confidence": 100,
26 | "lang": "en",
27 | "name": "JRI",
28 | "identity_class": "organization"
29 | },
30 | {
31 | "id": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3c",
32 | "type": "malware",
33 | "name": "malware 01",
34 | "created": "2019-09-30T16:38:26.000Z",
35 | "modified": "2020-01-14T14:01:48.288Z",
36 | "x_opencti_id": "82316ffd-a0ec-4519-a454-6566f8f5676c"
37 | },
38 | {
39 | "id": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3d",
40 | "type": "malware",
41 | "name": "malware 02",
42 | "created": "2019-09-30T16:38:26.000Z",
43 | "modified": "2020-01-14T14:01:48.288Z"
44 | },
45 | {
46 | "id": "relationship--10e8c71d-a1b4-4e35-bca8-2e4a3785ea04",
47 | "type": "relationship",
48 | "relationship_type": "part-of",
49 | "created_by_ref": "ced3e53e-9663-4c96-9c60-07d2e778d931",
50 | "source_ref": "82316ffd-a0ec-4519-a454-6566f8f5676c",
51 | "target_ref": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3d"
52 | }
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/tests/data/cyclic-bundle.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "bundle",
3 | "id": "bundle--8c939929-688f-4a72-badb-3dd1bd6af0fa",
4 | "objects": [
5 | {
6 | "id": "marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27",
7 | "type": "marking-definition",
8 | "spec_version": "2.1",
9 | "definition_type": "TLP",
10 | "definition": {
11 | "TLP": "TLP:TEST"
12 | },
13 | "created": "2020-02-25T09:02:29.040Z",
14 | "modified": "2020-02-25T09:02:29.040Z",
15 | "created_by_ref": "marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"
16 | },
17 | {
18 | "id": "marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27",
19 | "type": "marking-definition",
20 | "spec_version": "2.1",
21 | "definition_type": "TLP",
22 | "definition": {
23 | "TLP": "TLP:TEST"
24 | },
25 | "created": "2020-02-25T09:02:29.040Z",
26 | "modified": "2020-02-25T09:02:29.040Z",
27 | "created_by_ref": "marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"
28 | },
29 | {
30 | "id": "x-mitre-asset--0804f037-a3b9-4715-98e1-9f73d19d6945",
31 | "type": "x-mitre-asset",
32 | "name": "Virtual Private Network (VPN) Server"
33 | },
34 | {
35 | "id": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3c",
36 | "type": "malware",
37 | "spec_version": "2.1",
38 | "name": "Paradise Ransomware",
39 | "labels": ["malware"],
40 | "description": "MalwareHunterTeam discovered a new Paradise Ransomware variant that uses the extension _V.0.0.0.1{paradise@all-ransomware.info}.prt and drops a ransom note named PARADISE_README_paradise@all-ransomware.info.txt.",
41 | "created": "2019-09-30T16:38:26.000Z",
42 | "modified": "2020-01-14T14:01:48.288Z",
43 | "kill_chain_phases": [
44 | {
45 | "kill_chain_name": "mitre-attack",
46 | "phase_name": "persistence",
47 | "x_opencti_order": 20
48 | }
49 | ]
50 | },
51 | {
52 | "id": "relationship--10e8c71d-a1b4-4e35-bca8-2e4a3785ea04",
53 | "type": "relationship",
54 | "spec_version": "2.1",
55 | "relationship_type": "part-of",
56 | "description": "Sector Information Technologies Consulting is a subsector of Consulting",
57 | "source_ref": "x-mitre-asset--0804f037-a3b9-4715-98e1-9f73d19d6945",
58 | "target_ref": "identity--5556c4ab-3e5e-4d56-8410-60b29cecbeb6",
59 | "created": "2020-02-23T23:40:53.582Z",
60 | "modified": "2020-02-23T23:40:53.582Z",
61 | "start_time": "1900-05-01T00:00:00.000Z",
62 | "stop_time": "1900-05-01T00:00:00.000Z"
63 | },
64 | {
65 | "id": "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd7",
66 | "type": "report",
67 | "spec_version": "2.1",
68 | "name": "A demo report for testing purposes",
69 | "labels": ["report"],
70 | "description": "Report for testing purposes (random data).",
71 | "published": "2020-03-01T14:02:48.111Z",
72 | "created": "2020-03-01T14:02:55.327Z",
73 | "modified": "2020-03-01T14:09:48.078Z",
74 | "report_types": ["threat-report"],
75 | "x_opencti_report_status": 2,
76 | "confidence": 3,
77 | "created_by_ref": "identity--7b82b010-b1c0-4dae-981f-7756374a17df",
78 | "object_marking_refs": ["marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"],
79 | "object_refs": [
80 | "observed-data--7d258c31-9a26-4543-aecb-2abc5ed366be",
81 | "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd7",
82 | "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd8"
83 | ],
84 | "external_references": [
85 | {
86 | "source_name": "mitre-attack",
87 | "external_id": "enterprise-attack",
88 | "url": "https://attack.mitre.org/matrices/enterprise"
89 | },
90 | {
91 | "source_name": "mitre-attack",
92 | "external_id": "enterprise-attack",
93 | "url": "https://attack.mitre.org/matrices/enterprise"
94 | },
95 | {
96 | "source_name": "mitre-attack",
97 | "external_id": "enterprise-attack",
98 | "url": "https://attack.mitre.org/matrices/enterprise"
99 | }
100 | ]
101 | },
102 | {
103 | "id": "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd8",
104 | "type": "report",
105 | "spec_version": "2.1",
106 | "name": "A cyclic report",
107 | "labels": ["report"],
108 | "description": "Report for testing purposes (random data).",
109 | "published": "2020-03-01T14:02:48.111Z",
110 | "created": "2020-03-01T14:02:55.327Z",
111 | "modified": "2020-03-01T14:09:48.078Z",
112 | "report_types": ["threat-report"],
113 | "x_opencti_report_status": 2,
114 | "confidence": 3,
115 | "created_by_ref": "identity--7b82b010-b1c0-4dae-981f-7756374a17df",
116 | "object_marking_refs": ["marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"],
117 | "object_refs": [
118 | "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd7",
119 | "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd9"
120 | ],
121 | "external_references": [
122 | {
123 | "source_name": "mitre-attack",
124 | "external_id": "enterprise-attack",
125 | "url": "https://attack.mitre.org/matrices/enterprise"
126 | }
127 | ]
128 | },
129 | {
130 | "id": "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd9",
131 | "type": "report",
132 | "spec_version": "2.1",
133 | "name": "A cyclic report",
134 | "labels": ["report"],
135 | "description": "Report for testing purposes (random data).",
136 | "published": "2020-03-01T14:02:48.111Z",
137 | "created": "2020-03-01T14:02:55.327Z",
138 | "modified": "2020-03-01T14:09:48.078Z",
139 | "report_types": ["threat-report"],
140 | "x_opencti_report_status": 2,
141 | "confidence": 3,
142 | "created_by_ref": "identity--7b82b010-b1c0-4dae-981f-7756374a17df",
143 | "object_marking_refs": ["marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"],
144 | "object_refs": [
145 | "report--a445d22a-db0c-4b5d-9ec8-e9ad0b6dbdd7"
146 | ]
147 | },
148 | {
149 | "id": "identity--7b82b010-b1c0-4dae-981f-7756374a17df",
150 | "type": "identity",
151 | "spec_version": "2.1",
152 | "name": "ANSSI",
153 | "identity_class": "organization",
154 | "labels": ["identity"],
155 | "created": "2020-02-23T23:40:53.575Z",
156 | "modified": "2020-02-27T08:45:39.351Z",
157 | "x_opencti_organization_type": "CSIRT",
158 | "created_by_ref": "identity--7b82b010-b1c0-4dae-981f-7756374a17df"
159 | }
160 | ]
161 | }
162 |
--------------------------------------------------------------------------------
/tests/data/external_import_config.yml:
--------------------------------------------------------------------------------
1 | connector:
2 | id: '65175acc-869b-465d-8a07-47ef00b92394'
3 | type: 'EXTERNAL_IMPORT'
4 | name: 'TestExternalImport'
5 | scope: 'identity,vulnerability'
6 | confidence_level: 80 # From 0 (Unknown) to 100 (Fully trusted)
7 | update_existing_data: True
8 | log_level: 'debug'
9 |
10 | test:
11 | interval: 1
12 |
--------------------------------------------------------------------------------
/tests/data/indicator_stix.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "bundle",
3 | "id": "bundle--3e0921e7-0d87-49c4-8121-ec28db0f535f",
4 | "objects": [
5 | {
6 | "id": "indicator--39915430-6d24-4f9d-a871-76a264169674",
7 | "type": "indicator",
8 | "spec_version": "2.1",
9 | "name": "103.83.192.6",
10 | "labels": [
11 | "indicator"
12 | ],
13 | "pattern": "[ipv4-addr:value = '103.83.192.6']",
14 | "valid_from": "2020-04-17T09:50:27.000Z",
15 | "valid_until": "2021-04-17T09:50:27.000Z",
16 | "pattern_type": "stix",
17 | "created": "2020-04-30T06:29:41.232Z",
18 | "modified": "2020-04-30T06:29:41.232Z",
19 | "x_opencti_score": 50,
20 | "x_opencti_id": "40caf0b8-0632-4f62-9ba6-dc10c093a63e",
21 | "created_by_ref": "identity--26926631-96e9-40e9-9ac0-ab93384f0ce7",
22 | "object_marking_refs": [
23 | "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
24 | ]
25 | },
26 | {
27 | "id": "indicator--41be0c61-43b3-4233-aea0-7077a8981c77",
28 | "type": "indicator",
29 | "spec_version": "2.1",
30 | "name": "192.185.46.253",
31 | "labels": [
32 | "indicator"
33 | ],
34 | "pattern": "[ipv4-addr:value = '192.185.46.253']",
35 | "valid_from": "2020-04-17T09:50:27.000Z",
36 | "valid_until": "2021-04-17T09:50:27.000Z",
37 | "pattern_type": "stix",
38 | "created": "2020-04-30T06:29:41.303Z",
39 | "modified": "2020-04-30T06:29:41.303Z",
40 | "x_opencti_score": 50,
41 | "x_opencti_id": "2962d903-76ad-4092-b4e0-2021a30db0cb",
42 | "created_by_ref": "identity--26926631-96e9-40e9-9ac0-ab93384f0ce7",
43 | "object_marking_refs": [
44 | "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9"
45 | ]
46 | }
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/tests/data/internal_enrichment_config.yml:
--------------------------------------------------------------------------------
1 | connector:
2 | id: '7f175acc-869b-465d-8a07-47ef11b92394'
3 | type: 'INTERNAL_ENRICHMENT'
4 | name: 'SetScore100Enrichment'
5 | scope: 'IPv4'
6 | auto: false # Enable/disable auto-import of file
7 | only_contextual: false # Only extract data related to an entity (a report, a threat actor, etc.)
8 | confidence_level: 15 # From 0 (Unknown) to 100 (Fully trusted)
9 | log_level: 'debug'
10 |
--------------------------------------------------------------------------------
/tests/data/internal_import_config.yml:
--------------------------------------------------------------------------------
1 | connector:
2 | id: '7f175acc-811b-465d-8a07-47ef11b92394'
3 | type: 'INTERNAL_IMPORT_FILE'
4 | name: 'ParseFileTest'
5 | scope: 'application/pdf,text/plain'
6 | auto: true # Enable/disable auto-import of file
7 | only_contextual: true # Only extract data related to an entity (a report, a threat actor, etc.)
8 | confidence_level: 15 # From 0 (Unknown) to 100 (Fully trusted)
9 | log_level: 'debug'
10 |
--------------------------------------------------------------------------------
/tests/data/internal_import_data.txt:
--------------------------------------------------------------------------------
1 | Test file
2 |
--------------------------------------------------------------------------------
/tests/data/missing_refs.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "bundle",
3 | "id": "bundle--8c939929-688f-4a72-badb-3dd1bd6af0fa",
4 | "objects": [
5 | {
6 | "id": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3c",
7 | "type": "malware",
8 | "name": "malware 01",
9 | "created": "2019-09-30T16:38:26.000Z",
10 | "modified": "2020-01-14T14:01:48.288Z"
11 | },
12 | {
13 | "id": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3d",
14 | "type": "malware",
15 | "name": "malware 02",
16 | "created": "2019-09-30T16:38:26.000Z",
17 | "modified": "2020-01-14T14:01:48.288Z"
18 | },
19 | {
20 | "id": "relationship--10e8c71d-a1b4-4e35-bca8-2e4a3785ea04",
21 | "type": "relationship",
22 | "relationship_type": "part-of",
23 | "created_by_ref": "identity--not-available",
24 | "source_ref": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3c",
25 | "target_ref": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3d"
26 | },
27 | {
28 | "id": "relationship--10e8c71d-a1b4-4e35-bca8-2e4a3785ea05",
29 | "type": "relationship",
30 | "relationship_type": "part-of",
31 | "created_by_ref": "identity--not-available",
32 | "source_ref": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3c",
33 | "target_ref": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3e"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/tests/data/mono-bundle-entity.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "bundle--1f126d0c-356c-43af-88f8-7a3b695dee83",
3 | "objects": [
4 | {
5 | "id": "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168",
6 | "created": "2017-06-01T00:00:00.000Z",
7 | "created_by_ref": "fa42a846-8d90-4e51-bc29-71d5b4802168",
8 | "definition": {
9 | "statement": "Copyright 2015-2024, The MITRE Corporation. MITRE ATT&CK and ATT&CK are registered trademarks of The MITRE Corporation."
10 | },
11 | "definition_type": "statement",
12 | "spec_version": "2.1",
13 | "type": "marking-definition",
14 | "x_mitre_attack_spec_version": "2.1.0",
15 | "x_mitre_domains": ["enterprise-attack"]
16 | }
17 | ],
18 | "spec_version": "2.1",
19 | "type": "bundle",
20 | "x_opencti_seq": 1
21 | }
--------------------------------------------------------------------------------
/tests/data/mono-bundle-relationship.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "bundle--1f126d0c-356c-43af-88f8-7a3b695dee83",
3 | "objects": [
4 | {
5 | "id": "relationship--10e8c71d-a1b4-4e35-bca8-2e4a3785ea04",
6 | "type": "relationship",
7 | "relationship_type": "part-of",
8 | "created_by_ref": "identity--not-available",
9 | "source_ref": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3c",
10 | "target_ref": "malware--faa5b705-cf44-4e50-8472-29e5fec43c3d"
11 | }
12 | ],
13 | "spec_version": "2.1",
14 | "type": "bundle",
15 | "x_opencti_seq": 1
16 | }
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import time
3 | from typing import Dict, List
4 |
5 | from dateutil.parser import parse
6 |
7 | from pycti import OpenCTIApiClient, OpenCTIApiConnector, OpenCTIApiWork
8 |
9 |
10 | def is_filters_empty(filters):
11 | if not filters:
12 | return True
13 | if len(filters["filters"]) == 0 and len(filters["filterGroups"]) == 0:
14 | return True
15 | return False
16 |
17 |
18 | def get_incident_start_date():
19 | return (
20 | parse("2019-12-01")
21 | .replace(tzinfo=datetime.timezone.utc)
22 | .isoformat(sep="T", timespec="milliseconds")
23 | .replace("+00:00", "Z")
24 | )
25 |
26 |
27 | def get_incident_end_date():
28 | return (
29 | parse("2021-12-01")
30 | .replace(tzinfo=datetime.timezone.utc)
31 | .isoformat(sep="T", timespec="milliseconds")
32 | .replace("+00:00", "Z")
33 | )
34 |
35 |
36 | def read_marking(api_client: OpenCTIApiClient, tlp_id: int):
37 | return api_client.marking_definition.read(id=tlp_id)
38 |
39 |
40 | def get_connector_id(connector_name: str, api_connector: OpenCTIApiConnector) -> str:
41 | connector_list = api_connector.list()
42 | connector_id = ""
43 | for connector in connector_list:
44 | if connector["name"] == connector_name:
45 | connector_id = connector["id"]
46 |
47 | return connector_id
48 |
49 |
50 | def get_new_work_id(api_client: OpenCTIApiClient, connector_id: str) -> str:
51 | worker = OpenCTIApiWork(api_client)
52 | new_works = worker.get_connector_works(connector_id)
53 | cnt = 0
54 | while len(new_works) == 0:
55 | time.sleep(1)
56 | # wait 20 seconds for new work to be registered
57 | cnt += 1
58 | if cnt > 20:
59 | assert (
60 | cnt != cnt
61 | ), "Connector hasn't registered new work yet. Elapsed time 20s"
62 |
63 | assert (
64 | len(new_works) == 1
65 | ), f"Too many jobs were created. Expected 1, Actual: {len(new_works)}"
66 | return new_works[0]["id"]
67 |
68 |
69 | def compare_values(original_data: Dict, retrieved_data: Dict, exception_keys: List):
70 | for key, value in original_data.items():
71 | # Attributes which aren't present in the final Stix objects
72 | if key in exception_keys:
73 | continue
74 |
75 | assert (
76 | key in retrieved_data
77 | ), f"Key {key} is not in retrieved_data {retrieved_data}"
78 |
79 | compare_data = retrieved_data.get(key, None)
80 | if isinstance(value, str):
81 | assert (
82 | value == compare_data
83 | ), f"Key '{key}': '{value}' does't match value '{retrieved_data[key]}' ({retrieved_data}"
84 | elif key == "objects" and isinstance(value, list):
85 | assert isinstance(compare_data, list), f"Key '{key}': is not a list"
86 | original_ids = set()
87 | for elem in value:
88 | if isinstance(elem, dict):
89 | original_ids.add(elem.get("id", None))
90 | elif isinstance(elem, str):
91 | original_ids.add(elem)
92 |
93 | retrieved_ids = set()
94 | for elem in compare_data:
95 | if isinstance(elem, dict):
96 | retrieved_ids.add(elem.get("id", None))
97 | elif isinstance(elem, str):
98 | original_ids.add(elem)
99 |
100 | assert (
101 | original_ids == retrieved_ids
102 | ), f"Key '{key}': '{value}' does't match value '{compare_data}'"
103 | elif isinstance(value, dict):
104 | assert len(value) == len(
105 | compare_data
106 | ), f"Dict '{value}' does not have the same length as '{compare_data}'"
107 | assert (
108 | value == compare_data
109 | ), f"Dict '{value}' does not have the same content as'{compare_data}'"
110 |
--------------------------------------------------------------------------------