├── .github
└── workflows
│ └── python-app.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── access_control
├── __init__.py
├── create_subject.py
├── create_subject_test.py
├── delete_subject.py
├── delete_subject_test.py
├── get_subject.py
├── get_subject_test.py
├── list_roles.py
├── list_roles_test.py
├── list_subjects.py
├── list_subjects_test.py
├── update_role.py
├── update_role_test.py
├── update_subject.py
└── update_subject_test.py
├── common
├── __init__.py
├── chronicle_auth.py
├── chronicle_auth_test.py
├── datetime_converter.py
├── datetime_converter_test.py
├── project_id.py
├── project_instance.py
├── regions.py
└── regions_test.py
├── datatap
├── __init__.py
├── create_datatap.py
├── create_datatap_test.py
├── delete_datatap.py
├── delete_datatap_test.py
├── get_datatap.py
├── get_datatap_test.py
├── list_datatap.py
├── list_datatap_test.py
├── update_datatap.py
└── update_datatap_test.py
├── detect
├── v1alpha
│ ├── __init__.py
│ ├── batch_update_curated_rule_set_deployments.py
│ ├── bulk_update_alerts.py
│ ├── create_retrohunt.py
│ ├── create_rule.py
│ ├── delete_rule.py
│ ├── enable_rule.py
│ ├── get_alert.py
│ ├── get_retrohunt.py
│ ├── get_rule.py
│ ├── list_detections.py
│ ├── list_errors.py
│ ├── list_rules.py
│ ├── search_rules_alerts.py
│ ├── update_alert.py
│ └── update_rule.py
└── v2
│ ├── __init__.py
│ ├── archive_rule.py
│ ├── archive_rule_test.py
│ ├── cancel_retrohunt.py
│ ├── cancel_retrohunt_test.py
│ ├── create_rule.py
│ ├── create_rule_test.py
│ ├── create_rule_version.py
│ ├── create_rule_version_test.py
│ ├── delete_rule.py
│ ├── delete_rule_test.py
│ ├── disable_alerting.py
│ ├── disable_alerting_test.py
│ ├── disable_live_rule.py
│ ├── disable_live_rule_test.py
│ ├── enable_alerting.py
│ ├── enable_alerting_test.py
│ ├── enable_live_rule.py
│ ├── enable_live_rule_test.py
│ ├── get_detection.py
│ ├── get_detection_test.py
│ ├── get_error.py
│ ├── get_error_test.py
│ ├── get_retrohunt.py
│ ├── get_retrohunt_test.py
│ ├── get_rule.py
│ ├── get_rule_test.py
│ ├── list_curated_rule_detections.py
│ ├── list_curated_rule_detections_test.py
│ ├── list_curated_rules.py
│ ├── list_curated_rules_and_detections.py
│ ├── list_curated_rules_and_detections_test.py
│ ├── list_curated_rules_test.py
│ ├── list_detections.py
│ ├── list_detections_test.py
│ ├── list_errors.py
│ ├── list_errors_test.py
│ ├── list_retrohunts.py
│ ├── list_retrohunts_test.py
│ ├── list_rule_versions.py
│ ├── list_rule_versions_test.py
│ ├── list_rules.py
│ ├── list_rules_test.py
│ ├── run_retrohunt.py
│ ├── run_retrohunt_and_wait.py
│ ├── run_retrohunt_and_wait_test.py
│ ├── run_retrohunt_test.py
│ ├── stream_detection_alerts.py
│ ├── stream_detection_alerts_test.py
│ ├── stream_test_rule.py
│ ├── stream_test_rule_test.py
│ ├── unarchive_rule.py
│ ├── unarchive_rule_test.py
│ ├── verify_rule.py
│ └── verify_rule_test.py
├── feeds
├── __init__.py
├── create_azure_ad_context_feed.py
├── create_azure_ad_context_feed_test.py
├── create_azure_ad_feed.py
├── create_azure_ad_feed_test.py
├── create_okta_feed.py
├── create_okta_feed_test.py
├── create_okta_user_context_feed.py
├── create_okta_user_context_feed_test.py
├── create_workspace_activity_feed.py
├── create_workspace_activity_feed_test.py
├── create_workspace_alerts_feed.py
├── create_workspace_alerts_feed_test.py
├── delete_feed.py
├── delete_feed_test.py
├── disable_feed.py
├── disable_feed_test.py
├── enable_feed.py
├── enable_feed_test.py
├── get_feed.py
├── get_feed_test.py
├── list_feeds.py
└── list_feeds_test.py
├── forwarders
├── README.md
├── __init__.py
├── create_collector.py
├── create_collector_test.py
├── create_forwarder.py
├── create_forwarder_test.py
├── delete_collector.py
├── delete_collector_test.py
├── delete_forwarder.py
├── delete_forwarder_test.py
├── generate_files.py
├── generate_files_test.py
├── get_collector.py
├── get_collector_test.py
├── get_forwarder.py
├── get_forwarder_test.py
├── list_collectors.py
├── list_collectors_test.py
├── list_forwarders.py
├── list_forwarders_test.py
├── update_collector.py
├── update_collector_test.py
├── update_forwarder.py
└── update_forwarder_test.py
├── ingestion
├── __init__.py
├── create_entities.py
├── create_entities_test.py
├── create_udm_events.py
├── create_udm_events_test.py
├── create_unstructured_log_entries.py
├── create_unstructured_log_entries_test.py
├── example_input
│ ├── sample_entities.json
│ ├── sample_udm_events.json
│ └── sample_unstructured_log_entries.txt
├── list_log_types.py
├── list_log_types_test.py
└── v1alpha
│ ├── create_udm_events.py
│ └── get_udm_event.py
├── lists
├── __init__.py
├── append_to_list.py
├── create_list.py
├── create_list_test.py
├── example_input
│ ├── coldriver_sha256.txt
│ └── foo.txt
├── get_list.py
├── get_list_test.py
├── list_lists.py
├── list_lists_test.py
├── remove_from_list.py
├── update_list.py
├── update_list_test.py
├── v1alpha
│ ├── create_list.py
│ ├── get_list.py
│ ├── patch_list.py
│ └── patch_list_test.py
├── verify_list.py
└── verify_list_test.py
├── requirements.txt
├── search
├── __init__.py
├── list_alerts.py
├── list_alerts_test.py
├── list_asset_events.py
├── list_asset_events_test.py
├── list_iocs.py
├── list_iocs_test.py
├── udm_search.py
└── udm_search_test.py
├── service_management
├── README.md
├── __init__.py
├── create_gcp_association.py
├── create_gcp_association_test.py
├── delete_gcp_association.py
├── delete_gcp_association_test.py
├── get_gcp_association.py
├── get_gcp_association_test.py
├── get_gcp_log_flow_filter.py
├── get_gcp_log_flow_filter_test.py
├── get_gcp_settings.py
├── get_gcp_settings_test.py
├── update_gcp_log_flow_filter.py
├── update_gcp_log_flow_filter_test.py
├── update_gcp_settings.py
└── update_gcp_settings_test.py
└── uppercase
├── __init__.py
├── get_alert.py
├── get_alert_test.py
├── list_alerts.py
└── list_alerts_test.py
/.github/workflows/python-app.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Python application
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build-and-test:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up Python 3.8
20 | uses: actions/setup-python@v2
21 | with:
22 | python-version: 3.8
23 | - name: Install dependencies
24 | run: |
25 | python -m pip install --upgrade pip
26 | pip install flake8 pytest
27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
28 | - name: Lint with flake8
29 | run: |
30 | # stop the build if there are Python syntax errors or undefined names
31 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
32 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
33 | # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
34 | - name: Test with pytest
35 | run: |
36 | python -m pytest
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store/
2 |
3 | __pycache__/
4 | venv/
5 |
6 | node_modules/
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code Reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google/conduct/).
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chronicle API Samples in Python
2 |
3 | Python samples and guidelines for using Chronicle APIs.
4 |
5 | ## Setup
6 |
7 | Follow these instructions: https://cloud.google.com/python/setup
8 |
9 | You may skip installing the Cloud Client Libraries and the Cloud SDK, they are
10 | unnecessary for interacting with Chronicle.
11 |
12 | After creating and activating a virtual environment, install Python
13 | library dependencies by running this command:
14 |
15 | ```shell
16 | pip install -r requirements.txt
17 | ```
18 |
19 | It is assumed that you're using Python 3.7 or above. If you're using an older
20 | Python 3 version, you need to install this backported library as well:
21 |
22 | ```shell
23 | pip install dataclasses
24 | ```
25 |
26 | ## Credentials
27 |
28 | Running the samples requires a JSON credentials file. By default, all the
29 | samples try to use the file `.chronicle_credentials.json` in the user's home
30 | directory. If this file is not found, you need to specify it explicitly by
31 | adding the following argument to the sample's command-line:
32 |
33 | ```shell
34 | -c
35 | ```
36 |
37 | or
38 |
39 | ```shell
40 | --credentials_file
41 | ```
42 |
43 | ## Usage
44 |
45 | You can run samples on the command-line, assuming the current working directory
46 | is the root directory of this repository (i.e. the directory which contains
47 | this `README.md` file):
48 |
49 | ### Detect API
50 |
51 | ```shell
52 | python3 -m detect.v2. -h
53 | ```
54 |
55 | ### Lists API
56 |
57 | ```shell
58 | python3 -m lists. -h
59 | ```
60 |
61 | ### Lists API v1alpha
62 |
63 | ```
64 | python -m lists.v1alpha.create_list -h
65 | python -m lists.v1alpha.get_list -h
66 | python -m lists.v1alpha.patch_list -h
67 | ```
68 |
--------------------------------------------------------------------------------
/access_control/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/access_control/delete_subject_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "delete_subject" module."""
16 |
17 | import unittest
18 | import argparse
19 |
20 | from unittest import mock
21 |
22 | from google.auth.transport import requests
23 |
24 | from . import delete_subject
25 |
26 |
27 | class DeleteSubjectTest(unittest.TestCase):
28 |
29 | def test_initialize_command_line_args(self):
30 | actual = delete_subject.initialize_command_line_args(
31 | ["--name=test@test.com"])
32 | self.assertEqual(
33 | actual,
34 | argparse.Namespace(
35 | credentials_file=None, name="test@test.com", region="us"))
36 |
37 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
38 | @mock.patch.object(requests.requests, "Response", autospec=True)
39 | def test_delete_subject_error(self, mock_response, mock_session):
40 | mock_session.request.return_value = mock_response
41 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
42 | mock_response.raise_for_status.side_effect = (
43 | requests.requests.exceptions.HTTPError())
44 |
45 | with self.assertRaises(requests.requests.exceptions.HTTPError):
46 | delete_subject.delete_subject(mock_session, "")
47 |
48 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
49 | @mock.patch.object(requests.requests, "Response", autospec=True)
50 | def test_delete_subject(self, mock_response, mock_session):
51 | mock_session.request.return_value = mock_response
52 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
53 | subject_id = "test@test.com"
54 | expected = None
55 | mock_response.json.return_value = expected
56 | actual = delete_subject.delete_subject(mock_session, subject_id)
57 | self.assertEqual(actual, expected)
58 |
59 |
60 | if __name__ == "__main__":
61 | unittest.main()
62 |
--------------------------------------------------------------------------------
/access_control/list_roles_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_roles" module."""
16 |
17 | import unittest
18 | import argparse
19 |
20 | from unittest import mock
21 |
22 | from google.auth.transport import requests
23 |
24 | from . import list_roles
25 |
26 |
27 | class ListRolesTest(unittest.TestCase):
28 |
29 | def test_initialize_command_line_args(self):
30 | actual = list_roles.initialize_command_line_args([])
31 | self.assertEqual(actual,
32 | argparse.Namespace(credentials_file=None, region="us"))
33 |
34 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
35 | @mock.patch.object(requests.requests, "Response", autospec=True)
36 | def test_list_roles_error(self, mock_response, mock_session):
37 | mock_session.request.return_value = mock_response
38 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
39 | mock_response.raise_for_status.side_effect = (
40 | requests.requests.exceptions.HTTPError())
41 |
42 | with self.assertRaises(requests.requests.exceptions.HTTPError):
43 | list_roles.list_roles(mock_session)
44 |
45 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
46 | @mock.patch.object(requests.requests, "Response", autospec=True)
47 | def test_list_roles(self, mock_response, mock_session):
48 | mock_session.request.return_value = mock_response
49 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
50 | expected = {
51 | "name":
52 | "Test",
53 | "title":
54 | "Test role",
55 | "description":
56 | "The Test role",
57 | "createTime":
58 | "2020-11-05T00:00:00Z",
59 | "isDefault":
60 | False,
61 | "permissions": [{
62 | "name": "Test",
63 | "title": "Test permission",
64 | "description": "The Test permission",
65 | "createTime": "2020-11-05T00:00:00Z",
66 | },]
67 | },
68 | mock_response.json.return_value = {"roles": [expected]}
69 | actual = list_roles.list_roles(mock_session)
70 | self.assertEqual(actual, [expected])
71 |
72 |
73 | if __name__ == "__main__":
74 | unittest.main()
75 |
--------------------------------------------------------------------------------
/access_control/update_role_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "update_role" module."""
16 |
17 | import unittest
18 | import argparse
19 |
20 | from unittest import mock
21 |
22 | from google.auth.transport import requests
23 |
24 | from . import update_role
25 |
26 |
27 | class UpdateRoleTest(unittest.TestCase):
28 |
29 | def test_initialize_command_line_args(self):
30 | actual = update_role.initialize_command_line_args(
31 | ["--name=Test", "--is-default=true"])
32 | self.assertEqual(
33 | actual,
34 | argparse.Namespace(
35 | credentials_file=None, name="Test", is_default=True, region="us"))
36 |
37 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
38 | @mock.patch.object(requests.requests, "Response", autospec=True)
39 | def test_update_role_error(self, mock_response, mock_session):
40 | mock_session.request.return_value = mock_response
41 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
42 | mock_response.raise_for_status.side_effect = (
43 | requests.requests.exceptions.HTTPError())
44 |
45 | with self.assertRaises(requests.requests.exceptions.HTTPError):
46 | update_role.update_role(mock_session, "", False)
47 |
48 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
49 | @mock.patch.object(requests.requests, "Response", autospec=True)
50 | def test_update_role(self, mock_response, mock_session):
51 | mock_session.request.return_value = mock_response
52 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
53 | role_id = "Test"
54 | is_default = True
55 | expected = {
56 | "name":
57 | "Test",
58 | "title":
59 | "Test role",
60 | "description":
61 | "The Test role",
62 | "createTime":
63 | "2020-11-05T00:00:00Z",
64 | "isDefault":
65 | True,
66 | "permissions": [{
67 | "name": "Test",
68 | "title": "Test permission",
69 | "description": "The Test permission",
70 | "createTime": "2020-11-05T00:00:00Z",
71 | },]
72 | }
73 | mock_response.json.return_value = expected
74 | actual = update_role.update_role(mock_session, role_id, is_default)
75 | self.assertEqual(actual, expected)
76 |
77 |
78 | if __name__ == "__main__":
79 | unittest.main()
80 |
--------------------------------------------------------------------------------
/common/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/common/datetime_converter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Helper functions to convert ISO 8601 strings into datetime objects.
16 | """
17 |
18 | import datetime
19 | import re
20 |
21 |
22 | def iso8601_datetime_utc(utc_date_time: str) -> datetime.datetime:
23 | """Converts an ISO 8601 string ("yyyy-mm-ddThh:mm:ssZ") to a datetime object.
24 |
25 | More details: https://en.wikipedia.org/wiki/ISO_8601
26 |
27 | Args:
28 | utc_date_time: Date and time in the extended ("T") ISO 8601 format, where
29 | the time is in UTC ("Z").
30 |
31 | Returns:
32 | Builtin datetime object with a UTC timezone.
33 |
34 | Raises:
35 | ValueError: Invalid input value.
36 | """
37 | # Work-around fixable issues in user-specified timestamps.
38 | utc_date_time = re.sub(r"(\d{2}-\d{2}-\d{2})\s+(\d)", r"\1T\2",
39 | utc_date_time).upper()
40 | if utc_date_time[-1] != "Z":
41 | utc_date_time += "Z"
42 |
43 | # Append the suffix "+0000" in order to produce a timezone-aware UTC datetime,
44 | # because strptime's "%z" does not recognize the meaning of the "Z" suffix.
45 | try:
46 | # Support (but don't require) sub-second parsing, but ignore anything
47 | # smaller than microseconds.
48 | utc_date_time = re.sub(r"(\d{6})\d+Z", r"\1Z", utc_date_time)
49 | return datetime.datetime.strptime(f"{utc_date_time}+0000",
50 | "%Y-%m-%dT%H:%M:%S.%fZ%z")
51 | except ValueError:
52 | # No microseconds? No problem, try to parse without them.
53 | # If there's a different parsing problem, it will surface below too.
54 | pass
55 |
56 | return datetime.datetime.strptime(f"{utc_date_time}+0000",
57 | "%Y-%m-%dT%H:%M:%SZ%z")
58 |
59 |
60 | def strftime(utc_date_time: datetime.datetime) -> str:
61 | """Converts a datetime object to a string with the format "%Y-%m-%dT%H:%M:%SZ".
62 |
63 | Args:
64 | utc_date_time: Builtin datetime object with a UTC timezone.
65 |
66 | Returns:
67 | Date and time in the format "%Y-%m-%dT%H:%M:%SZ".
68 |
69 | Raises:
70 | ValueError: Invalid input value.
71 | """
72 | if utc_date_time is None:
73 | return ""
74 | return utc_date_time.astimezone(
75 | datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
76 |
--------------------------------------------------------------------------------
/common/datetime_converter_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "datetime_converter" module."""
16 |
17 | import datetime
18 | import unittest
19 |
20 | from . import datetime_converter
21 |
22 |
23 | class DatetimeConverterTest(unittest.TestCase):
24 |
25 | def setUp(self):
26 | super().setUp()
27 | self.date_time = datetime.datetime(
28 | 2020, 11, 5, 0, 0, 0, 0, tzinfo=datetime.timezone.utc)
29 |
30 | def test_iso8601_datetime_utc(self):
31 | expected_date_time = self.date_time
32 | date_time = datetime_converter.iso8601_datetime_utc("2020-11-05T00:00:00Z")
33 | self.assertEqual(date_time, expected_date_time)
34 |
35 | def test_strftime(self):
36 | expected_date_time_str = "2020-11-05T00:00:00Z"
37 | date_time_str = datetime_converter.strftime(self.date_time)
38 | self.assertEqual(date_time_str, expected_date_time_str)
39 |
40 |
41 | if __name__ == "__main__":
42 | unittest.main()
43 |
--------------------------------------------------------------------------------
/common/project_id.py:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Support for Project ID for v1alpha Chronicle API calls."""
16 | import argparse
17 |
18 |
19 | def add_argument_project_id(parser: argparse.ArgumentParser):
20 | """Adds a shared command-line argument to all the sample modules."""
21 | parser.add_argument(
22 | "-p", "--project_id", type=str, required=True,
23 | help="Your BYOP, project id",
24 | )
25 |
--------------------------------------------------------------------------------
/common/project_instance.py:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Support for Project INSTANCE for v1alpha Chronicle API calls."""
16 |
17 | import argparse
18 |
19 |
20 | def add_argument_project_instance(parser: argparse.ArgumentParser):
21 | """Adds a shared command-line argument to all the sample modules."""
22 | parser.add_argument(
23 | "-i",
24 | "--project_instance",
25 | type=str,
26 | required=True,
27 | help="Customer ID for Chronicle instance",
28 | )
29 |
--------------------------------------------------------------------------------
/common/regions.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Support for regional URLs in all Chronicle API calls.
16 |
17 | For backward compatibility, the US region is considered as the default.
18 | """
19 |
20 | import argparse
21 |
22 | REGION_LIST = (
23 | "asia-northeast1",
24 | "asia-south1",
25 | "asia-southeast1",
26 | "australia-southeast1",
27 | "eu",
28 | "europe",
29 | "europe-west12",
30 | "europe-west2",
31 | "europe-west3",
32 | "europe-west6",
33 | "europe-west9",
34 | "me-central1",
35 | "me-central2",
36 | "me-west1",
37 | "northamerica-northeast2",
38 | "southamerica-east1",
39 | "us",
40 | )
41 |
42 |
43 | def add_argument_region(parser: argparse.ArgumentParser):
44 | """Adds a shared command-line argument to all the sample modules."""
45 | parser.add_argument(
46 | "-r",
47 | "--region",
48 | type=str,
49 | required=False,
50 | default="us",
51 | choices=REGION_LIST,
52 | help="the region where the customer is located (default: us)",
53 | )
54 |
55 |
56 | def url(base_url: str, region: str) -> str:
57 | """Returns a regionalized URL based on the default and the given region."""
58 | if region != "us":
59 | base_url = base_url.replace("https://", f"https://{region}-")
60 | return base_url
61 |
62 |
63 | def url_always_prepend_region(base_url: str, region: str) -> str:
64 | """Returns a regionalized URL.
65 |
66 | Args:
67 | base_url: URL pointing to Chronicle API
68 | region: region in which the target project is located
69 |
70 | Returns:
71 | A string containing a regionalized URL. Unlike the url() function,
72 | this function always prepends region; this function also checks whether
73 | the URL already has the region prefix, and if so, returns the URL unchanged.
74 | v1alpha samples should use this function.
75 | """
76 | if not base_url.startswith(f"https://{region}-"):
77 | base_url = base_url.replace("https://", f"https://{region}-")
78 | return base_url
79 |
--------------------------------------------------------------------------------
/common/regions_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "regions" module."""
16 |
17 | import unittest
18 |
19 | from . import regions
20 |
21 |
22 | class RegionsTest(unittest.TestCase):
23 |
24 | def test_url_asia_southeast1(self):
25 | self.assertEqual(
26 | regions.url("https://test", "asia-southeast1"),
27 | "https://asia-southeast1-test")
28 |
29 | def test_url_eu(self):
30 | self.assertEqual(
31 | regions.url("https://test", "eu"), "https://eu-test")
32 |
33 | def test_url_us(self):
34 | self.assertEqual(regions.url("https://test", "us"), "https://test")
35 |
36 | def test_url_always_prepend_region_us(self):
37 | self.assertEqual(
38 | regions.url_always_prepend_region("https://test", "us"),
39 | "https://us-test",
40 | )
41 |
42 | def test_url_always_prepend_region_e(self):
43 | self.assertEqual(
44 | regions.url_always_prepend_region("https://test", "eu"),
45 | "https://eu-test",
46 | )
47 |
48 | def test_url_always_prepend_region_twice(self):
49 | url_once = regions.url_always_prepend_region("https://test", "eu")
50 | url_twice = regions.url_always_prepend_region(url_once, "eu")
51 | self.assertEqual(
52 | "https://eu-test",
53 | url_twice,
54 | )
55 |
56 |
57 | if __name__ == "__main__":
58 | unittest.main()
59 |
--------------------------------------------------------------------------------
/datatap/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/datatap/delete_datatap.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for deleting a Datatap.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/preview/datatap-config/datatapconfig-api?hl=en#delete
21 | """
22 |
23 | import argparse
24 | import json
25 | import sys
26 | from typing import Any, Mapping
27 | from typing import Optional
28 | from typing import Sequence
29 |
30 | from google.auth.transport import requests
31 |
32 | from common import chronicle_auth
33 | from common import regions
34 |
35 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
36 |
37 |
38 | def initialize_command_line_args(
39 | args: Optional[Sequence[str]] = None) -> Optional[argparse.Namespace]:
40 | """Initializes and checks all the command-line arguments."""
41 | parser = argparse.ArgumentParser()
42 | chronicle_auth.add_argument_credentials_file(parser)
43 | regions.add_argument_region(parser)
44 | parser.add_argument(
45 | "-id", "--tapId", type=str, required=True, help="tap Id")
46 |
47 | return parser.parse_args(args)
48 |
49 |
50 | def delete_datatap(http_session: requests.AuthorizedSession,
51 | tap_id: str) -> Mapping[str, Sequence[Any]]:
52 | """Deletes the given datatap.
53 |
54 | Args:
55 | http_session: Authorized session for HTTP requests.
56 | tap_id: unique datatap Id returned on Datatap creation.
57 |
58 | Returns:
59 | Empty with 200 if success.
60 |
61 | Raises:
62 | requests.exceptions.HTTPError: HTTP request resulted in an error
63 | (response.status_code >= 400).
64 | """
65 | url = f"{CHRONICLE_API_BASE_URL}/v1/dataTaps/{tap_id}"
66 |
67 | response = http_session.request("DELETE", url)
68 |
69 | if response.status_code >= 400:
70 | print(response.text)
71 | response.raise_for_status()
72 | return response.json()
73 |
74 |
75 | if __name__ == "__main__":
76 | cli = initialize_command_line_args()
77 | if not cli:
78 | sys.exit(1) # A sanity check failed.
79 |
80 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, cli.region)
81 | session = chronicle_auth.initialize_http_session(cli.credentials_file)
82 | print(
83 | json.dumps(
84 | delete_datatap(session, cli.tapId),
85 | indent=2))
86 |
--------------------------------------------------------------------------------
/datatap/delete_datatap_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "delete_datatap" module."""
16 |
17 | import unittest
18 | import argparse
19 |
20 | from unittest import mock
21 |
22 | from google.auth.transport import requests
23 |
24 | from . import delete_datatap
25 |
26 |
27 | class DeleteDatatapTest(unittest.TestCase):
28 |
29 | def test_initialize_command_line_args(self):
30 | actual = delete_datatap.initialize_command_line_args(
31 | ["--tapId=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"])
32 | self.assertEqual(
33 | actual,
34 | argparse.Namespace(
35 | credentials_file=None,
36 | tapId="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
37 | region="us"))
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_delete_datatap_error(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
44 | mock_response.raise_for_status.side_effect = (
45 | requests.requests.exceptions.HTTPError())
46 |
47 | with self.assertRaises(requests.requests.exceptions.HTTPError):
48 | delete_datatap.delete_datatap(mock_session, "")
49 |
50 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
51 | @mock.patch.object(requests.requests, "Response", autospec=True)
52 | def test_delete_datatap(self, mock_response, mock_session):
53 | mock_session.request.return_value = mock_response
54 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
55 | tap_id = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
56 | expected = {}
57 |
58 | mock_response.json.return_value = expected
59 | actual = delete_datatap.delete_datatap(mock_session, tap_id)
60 | self.assertEqual(actual, expected)
61 |
62 |
63 | if __name__ == "__main__":
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/datatap/get_datatap_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "get_datatap" module."""
16 |
17 | import unittest
18 | import argparse
19 |
20 | from unittest import mock
21 |
22 | from google.auth.transport import requests
23 |
24 | from . import get_datatap
25 |
26 |
27 | class GetDatatapTest(unittest.TestCase):
28 |
29 | def test_initialize_command_line_args(self):
30 | actual = get_datatap.initialize_command_line_args(
31 | ["--tapId=aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"])
32 | self.assertEqual(
33 | actual,
34 | argparse.Namespace(
35 | credentials_file=None,
36 | tapId="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
37 | region="us"))
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_get_datatap_error(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
44 | mock_response.raise_for_status.side_effect = (
45 | requests.requests.exceptions.HTTPError())
46 |
47 | with self.assertRaises(requests.requests.exceptions.HTTPError):
48 | get_datatap.get_datatap(mock_session, "")
49 |
50 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
51 | @mock.patch.object(requests.requests, "Response", autospec=True)
52 | def test_get_datatap(self, mock_response, mock_session):
53 | mock_session.request.return_value = mock_response
54 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
55 | tap_id = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
56 | expected = {
57 | "customerId": "cccccccc-cccc-cccc-cccc-cccccccccccc",
58 | "tapId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
59 | "displayName": "tap1",
60 | "cloudPubsubSink": {
61 | "topic": "projects/sample-project/topics/sample-topic",
62 | "filter": "ALL_UDM_EVENTS"
63 | }
64 | }
65 |
66 | mock_response.json.return_value = expected
67 | actual = get_datatap.get_datatap(mock_session, tap_id)
68 | self.assertEqual(actual, expected)
69 |
70 |
71 | if __name__ == "__main__":
72 | unittest.main()
73 |
--------------------------------------------------------------------------------
/datatap/list_datatap_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_datatap" module."""
16 |
17 | import unittest
18 | import argparse
19 |
20 | from unittest import mock
21 |
22 | from google.auth.transport import requests
23 |
24 | from . import list_datatap
25 |
26 |
27 | class ListDatatapTest(unittest.TestCase):
28 |
29 | def test_initialize_command_line_args(self):
30 | actual = list_datatap.initialize_command_line_args([])
31 | self.assertEqual(
32 | actual,
33 | argparse.Namespace(
34 | credentials_file=None,
35 | region="us"))
36 |
37 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
38 | @mock.patch.object(requests.requests, "Response", autospec=True)
39 | def test_list_datatap_error(self, mock_response, mock_session):
40 | mock_session.request.return_value = mock_response
41 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
42 | mock_response.raise_for_status.side_effect = (
43 | requests.requests.exceptions.HTTPError())
44 |
45 | with self.assertRaises(requests.requests.exceptions.HTTPError):
46 | list_datatap.list_datatap(mock_session)
47 |
48 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
49 | @mock.patch.object(requests.requests, "Response", autospec=True)
50 | def test_list_datatap(self, mock_response, mock_session):
51 | mock_session.request.return_value = mock_response
52 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
53 | expected = {
54 | "dataTaps": [{
55 | "customerId": "cccccccc-cccc-cccc-cccc-cccccccccccc",
56 | "tapId": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
57 | "displayName": "tap1",
58 | "filter": "ALL_UDM_EVENTS",
59 | "cloudPubsubSink": {
60 | "topic": "projects/sample-project/topics/sample-topic",
61 | }
62 | }]
63 | }
64 |
65 | mock_response.json.return_value = expected
66 | actual = list_datatap.list_datatap(mock_session)
67 | self.assertEqual(actual, expected)
68 |
69 |
70 | if __name__ == "__main__":
71 | unittest.main()
72 |
--------------------------------------------------------------------------------
/detect/v1alpha/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2024 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/detect/v2/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/detect/v2/archive_rule.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for archiving a detection rule."""
18 |
19 | import argparse
20 |
21 | from google.auth.transport import requests
22 |
23 | from common import chronicle_auth
24 | from common import regions
25 |
26 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
27 |
28 |
29 | def archive_rule(http_session: requests.AuthorizedSession, version_id: str):
30 | """Archives a detection rule.
31 |
32 | Archiving a rule will fail if:
33 | - The provided version is not the latest rule version
34 | - The rule is enabled as live
35 | - The rule has retrohunts in progress
36 |
37 | If alerting is enabled for a rule, archiving the rule will automatically
38 | disable alerting for the rule.
39 |
40 | Args:
41 | http_session: Authorized session for HTTP requests.
42 | version_id: Unique ID of the detection rule to archive ("ru_" or
43 | "ru_@v__"). If a version suffix isn't
44 | specified we use the rule's latest version.
45 |
46 | Raises:
47 | requests.exceptions.HTTPError: HTTP request resulted in an error
48 | (response.status_code >= 400).
49 | """
50 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules/{version_id}:archive"
51 |
52 | response = http_session.request("POST", url)
53 | # Expected server response:
54 | # {}
55 |
56 | if response.status_code >= 400:
57 | print(response.text)
58 | response.raise_for_status()
59 |
60 |
61 | if __name__ == "__main__":
62 | parser = argparse.ArgumentParser()
63 | chronicle_auth.add_argument_credentials_file(parser)
64 | regions.add_argument_region(parser)
65 | parser.add_argument(
66 | "-vi",
67 | "--version_id",
68 | type=str,
69 | required=True,
70 | help="version ID ('ru_[@v__]')")
71 |
72 | args = parser.parse_args()
73 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
74 | session = chronicle_auth.initialize_http_session(args.credentials_file)
75 | archive_rule(session, args.version_id)
76 |
--------------------------------------------------------------------------------
/detect/v2/archive_rule_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "archive_rule" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import archive_rule
23 |
24 |
25 | class ArchiveRuleTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | archive_rule.archive_rule(mock_session,
37 | "ru_12345678-1234-1234-1234-1234567890ab")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 |
45 | archive_rule.archive_rule(mock_session,
46 | "ru_12345678-1234-1234-1234-1234567890ab")
47 |
48 |
49 | if __name__ == "__main__":
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/detect/v2/cancel_retrohunt.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for cancelling a retrohunt.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/reference/detection-engine-api#cancelretrohunt
21 | """
22 |
23 | import argparse
24 |
25 | from google.auth.transport import requests
26 |
27 | from common import chronicle_auth
28 | from common import regions
29 |
30 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
31 |
32 |
33 | def cancel_retrohunt(http_session: requests.AuthorizedSession, version_id: str,
34 | retrohunt_id: str):
35 | """Cancel a specific retrohunt.
36 |
37 | Args:
38 | http_session: Authorized session for HTTP requests.
39 | version_id: Unique ID of the detection rule to cancel a retrohunt for
40 | ("ru_" or "ru_@v__"). If a version
41 | suffix isn't specified we use the rule's latest version.
42 | retrohunt_id: Id of the retrohunt to cancel.
43 |
44 | Raises:
45 | requests.exceptions.HTTPError: HTTP request resulted in an error
46 | (response.status_code >= 400).
47 | """
48 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules/{version_id}/retrohunts/{retrohunt_id}:cancelRetrohunt"
49 |
50 | response = http_session.request("POST", url)
51 | # Expected server response:
52 | # {}
53 |
54 | if response.status_code >= 400:
55 | print(response.text)
56 | response.raise_for_status()
57 |
58 |
59 | if __name__ == "__main__":
60 | parser = argparse.ArgumentParser()
61 | chronicle_auth.add_argument_credentials_file(parser)
62 | regions.add_argument_region(parser)
63 | parser.add_argument(
64 | "-vi",
65 | "--version_id",
66 | type=str,
67 | required=True,
68 | help="version ID ('ru_[@v__]')")
69 | parser.add_argument(
70 | "-ri",
71 | "--retrohunt_id",
72 | type=str,
73 | required=True,
74 | help="retrohunt ID (for Retrohunts: 'oh_')")
75 |
76 | args = parser.parse_args()
77 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
78 | session = chronicle_auth.initialize_http_session(args.credentials_file)
79 | cancel_retrohunt(session, args.version_id, args.retrohunt_id)
80 |
--------------------------------------------------------------------------------
/detect/v2/cancel_retrohunt_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "cancel_retrohunt" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import cancel_retrohunt
23 |
24 |
25 | class CancelRetrohuntTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | cancel_retrohunt.cancel_retrohunt(
37 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab",
38 | "oh_12345678-1234-1234-1234-1234567890ab")
39 |
40 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
41 | @mock.patch.object(requests.requests, "Response", autospec=True)
42 | def test_happy_path(self, mock_response, mock_session):
43 | mock_session.request.return_value = mock_response
44 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
45 |
46 | cancel_retrohunt.cancel_retrohunt(
47 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab",
48 | "oh_12345678-1234-1234-1234-1234567890ab")
49 |
50 |
51 | if __name__ == "__main__":
52 | unittest.main()
53 |
--------------------------------------------------------------------------------
/detect/v2/create_rule_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "create_rule" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import create_rule
23 |
24 |
25 | class CreateRuleTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | create_rule.create_rule(mock_session, "new rule content")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_rule = {
44 | "ruleId": "ru_12345678-1234-1234-1234-1234567890ab",
45 | "rule": "rule content",
46 | "metadata": {
47 | "author": "first_author",
48 | "contributor": "first_contributor",
49 | },
50 | }
51 | mock_response.json.return_value = expected_rule
52 |
53 | actual_rule = create_rule.create_rule(mock_session, "new rule content")
54 | self.assertEqual(actual_rule, expected_rule)
55 |
56 |
57 | if __name__ == "__main__":
58 | unittest.main()
59 |
--------------------------------------------------------------------------------
/detect/v2/create_rule_version_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "create_rule_version" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import create_rule_version
23 |
24 |
25 | class CreateRuleVersionTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | create_rule_version.create_rule_version(
37 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab",
38 | "new rule content")
39 |
40 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
41 | @mock.patch.object(requests.requests, "Response", autospec=True)
42 | def test_happy_path(self, mock_response, mock_session):
43 | mock_session.request.return_value = mock_response
44 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
45 | expected_version_id = "ru_12345678-1234-1234-1234-1234567890ab@v_100000_000000"
46 | expected_rule = {
47 | "ruleId": "ru_12345678-1234-1234-1234-1234567890ab",
48 | "versionId": expected_version_id,
49 | "rule": "rule content",
50 | "metadata": {
51 | "author": "first_author",
52 | "contributor": "first_contributor",
53 | },
54 | }
55 | mock_response.json.return_value = expected_rule
56 |
57 | actual_version_id = create_rule_version.create_rule_version(
58 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab",
59 | "new rule content")
60 | self.assertEqual(actual_version_id, expected_version_id)
61 |
62 |
63 | if __name__ == "__main__":
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/detect/v2/delete_rule.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for deleting a detection rule.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/reference/detection-engine-api#deleterule
21 | """
22 |
23 | import argparse
24 |
25 | from google.auth.transport import requests
26 |
27 | from common import chronicle_auth
28 | from common import regions
29 |
30 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
31 |
32 |
33 | def delete_rule(http_session: requests.AuthorizedSession, rule_id: str):
34 | """Delete a specific detection rule.
35 |
36 | Args:
37 | http_session: Authorized session for HTTP requests.
38 | rule_id: Unique ID of the detection rule to delete ("ru_"). It does
39 | not accept version id format ("ru_@v__").
40 |
41 | Raises:
42 | requests.exceptions.HTTPError: HTTP request resulted in an error
43 | (response.status_code >= 400).
44 | """
45 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules/{rule_id}"
46 |
47 | response = http_session.request("DELETE", url)
48 | # Expected server response:
49 | # {}
50 |
51 | if response.status_code >= 400:
52 | print(response.text)
53 | response.raise_for_status()
54 |
55 |
56 | if __name__ == "__main__":
57 | parser = argparse.ArgumentParser()
58 | chronicle_auth.add_argument_credentials_file(parser)
59 | regions.add_argument_region(parser)
60 | parser.add_argument(
61 | "-ri", "--rule_id", type=str, required=True, help="rule ID ('ru_')")
62 |
63 | args = parser.parse_args()
64 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
65 | session = chronicle_auth.initialize_http_session(args.credentials_file)
66 | delete_rule(session, args.rule_id)
67 |
--------------------------------------------------------------------------------
/detect/v2/delete_rule_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "delete_rule" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import delete_rule
23 |
24 |
25 | class DeleteRuleTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | delete_rule.delete_rule(mock_session,
37 | "ru_12345678-1234-1234-1234-1234567890ab")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 |
45 | delete_rule.delete_rule(mock_session,
46 | "ru_12345678-1234-1234-1234-1234567890ab")
47 |
48 |
49 | if __name__ == "__main__":
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/detect/v2/disable_alerting.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for disabling alerting for a rule.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/reference/detection-engine-api#disablealerting
21 | """
22 |
23 | import argparse
24 |
25 | from google.auth.transport import requests
26 |
27 | from common import chronicle_auth
28 | from common import regions
29 |
30 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
31 |
32 |
33 | def disable_alerting(http_session: requests.AuthorizedSession, rule_id: str):
34 | """Disables alerting for a detection rule.
35 |
36 | Args:
37 | http_session: Authorized session for HTTP requests.
38 | rule_id: Unique ID of the detection rule to disable alerting for
39 | ("ru_"). A version suffix should not be provided, because alerting
40 | is set for a detection rule, not specific version of the rule.
41 |
42 | Raises:
43 | requests.exceptions.HTTPError: HTTP request resulted in an error
44 | (response.status_code >= 400).
45 | """
46 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules/{rule_id}:disableAlerting"
47 |
48 | response = http_session.request("POST", url)
49 | # Expected server response:
50 | # {}
51 |
52 | if response.status_code >= 400:
53 | print(response.text)
54 | response.raise_for_status()
55 |
56 |
57 | if __name__ == "__main__":
58 | parser = argparse.ArgumentParser()
59 | chronicle_auth.add_argument_credentials_file(parser)
60 | regions.add_argument_region(parser)
61 | parser.add_argument(
62 | "-ri", "--rule_id", type=str, required=True, help="rule ID ('ru_')")
63 |
64 | args = parser.parse_args()
65 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
66 | session = chronicle_auth.initialize_http_session(args.credentials_file)
67 | disable_alerting(session, args.rule_id)
68 |
--------------------------------------------------------------------------------
/detect/v2/disable_alerting_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "disable_alerting" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import disable_alerting
23 |
24 |
25 | class DisableAlertingTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | disable_alerting.disable_alerting(
37 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 |
45 | disable_alerting.disable_alerting(
46 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
47 |
48 |
49 | if __name__ == "__main__":
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/detect/v2/disable_live_rule.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for disabling a live rule.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/reference/detection-engine-api#disableliverule
21 | """
22 |
23 | import argparse
24 |
25 | from google.auth.transport import requests
26 |
27 | from common import chronicle_auth
28 | from common import regions
29 |
30 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
31 |
32 |
33 | def disable_live_rule(http_session: requests.AuthorizedSession, rule_id: str):
34 | """Disables a detection rule that is currently enabled as live.
35 |
36 | If a version of a detection rule is enabled as live, then is updated with a
37 | new version, the following happens automatically:
38 | - The old version is disabled.
39 | - The new version is enabled as live.
40 |
41 | Args:
42 | http_session: Authorized session for HTTP requests.
43 | rule_id: Unique ID of the detection rule to disable ("ru_"). A version
44 | suffix should not be provided, because at most one version of a detection
45 | rule (by default the latest version of a rule) can be enabled at a time.
46 |
47 | Raises:
48 | requests.exceptions.HTTPError: HTTP request resulted in an error
49 | (response.status_code >= 400).
50 | """
51 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules/{rule_id}:disableLiveRule"
52 |
53 | response = http_session.request("POST", url)
54 | # Expected server response:
55 | # {}
56 |
57 | if response.status_code >= 400:
58 | print(response.text)
59 | response.raise_for_status()
60 |
61 |
62 | if __name__ == "__main__":
63 | parser = argparse.ArgumentParser()
64 | chronicle_auth.add_argument_credentials_file(parser)
65 | regions.add_argument_region(parser)
66 | parser.add_argument(
67 | "-ri", "--rule_id", type=str, required=True, help="rule ID ('ru_')")
68 |
69 | args = parser.parse_args()
70 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
71 | session = chronicle_auth.initialize_http_session(args.credentials_file)
72 | disable_live_rule(session, args.rule_id)
73 |
--------------------------------------------------------------------------------
/detect/v2/disable_live_rule_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "disable_live_rule" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import disable_live_rule
23 |
24 |
25 | class DisableLiveRuleTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | disable_live_rule.disable_live_rule(
37 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 |
45 | disable_live_rule.disable_live_rule(
46 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
47 |
48 |
49 | if __name__ == "__main__":
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/detect/v2/enable_alerting.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for enabling alerting for a detection rule.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/reference/detection-engine-api#enablealerting
21 | """
22 |
23 | import argparse
24 |
25 | from google.auth.transport import requests
26 |
27 | from common import chronicle_auth
28 | from common import regions
29 |
30 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
31 |
32 |
33 | def enable_alerting(http_session: requests.AuthorizedSession, rule_id: str):
34 | """Enable alerting for a specific detection rule.
35 |
36 | Args:
37 | http_session: Authorized session for HTTP requests.
38 | rule_id: Unique ID of the detection rule to enable alerting for
39 | ("ru_"). It does not accept version id format
40 | ("ru_@v__").
41 |
42 | Raises:
43 | requests.exceptions.HTTPError: HTTP request resulted in an error
44 | (response.status_code >= 400).
45 | """
46 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules/{rule_id}:enableAlerting"
47 |
48 | response = http_session.request("POST", url)
49 | # Expected server response:
50 | # {}
51 |
52 | if response.status_code >= 400:
53 | print(response.text)
54 | response.raise_for_status()
55 |
56 |
57 | if __name__ == "__main__":
58 | parser = argparse.ArgumentParser()
59 | chronicle_auth.add_argument_credentials_file(parser)
60 | regions.add_argument_region(parser)
61 | parser.add_argument(
62 | "-ri", "--rule_id", type=str, required=True, help="rule ID ('ru_')")
63 |
64 | args = parser.parse_args()
65 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
66 | session = chronicle_auth.initialize_http_session(args.credentials_file)
67 | enable_alerting(session, args.rule_id)
68 |
--------------------------------------------------------------------------------
/detect/v2/enable_alerting_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "enable_alerting" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import enable_alerting
23 |
24 |
25 | class EnableAlertingTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | enable_alerting.enable_alerting(mock_session,
37 | "ru_12345678-1234-1234-1234-1234567890ab")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 |
45 | enable_alerting.enable_alerting(mock_session,
46 | "ru_12345678-1234-1234-1234-1234567890ab")
47 |
48 |
49 | if __name__ == "__main__":
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/detect/v2/enable_live_rule.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for enabling a live detection rule.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/reference/detection-engine-api#enableliverule
21 | """
22 |
23 | import argparse
24 |
25 | from google.auth.transport import requests
26 |
27 | from common import chronicle_auth
28 | from common import regions
29 |
30 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
31 |
32 |
33 | def enable_live_rule(http_session: requests.AuthorizedSession, rule_id: str):
34 | """Enables a detection rule as live.
35 |
36 | The rule will run continuously against all *new* logs that your organization
37 | has that fall after the time the rule was enabled as live.
38 |
39 | To stop the rule from running, you can call the corresponding
40 | "disable_live_rule" action.
41 |
42 | If a version of a detection rule is enabled as live, then is updated with a
43 | new version, the following happens automatically:
44 | - The old version is disabled.
45 | - The new version is enabled as live.
46 |
47 | Args:
48 | http_session: Authorized session for HTTP requests.
49 | rule_id: Unique ID of the detection rule to enable ("ru_"). A version
50 | suffix should not be provided, because at most one version of a detection
51 | rule (by default the latest version of a rule) can be enabled at a time.
52 |
53 | Raises:
54 | requests.exceptions.HTTPError: HTTP request resulted in an error
55 | (response.status_code >= 400).
56 | """
57 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules/{rule_id}:enableLiveRule"
58 | response = http_session.request("POST", url)
59 | # Expected server response:
60 | # {}
61 |
62 | if response.status_code >= 400:
63 | print(response.text)
64 | response.raise_for_status()
65 |
66 |
67 | if __name__ == "__main__":
68 | parser = argparse.ArgumentParser()
69 | chronicle_auth.add_argument_credentials_file(parser)
70 | regions.add_argument_region(parser)
71 | parser.add_argument(
72 | "-ri", "--rule_id", type=str, required=True, help="rule ID ('ru_')")
73 |
74 | args = parser.parse_args()
75 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
76 | session = chronicle_auth.initialize_http_session(args.credentials_file)
77 | enable_live_rule(session, args.rule_id)
78 |
--------------------------------------------------------------------------------
/detect/v2/enable_live_rule_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "enable_live_rule" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import enable_live_rule
23 |
24 |
25 | class EnableLiveRuleTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | enable_live_rule.enable_live_rule(
37 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 |
45 | enable_live_rule.enable_live_rule(
46 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
47 |
48 |
49 | if __name__ == "__main__":
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/detect/v2/get_error.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for retrieving an error.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/reference/detection-engine-api#geterror
21 | """
22 |
23 | import argparse
24 | import json
25 | from typing import Any, Mapping
26 |
27 | from google.auth.transport import requests
28 |
29 | from common import chronicle_auth
30 | from common import regions
31 |
32 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
33 |
34 |
35 | def get_error(http_session: requests.AuthorizedSession,
36 | error_id: str) -> Mapping[str, Any]:
37 | """Get error.
38 |
39 | Args:
40 | http_session: Authorized session for HTTP requests.
41 | error_id: Id of the error to get information for.
42 |
43 | Returns:
44 | Detection information.
45 |
46 | Raises:
47 | requests.exceptions.HTTPError: HTTP request resulted in an error
48 | (response.status_code >= 400).
49 | """
50 | url = f"{CHRONICLE_API_BASE_URL}/v2/health/errors/{error_id}"
51 |
52 | response = http_session.request("GET", url)
53 | # Expected server response:
54 | # {
55 | # "category": "RULES_EXECUTION_ERROR",
56 | # "errorId": "ed_",
57 | # "errorTime": "yyyy-mm-ddThh:mm:ssZ",
58 | # "ruleExecution": {
59 | # "ruleId": "ru_",
60 | # "versionId": "ru_@v__",
61 | # "windowEndTime": "yyyy-mm-ddThh:mm:ssZ",
62 | # "windowStartTime": "yyyy-mm-ddThh:mm:ssZ"
63 | # },
64 | # "text": ""
65 | # }
66 |
67 | if response.status_code >= 400:
68 | print(response.text)
69 | response.raise_for_status()
70 | return response.json()
71 |
72 |
73 | if __name__ == "__main__":
74 | parser = argparse.ArgumentParser()
75 | chronicle_auth.add_argument_credentials_file(parser)
76 | regions.add_argument_region(parser)
77 | parser.add_argument(
78 | "-ei",
79 | "--error_id",
80 | type=str,
81 | required=True,
82 | help="error ID (for Detect errors: 'ed_')")
83 |
84 | args = parser.parse_args()
85 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
86 | session = chronicle_auth.initialize_http_session(args.credentials_file)
87 | error = get_error(session, args.error_id)
88 | print(json.dumps(error, indent=2))
89 |
--------------------------------------------------------------------------------
/detect/v2/get_error_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "get_error" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_error
23 |
24 |
25 | class GetErrorTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | get_error.get_error(mock_session, "")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | error_id = "ed_12345678-1234-1234-1234-1234567890ab"
44 | version_id = "ru_12345678-1234-1234-1234-1234567890ab@v_100000_000000"
45 | expected_error = {
46 | "errorId": error_id,
47 | "text": "something went wrong",
48 | "category": "RULES_EXECUTION_ERROR",
49 | "errorTime": "2020-11-05T00:00:00Z",
50 | "metadata": {
51 | "ruleExecution": {
52 | "windowStartTime": "2020-11-05T00:00:00Z",
53 | "windowEndTime": "2020-11-05T01:00:00Z",
54 | "ruleId": "ru_12345678-1234-1234-1234-1234567890ab",
55 | "versionId": version_id,
56 | },
57 | },
58 | }
59 | mock_response.json.return_value = expected_error
60 |
61 | error = get_error.get_error(mock_session, error_id)
62 | self.assertEqual(error, expected_error)
63 |
64 |
65 | if __name__ == "__main__":
66 | unittest.main()
67 |
--------------------------------------------------------------------------------
/detect/v2/get_retrohunt_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "get_retrohunt" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_retrohunt
23 |
24 |
25 | class GetRetrohuntTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | get_retrohunt.get_retrohunt(mock_session,
37 | "ru_12345678-1234-1234-1234-1234567890ab",
38 | "oh_87654321-4321-4321-4321-ba0987654321")
39 |
40 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
41 | @mock.patch.object(requests.requests, "Response", autospec=True)
42 | def test_happy_path(self, mock_response, mock_session):
43 | mock_session.request.return_value = mock_response
44 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
45 | expected_response = {
46 | "retrohuntId":
47 | "oh_87654321-4321-4321-4321-ba0987654321",
48 | "ruleId":
49 | "ru_12345678-1234-1234-1234-1234567890ab",
50 | "versionId":
51 | "ru_12345678-1234-1234-1234-1234567890ab@v_1234567890_123456789",
52 | "eventStartTime":
53 | "2021-01-01T00:00:00Z",
54 | "eventEndTime":
55 | "2021-01-06T00:00:00Z",
56 | "retrohuntStartTime":
57 | "2021-04-28T00:00:00Z",
58 | "state":
59 | "RUNNING",
60 | "progressPercentage":
61 | 19.63,
62 | }
63 | mock_response.json.return_value = expected_response
64 |
65 | got_response = get_retrohunt.get_retrohunt(
66 | mock_session,
67 | "ru_12345678-1234-1234-1234-1234567890ab",
68 | "oh_87654321-4321-4321-4321-ba0987654321"
69 | )
70 | self.assertEqual(got_response, expected_response)
71 |
72 |
73 | if __name__ == "__main__":
74 | unittest.main()
75 |
--------------------------------------------------------------------------------
/detect/v2/get_rule_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "get_rule" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_rule
23 |
24 |
25 | class GetRuleTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | get_rule.get_rule(mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_rule_content = "rule content"
44 | expected_response = {
45 | "ruleId": "ru_12345678-1234-1234-1234-1234567890ab",
46 | "rule": expected_rule_content,
47 | }
48 | mock_response.json.return_value = expected_response
49 |
50 | got_response = get_rule.get_rule(mock_session,
51 | "ru_12345678-1234-1234-1234-1234567890ab")
52 | self.assertEqual(got_response, expected_response)
53 |
54 |
55 | if __name__ == "__main__":
56 | unittest.main()
57 |
--------------------------------------------------------------------------------
/detect/v2/list_curated_rules_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2023 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_curated_rules" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_curated_rules
23 |
24 |
25 | class ListCuratedRulesTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | list_curated_rules.list_curated_rules(mock_session)
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path_without_page_size(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_rule = {
44 | "ruleId": "ur_sample_rule",
45 | "ruleName": "Sample Rule",
46 | "severity": "Info",
47 | "ruleType": "SINGLE_EVENT",
48 | "precision": "PRECISE",
49 | "tactics": ["TA0042",],
50 | "techniques": ["T1595.001",],
51 | "updateTime": "2023-01-01T00:00:00Z",
52 | "ruleSet": "87654321-4321-4321-4321-ba0987654321",
53 | "description": "Sample Rule Description",
54 | }
55 | expected_page_token = "page token here"
56 | mock_response.json.return_value = {
57 | "curatedRules": [expected_rule],
58 | "nextPageToken": expected_page_token,
59 | }
60 |
61 | rules, next_page_token = list_curated_rules.list_curated_rules(mock_session)
62 | self.assertEqual(len(rules), 1)
63 | self.assertEqual(rules[0], expected_rule)
64 | self.assertEqual(next_page_token, expected_page_token)
65 |
66 |
67 | if __name__ == "__main__":
68 | unittest.main()
69 |
--------------------------------------------------------------------------------
/detect/v2/list_errors_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_errors" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_errors
23 |
24 |
25 | class ListErrorsTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | list_errors.list_errors(mock_session)
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path_without_page_size(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_error = {
44 | "category": "RULES_EXECUTION_ERROR",
45 | "errorId": "ed_12345678-1234-1234-1234-1234567890ab",
46 | "errorTime": "2020-12-09T16:00:00Z",
47 | "ruleExecution": {
48 | "ruleId":
49 | "ru_12345678-1234-1234-1234-1234567890ab",
50 | "versionId":
51 | "ru_12345678-1234-1234-1234-1234567890ab@v_100000_000000",
52 | "windowEndTime":
53 | "2020-12-09T16:00:00Z",
54 | "windowStartTime":
55 | "2020-12-09T15:00:00Z"
56 | },
57 | "text": "rule error text"
58 | "line: 15 \n"
59 | "column: 37-74 "
60 | }
61 | expected_page_token = "page token here"
62 | mock_response.json.return_value = {
63 | "errors": [expected_error],
64 | "nextPageToken": expected_page_token,
65 | }
66 |
67 | errors, next_page_token = list_errors.list_errors(mock_session)
68 | self.assertEqual(len(errors), 1)
69 | self.assertEqual(errors[0], expected_error)
70 | self.assertEqual(next_page_token, expected_page_token)
71 |
72 |
73 | if __name__ == "__main__":
74 | unittest.main()
75 |
--------------------------------------------------------------------------------
/detect/v2/list_rule_versions_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_rule_versions" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_rule_versions
23 |
24 |
25 | class ListRuleVersionsTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | list_rule_versions.list_rule_versions(
37 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path_without_page_size(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 | expected_rule = {
45 | "ruleId": "ru_12345678-1234-1234-1234-1234567890ab",
46 | "versionId": "ru_12345678-1234-1234-1234-1234567890ab@v_100000_000000",
47 | "rule": "rule content",
48 | "metadata": {
49 | "author": "first_author",
50 | "contributor": "first_contributor",
51 | },
52 | }
53 | expected_page_token = "page token here"
54 | mock_response.json.return_value = {
55 | "rules": [expected_rule],
56 | "nextPageToken": expected_page_token,
57 | }
58 |
59 | rules, next_page_token = list_rule_versions.list_rule_versions(
60 | mock_session, "ru_12345678-1234-1234-1234-1234567890ab")
61 | self.assertEqual(len(rules), 1)
62 | self.assertEqual(rules[0], expected_rule)
63 | self.assertEqual(next_page_token, expected_page_token)
64 |
65 |
66 | if __name__ == "__main__":
67 | unittest.main()
68 |
--------------------------------------------------------------------------------
/detect/v2/list_rules_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_rules" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_rules
23 |
24 |
25 | class ListRulesTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | list_rules.list_rules(mock_session)
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path_without_page_size(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_rule = {
44 | "ruleId": "ru_12345678-1234-1234-1234-1234567890ab",
45 | "rule": "rule content",
46 | "metadata": {
47 | "author": "first_author",
48 | "contributor": "first_contributor",
49 | },
50 | }
51 | expected_page_token = "page token here"
52 | mock_response.json.return_value = {
53 | "rules": [expected_rule],
54 | "nextPageToken": expected_page_token,
55 | }
56 |
57 | rules, next_page_token = list_rules.list_rules(mock_session)
58 | self.assertEqual(len(rules), 1)
59 | self.assertEqual(rules[0], expected_rule)
60 | self.assertEqual(next_page_token, expected_page_token)
61 |
62 |
63 | if __name__ == "__main__":
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/detect/v2/unarchive_rule.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for unarchiving a detection rule."""
18 |
19 | import argparse
20 |
21 | from google.auth.transport import requests
22 |
23 | from common import chronicle_auth
24 | from common import regions
25 |
26 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
27 |
28 |
29 | def unarchive_rule(http_session: requests.AuthorizedSession, version_id: str):
30 | """Unarchives a detection rule.
31 |
32 | Unarchiving a rule will fail if the provided version is not the latest rule
33 | version.
34 |
35 | Args:
36 | http_session: Authorized session for HTTP requests.
37 | version_id: Unique ID of the detection rule to unarchive ("ru_" or
38 | "ru_@v__"). If a version suffix isn't
39 | specified we use the rule's latest version.
40 |
41 | Raises:
42 | requests.exceptions.HTTPError: HTTP request resulted in an error
43 | (response.status_code >= 400).
44 | """
45 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules/{version_id}:unarchive"
46 |
47 | response = http_session.request("POST", url)
48 | # Expected server response:
49 | # {}
50 |
51 | if response.status_code >= 400:
52 | print(response.text)
53 | response.raise_for_status()
54 |
55 |
56 | if __name__ == "__main__":
57 | parser = argparse.ArgumentParser()
58 | chronicle_auth.add_argument_credentials_file(parser)
59 | regions.add_argument_region(parser)
60 | parser.add_argument(
61 | "-vi",
62 | "--version_id",
63 | type=str,
64 | required=True,
65 | help="version ID ('ru_[@v__]')")
66 |
67 | args = parser.parse_args()
68 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
69 | session = chronicle_auth.initialize_http_session(args.credentials_file)
70 | unarchive_rule(session, args.version_id)
71 |
--------------------------------------------------------------------------------
/detect/v2/unarchive_rule_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "unarchive_rule" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import unarchive_rule
23 |
24 |
25 | class UnrchiveRuleTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | unarchive_rule.unarchive_rule(mock_session,
37 | "ru_12345678-1234-1234-1234-1234567890ab")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 |
45 | unarchive_rule.unarchive_rule(mock_session,
46 | "ru_12345678-1234-1234-1234-1234567890ab")
47 |
48 |
49 | if __name__ == "__main__":
50 | unittest.main()
51 |
--------------------------------------------------------------------------------
/detect/v2/verify_rule.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for verifying a detection rule.
18 |
19 | API reference:
20 | https://cloud.google.com/chronicle/docs/reference/detection-engine-api#verifyrule
21 | """
22 |
23 | import argparse
24 | import json
25 | from typing import Any, Mapping
26 |
27 | from google.auth.transport import requests
28 |
29 | from common import chronicle_auth
30 | from common import regions
31 |
32 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
33 |
34 |
35 | def verify_rule(http_session: requests.AuthorizedSession,
36 | rule_content: str) -> Mapping[str, Any]:
37 | """Validates that a detection rule is a valid YL2 rule.
38 |
39 | Args:
40 | http_session: Authorized session for HTTP requests.
41 | rule_content: Content of the detection rule.
42 |
43 | Returns:
44 | A compilation error if there is one.
45 |
46 | Raises:
47 | requests.exceptions.HTTPError: HTTP request resulted in an error
48 | (response.status_code >= 400).
49 | """
50 | url = f"{CHRONICLE_API_BASE_URL}/v2/detect/rules:verifyRule"
51 | body = {"rule_text": rule_content}
52 |
53 | response = http_session.request("POST", url, json=body)
54 | # Expected server response:
55 | # {
56 | # "compilationError": "", <-- IFF compilation failed.
57 | # }
58 |
59 | if response.status_code >= 400:
60 | print(response.text)
61 | response.raise_for_status()
62 | return response.json()
63 |
64 |
65 | if __name__ == "__main__":
66 | parser = argparse.ArgumentParser()
67 | chronicle_auth.add_argument_credentials_file(parser)
68 | regions.add_argument_region(parser)
69 | parser.add_argument(
70 | "-f",
71 | "--rule_file",
72 | type=argparse.FileType("r"),
73 | required=True,
74 | # File example: python3 verify_rule.py -f
75 | # STDIN example: cat rule.txt | python3 verify_rule.py -f -
76 | help="path of a file with the rule's content, or - for STDIN")
77 |
78 | args = parser.parse_args()
79 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
80 | session = chronicle_auth.initialize_http_session(args.credentials_file)
81 | resp = verify_rule(session, args.rule_file.read())
82 | print(json.dumps(resp, indent=2))
83 |
--------------------------------------------------------------------------------
/detect/v2/verify_rule_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "verify_rule" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import verify_rule
23 |
24 |
25 | class VerifyRuleTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | verify_rule.verify_rule(mock_session, "new rule content")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_resp = {
44 | "compilationError": "generic::invalid_argument compilation error",
45 | }
46 | mock_response.json.return_value = expected_resp
47 |
48 | actual_rule = verify_rule.verify_rule(mock_session, "new rule content")
49 | self.assertEqual(actual_rule, expected_resp)
50 |
51 |
52 | if __name__ == "__main__":
53 | unittest.main()
54 |
--------------------------------------------------------------------------------
/feeds/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/feeds/create_okta_feed_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "create_okta_feed" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import create_okta_feed
23 |
24 |
25 | class CreateFeedTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | create_okta_feed.create_okta_feed(mock_session, "secret_example",
37 | "hostname.example.com", "my feed name")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 | expected_feed = {
45 | "name": "feeds/cf49ebc5-e7bf-4562-8061-cab43cecba35",
46 | "display_name": "my feed name",
47 | "details": {
48 | "logType": "OKTA",
49 | "feedSourceType": "API",
50 | "oktaSettings": {
51 | "authentication": {
52 | "headerKeyValues": [{
53 | "key": "key_example",
54 | "value": "value_example"
55 | }]
56 | }
57 | },
58 | },
59 | "feedState": "PENDING_ENABLEMENT"
60 | }
61 |
62 | mock_response.json.return_value = expected_feed
63 |
64 | actual_feed = create_okta_feed.create_okta_feed(
65 | mock_session, "secret_example", "hostname.example.com", "my feed name")
66 | self.assertEqual(actual_feed, expected_feed)
67 |
68 |
69 | if __name__ == "__main__":
70 | unittest.main()
71 |
--------------------------------------------------------------------------------
/feeds/create_okta_user_context_feed_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "create_okta_user_context_feed" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import create_okta_user_context_feed
23 |
24 |
25 | class CreateFeedTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | create_okta_user_context_feed.create_okta_user_context_feed(
37 | mock_session, "secret_example", "hostname.example.com",
38 | "my feed name")
39 |
40 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
41 | @mock.patch.object(requests.requests, "Response", autospec=True)
42 | def test_happy_path(self, mock_response, mock_session):
43 | mock_session.request.return_value = mock_response
44 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
45 | expected_feed = {
46 | "name": "feeds/cf49ebc5-e7bf-4562-8061-cab43cecba35",
47 | "display_name": "my feed name",
48 | "details": {
49 | "logType": "OKTA_USER_CONTEXT",
50 | "feedSourceType": "API",
51 | "oktaSettings": {
52 | "authentication": {
53 | "headerKeyValues": [{
54 | "key": "key_example",
55 | "value": "value_example"
56 | }]
57 | }
58 | },
59 | },
60 | "feedState": "PENDING_ENABLEMENT"
61 | }
62 |
63 | mock_response.json.return_value = expected_feed
64 |
65 | actual_feed = create_okta_user_context_feed.create_okta_user_context_feed(
66 | mock_session, "secret_example", "hostname.example.com", "my feed name")
67 | self.assertEqual(actual_feed, expected_feed)
68 |
69 |
70 | if __name__ == "__main__":
71 | unittest.main()
72 |
--------------------------------------------------------------------------------
/feeds/delete_feed.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for deleting a feed."""
18 |
19 | import argparse
20 |
21 | from google.auth.transport import requests
22 |
23 | from common import chronicle_auth
24 | from common import regions
25 |
26 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
27 |
28 |
29 | def delete_feed(http_session: requests.AuthorizedSession, name: str):
30 | """Delete a specific feed.
31 |
32 | Args:
33 | http_session: Authorized session for HTTP requests.
34 | name: Unique name for the feed.
35 |
36 | Raises:
37 | requests.exceptions.HTTPError: HTTP request resulted in an error
38 | (response.status_code >= 400).
39 | """
40 | url = f"{CHRONICLE_API_BASE_URL}/v1/feeds/{name}"
41 |
42 | response = http_session.request("DELETE", url)
43 | # Expected server response:
44 | # {}
45 |
46 | if response.status_code >= 400:
47 | print(response.text)
48 | response.raise_for_status()
49 |
50 |
51 | if __name__ == "__main__":
52 | parser = argparse.ArgumentParser()
53 | chronicle_auth.add_argument_credentials_file(parser)
54 | regions.add_argument_region(parser)
55 | parser.add_argument(
56 | "-n", "--name", type=str, required=True, help="unique name for the feed")
57 |
58 | args = parser.parse_args()
59 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
60 | session = chronicle_auth.initialize_http_session(args.credentials_file)
61 | delete_feed(session, args.name)
62 |
--------------------------------------------------------------------------------
/feeds/delete_feed_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "delete_feed" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import delete_feed
23 |
24 |
25 | class DeleteFeedTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | delete_feed.delete_feed(mock_session, "feed name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 |
44 | delete_feed.delete_feed(mock_session, "feed name")
45 |
46 |
47 | if __name__ == "__main__":
48 | unittest.main()
49 |
--------------------------------------------------------------------------------
/feeds/disable_feed.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for disabling a feed."""
18 |
19 | import argparse
20 |
21 | from google.auth.transport import requests
22 |
23 | from common import chronicle_auth
24 | from common import regions
25 |
26 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
27 |
28 |
29 | def disable_feed(http_session: requests.AuthorizedSession, name: str):
30 | """Disable a specific feed.
31 |
32 | Args:
33 | http_session: Authorized session for HTTP requests.
34 | name: Unique name for the feed.
35 |
36 | Raises:
37 | requests.exceptions.HTTPError: HTTP request resulted in an error
38 | (response.status_code >= 400).
39 | """
40 | url = f"{CHRONICLE_API_BASE_URL}/v1/feeds/{name}:disable"
41 |
42 | response = http_session.request("POST", url)
43 | # Expected server response:
44 | # {}
45 |
46 | if response.status_code >= 400:
47 | print(response.text)
48 | response.raise_for_status()
49 |
50 |
51 | if __name__ == "__main__":
52 | parser = argparse.ArgumentParser()
53 | chronicle_auth.add_argument_credentials_file(parser)
54 | regions.add_argument_region(parser)
55 | parser.add_argument(
56 | "-n", "--name", type=str, required=True, help="unique name for the feed")
57 |
58 | args = parser.parse_args()
59 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
60 | session = chronicle_auth.initialize_http_session(args.credentials_file)
61 | disable_feed(session, args.name)
62 |
--------------------------------------------------------------------------------
/feeds/disable_feed_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "disable_feed" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import disable_feed
23 |
24 |
25 | class DeleteFeedTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | disable_feed.disable_feed(mock_session, "feed name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 |
44 | disable_feed.disable_feed(mock_session, "feed name")
45 |
46 |
47 | if __name__ == "__main__":
48 | unittest.main()
49 |
--------------------------------------------------------------------------------
/feeds/enable_feed.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for enabling a feed."""
18 |
19 | import argparse
20 |
21 | from google.auth.transport import requests
22 |
23 | from common import chronicle_auth
24 | from common import regions
25 |
26 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
27 |
28 |
29 | def enable_feed(http_session: requests.AuthorizedSession, name: str):
30 | """Enable a specific feed.
31 |
32 | Args:
33 | http_session: Authorized session for HTTP requests.
34 | name: Unique name for the feed.
35 |
36 | Raises:
37 | requests.exceptions.HTTPError: HTTP request resulted in an error
38 | (response.status_code >= 400).
39 | """
40 | url = f"{CHRONICLE_API_BASE_URL}/v1/feeds/{name}:enable"
41 |
42 | response = http_session.request("POST", url)
43 | # Expected server response:
44 | # {}
45 |
46 | if response.status_code >= 400:
47 | print(response.text)
48 | response.raise_for_status()
49 |
50 |
51 | if __name__ == "__main__":
52 | parser = argparse.ArgumentParser()
53 | chronicle_auth.add_argument_credentials_file(parser)
54 | regions.add_argument_region(parser)
55 | parser.add_argument(
56 | "-n", "--name", type=str, required=True, help="unique name for the feed")
57 |
58 | args = parser.parse_args()
59 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
60 | session = chronicle_auth.initialize_http_session(args.credentials_file)
61 | enable_feed(session, args.name)
62 |
--------------------------------------------------------------------------------
/feeds/enable_feed_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "enable_feed" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import enable_feed
23 |
24 |
25 | class DeleteFeedTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | enable_feed.enable_feed(mock_session, "feed name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 |
44 | enable_feed.enable_feed(mock_session, "feed name")
45 |
46 |
47 | if __name__ == "__main__":
48 | unittest.main()
49 |
--------------------------------------------------------------------------------
/feeds/get_feed.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for retrieving a feed."""
18 |
19 | import argparse
20 | import json
21 | from typing import Mapping, Any
22 |
23 | from google.auth.transport import requests
24 |
25 | from common import chronicle_auth
26 | from common import regions
27 |
28 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
29 |
30 |
31 | def get_feed(http_session: requests.AuthorizedSession,
32 | name: str) -> Mapping[str, Any]:
33 | """Retrieves a feed.
34 |
35 | Args:
36 | http_session: Authorized session for HTTP requests.
37 | name: Unique name for the feed.
38 |
39 | Returns:
40 | Array containing each line of the feed's content.
41 |
42 | Raises:
43 | requests.exceptions.HTTPError: HTTP request resulted in an error
44 | (response.status_code >= 400).
45 | """
46 | url = f"{CHRONICLE_API_BASE_URL}/v1/feeds/{name}"
47 |
48 | response = http_session.request("GET", url)
49 | # Expected server response:
50 | # {
51 | # "name": "",
52 | # "feedDetails": {
53 | # feedType: "...",
54 | # ...settings...
55 | # },
56 | # "feedState": "ACTIVE / INACTIVE / PENDING_ENABLEMENT",
57 | # }
58 |
59 | if response.status_code >= 400:
60 | print(response.text)
61 | response.raise_for_status()
62 | return response.json()
63 |
64 |
65 | if __name__ == "__main__":
66 | parser = argparse.ArgumentParser()
67 | chronicle_auth.add_argument_credentials_file(parser)
68 | regions.add_argument_region(parser)
69 | parser.add_argument(
70 | "-n",
71 | "--name",
72 | type=str,
73 | required=True,
74 | help="unique name for the feed")
75 |
76 | args = parser.parse_args()
77 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
78 | session = chronicle_auth.initialize_http_session(args.credentials_file)
79 | print(json.dumps(get_feed(session, args.name), indent=2))
80 |
--------------------------------------------------------------------------------
/feeds/get_feed_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "get_feed" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_feed
23 |
24 |
25 | class GetFeedTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | get_feed.get_feed(mock_session, "feed name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_feed = {
44 | "feedDetails": {
45 | "cortexXdrSettings": {
46 | "hostname": "api-XXXX.xdr.XX.paloaltonetworks.com",
47 | },
48 | "feedType": "CORTEX_XDR",
49 | },
50 | "name": "feed name",
51 | }
52 | mock_response.json.return_value = expected_feed
53 |
54 | actual_feed = get_feed.get_feed(mock_session, "feed name")
55 | self.assertEqual(actual_feed, expected_feed)
56 |
57 |
58 | if __name__ == "__main__":
59 | unittest.main()
60 |
--------------------------------------------------------------------------------
/feeds/list_feeds.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for retrieving a list of feeds."""
18 |
19 | import argparse
20 | import json
21 | from typing import Mapping, Any
22 |
23 | from google.auth.transport import requests
24 |
25 | from common import chronicle_auth
26 | from common import regions
27 |
28 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
29 |
30 |
31 | def list_feeds(http_session: requests.AuthorizedSession) -> Mapping[str, Any]:
32 | """Retrieves all feeds for the tenant.
33 |
34 | Args:
35 | http_session: Authorized session for HTTP requests.
36 |
37 | Returns:
38 | Array containing each line of the feed's content.
39 |
40 | Raises:
41 | requests.exceptions.HTTPError: HTTP request resulted in an error
42 | (response.status_code >= 400).
43 | """
44 | url = f"{CHRONICLE_API_BASE_URL}/v1/feeds"
45 |
46 | response = http_session.request("GET", url)
47 | # Expected server response:
48 | # {
49 | # "feeds": [
50 | # {
51 | # "name": "feeds/19e82867-ab6d-4955-b9c8-bd4aee189439",
52 | # "details": {
53 | # "logType": "AZURE_AD_CONTEXT",
54 | # "feedSourceType": "API",
55 | # "azureAdContextSettings": {}
56 | # },
57 | # "feedState": "INACTIVE"
58 | # },
59 | # {
60 | # "name": "feeds/cdc096a5-93a8-4854-94d9-c05cf0c14d47",
61 | # "details": {
62 | # "logType": "PAN_PRISMA_CLOUD",
63 | # "feedSourceType": "API",
64 | # "panPrismaCloudSettings": {
65 | # "hostname": "api2.prismacloud.io"
66 | # }
67 | # },
68 | # "feedState": "ACTIVE"
69 | # }
70 | # ]
71 | # }
72 |
73 | if response.status_code >= 400:
74 | print(response.text)
75 | response.raise_for_status()
76 | return response.json()
77 |
78 |
79 | if __name__ == "__main__":
80 | parser = argparse.ArgumentParser()
81 | chronicle_auth.add_argument_credentials_file(parser)
82 | regions.add_argument_region(parser)
83 |
84 | args = parser.parse_args()
85 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
86 | session = chronicle_auth.initialize_http_session(args.credentials_file)
87 | print(json.dumps(list_feeds(session), indent=2))
88 |
--------------------------------------------------------------------------------
/feeds/list_feeds_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_feeds" function."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_feeds
23 |
24 |
25 | class ListFeedsTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | list_feeds.list_feeds(mock_session)
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_feeds = {
44 | "feeds": [
45 | {
46 | "name": "feeds/19e82867-ab6d-4955-b9c8-bd4aee189439",
47 | "details": {
48 | "logType": "AZURE_AD_CONTEXT",
49 | "feedSourceType": "API",
50 | "azureAdContextSettings": {}
51 | },
52 | "feedState": "INACTIVE"
53 | },
54 | {
55 | "name": "feeds/cdc096a5-93a8-4854-94d9-c05cf0c14d47",
56 | "details": {
57 | "logType": "PAN_PRISMA_CLOUD",
58 | "feedSourceType": "API",
59 | "panPrismaCloudSettings": {
60 | "hostname": "api2.prismacloud.io"
61 | }
62 | },
63 | "feedState": "ACTIVE"
64 | },
65 | ],
66 | }
67 | mock_response.json.return_value = expected_feeds
68 |
69 | actual_feeds = list_feeds.list_feeds(mock_session)
70 | self.assertEqual(actual_feeds, expected_feeds)
71 |
72 |
73 | if __name__ == "__main__":
74 | unittest.main()
75 |
--------------------------------------------------------------------------------
/forwarders/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/forwarders/create_collector_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "create_collector" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import create_collector
23 |
24 |
25 | class CreateCollectorTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | create_collector.create_collector(mock_session, "forwarder name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_collector = {
44 | "name": "forwarders/uuid/collectors/uuid",
45 | "displayName": "TestCollector1",
46 | "config": {
47 | "logType": "PAN_FIREWALL",
48 | "maxSecondsPerBatch": 10,
49 | "maxBytesPerBatch": "1048576",
50 | "syslogSettings": {
51 | "protocol": "TCP",
52 | "address": "0.0.0.0",
53 | "port": 10514,
54 | "bufferSize": "65536",
55 | "connectionTimeout": 60
56 | }
57 | },
58 | "state": "ACTIVE"
59 | }
60 |
61 | mock_response.json.return_value = expected_collector
62 |
63 | actual_collector = create_collector.create_collector(
64 | mock_session, "forwarder name")
65 | self.assertEqual(actual_collector, expected_collector)
66 |
67 |
68 | if __name__ == "__main__":
69 | unittest.main()
70 |
--------------------------------------------------------------------------------
/forwarders/create_forwarder_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "create_forwarder" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import create_forwarder
23 |
24 |
25 | class CreateForwarderTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | create_forwarder.create_forwarder(mock_session)
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_forwarder = {
44 | "name": "forwarders/c37e1d99-f6ba-43da-b591-788261d32cae",
45 | "displayName": "TestForwarder",
46 | "config": {
47 | "uploadCompression": True
48 | },
49 | "state": "ACTIVE"
50 | }
51 |
52 | mock_response.json.return_value = expected_forwarder
53 |
54 | actual_forwarder = create_forwarder.create_forwarder(mock_session)
55 | self.assertEqual(actual_forwarder, expected_forwarder)
56 |
57 |
58 | if __name__ == "__main__":
59 | unittest.main()
60 |
--------------------------------------------------------------------------------
/forwarders/delete_collector.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for deleting a collector."""
18 |
19 | import argparse
20 |
21 | from google.auth.transport import requests
22 |
23 | from common import chronicle_auth
24 | from common import regions
25 |
26 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
27 |
28 |
29 | def delete_collector(http_session: requests.AuthorizedSession, name: str):
30 | """Delete a specific collector.
31 |
32 | Args:
33 | http_session: Authorized session for HTTP requests.
34 | name: Resource name for the Collector (collectors/{UUID}).
35 |
36 | Raises:
37 | requests.exceptions.HTTPError: HTTP request resulted in an error
38 | (response.status_code >= 400).
39 | """
40 | url = f"{CHRONICLE_API_BASE_URL}/v2/{name}"
41 |
42 | response = http_session.request("DELETE", url)
43 | # Expected server response:
44 | # {}
45 |
46 | if response.status_code >= 400:
47 | print(response.text)
48 | response.raise_for_status()
49 |
50 |
51 | if __name__ == "__main__":
52 | parser = argparse.ArgumentParser()
53 | chronicle_auth.add_argument_credentials_file(parser)
54 | regions.add_argument_region(parser)
55 | parser.add_argument(
56 | "-n",
57 | "--name",
58 | type=str,
59 | required=True,
60 | help="resource name for the Collector (collectors/{UUID})")
61 |
62 | args = parser.parse_args()
63 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
64 | session = chronicle_auth.initialize_http_session(args.credentials_file)
65 | delete_collector(session, args.name)
66 |
--------------------------------------------------------------------------------
/forwarders/delete_collector_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "delete_collector" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import delete_collector
23 |
24 |
25 | class DeleteCollectorTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | delete_collector.delete_collector(mock_session, "collector name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 |
44 | delete_collector.delete_collector(mock_session, "collector name")
45 |
46 |
47 | if __name__ == "__main__":
48 | unittest.main()
49 |
--------------------------------------------------------------------------------
/forwarders/delete_forwarder.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for deleting a forwarder."""
18 |
19 | import argparse
20 |
21 | from google.auth.transport import requests
22 |
23 | from common import chronicle_auth
24 | from common import regions
25 |
26 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
27 |
28 |
29 | def delete_forwarder(http_session: requests.AuthorizedSession, name: str):
30 | """Delete a specific forwarder.
31 |
32 | Args:
33 | http_session: Authorized session for HTTP requests.
34 | name: Resource name for the Forwarder (forwarders/{UUID}).
35 |
36 | Raises:
37 | requests.exceptions.HTTPError: HTTP request resulted in an error
38 | (response.status_code >= 400).
39 | """
40 | url = f"{CHRONICLE_API_BASE_URL}/v2/{name}"
41 |
42 | response = http_session.request("DELETE", url)
43 | # Expected server response:
44 | # {}
45 |
46 | if response.status_code >= 400:
47 | print(response.text)
48 | response.raise_for_status()
49 |
50 |
51 | if __name__ == "__main__":
52 | parser = argparse.ArgumentParser()
53 | chronicle_auth.add_argument_credentials_file(parser)
54 | regions.add_argument_region(parser)
55 | parser.add_argument(
56 | "-n",
57 | "--name",
58 | type=str,
59 | required=True,
60 | help="resource name for the Forwarder (forwarders/{UUID})")
61 |
62 | args = parser.parse_args()
63 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
64 | session = chronicle_auth.initialize_http_session(args.credentials_file)
65 | delete_forwarder(session, args.name)
66 |
--------------------------------------------------------------------------------
/forwarders/delete_forwarder_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "delete_forwarder" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import delete_forwarder
23 |
24 |
25 | class DeleteForwarderTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | delete_forwarder.delete_forwarder(mock_session, "forwarder name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 |
44 | delete_forwarder.delete_forwarder(mock_session, "forwarder name")
45 |
46 |
47 | if __name__ == "__main__":
48 | unittest.main()
49 |
--------------------------------------------------------------------------------
/forwarders/generate_files_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "generate_files" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import generate_files
23 |
24 |
25 | class GenerateFilesTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | generate_files.generate_files(mock_session, "forwarder name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_files = {
44 | "config": "CONFIG FILE CONTENTS",
45 | "auth": "AUTH FILE CONTENTS",
46 | }
47 |
48 | mock_response.json.return_value = expected_files
49 |
50 | actual_files = generate_files.generate_files(mock_session, "forwarder name")
51 | self.assertEqual(actual_files, expected_files)
52 |
53 |
54 | if __name__ == "__main__":
55 | unittest.main()
56 |
--------------------------------------------------------------------------------
/forwarders/get_collector.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for retrieving a collector."""
18 |
19 | import argparse
20 | import json
21 | from typing import Mapping, Any
22 |
23 | from google.auth.transport import requests
24 |
25 | from common import chronicle_auth
26 | from common import regions
27 |
28 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
29 |
30 |
31 | def get_collector(http_session: requests.AuthorizedSession,
32 | name: str) -> Mapping[str, Any]:
33 | """Retrieves a collector.
34 |
35 | Args:
36 | http_session: Authorized session for HTTP requests.
37 | name: Resource name for the Collector (collectors/{UUID}).
38 |
39 | Returns:
40 | Array containing each line of the collector's content.
41 |
42 | Raises:
43 | requests.exceptions.HTTPError: HTTP request resulted in an error
44 | (response.status_code >= 400).
45 | """
46 | url = f"{CHRONICLE_API_BASE_URL}/v2/{name}"
47 |
48 | response = http_session.request("GET", url)
49 | # Example server response:
50 | # {
51 | # "name": "forwarders/{UUID}/collectors/{UUID}",
52 | # "displayName": "SyslogCollector1",
53 | # "config": {
54 | # "logType": "PAN_FIREWALL",
55 | # "maxSecondsPerBatch": 10,
56 | # "maxBytesPerBatch": "1048576",
57 | # "syslogSettings": {
58 | # "protocol": "TCP",
59 | # "address": "0.0.0.0",
60 | # "port": 10514,
61 | # "bufferSize": "65536",
62 | # "connectionTimeout": 60
63 | # }
64 | # },
65 | # "state": "ACTIVE"
66 | # }
67 |
68 | if response.status_code >= 400:
69 | print(response.text)
70 | response.raise_for_status()
71 | return response.json()
72 |
73 |
74 | if __name__ == "__main__":
75 | parser = argparse.ArgumentParser()
76 | chronicle_auth.add_argument_credentials_file(parser)
77 | regions.add_argument_region(parser)
78 | parser.add_argument(
79 | "-n",
80 | "--name",
81 | type=str,
82 | required=True,
83 | help="resource name for the Collector (collectors/{UUID})")
84 |
85 | args = parser.parse_args()
86 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
87 | session = chronicle_auth.initialize_http_session(args.credentials_file)
88 | print(json.dumps(get_collector(session, args.name), indent=2))
89 |
--------------------------------------------------------------------------------
/forwarders/get_collector_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "get_collector" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_collector
23 |
24 |
25 | class GetCollectorTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | get_collector.get_collector(mock_session, "collector name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_collector = {
44 | "name": "forwarders/{UUID}/collectors/{UUID}",
45 | "displayName": "SyslogCollector1",
46 | "config": {
47 | "logType": "PAN_FIREWALL",
48 | "maxSecondsPerBatch": 10,
49 | "maxBytesPerBatch": "1048576",
50 | "syslogSettings": {
51 | "protocol": "TCP",
52 | "address": "0.0.0.0",
53 | "port": 10514,
54 | "bufferSize": "65536",
55 | "connectionTimeout": 60
56 | }
57 | },
58 | "state": "ACTIVE"
59 | }
60 |
61 | mock_response.json.return_value = expected_collector
62 |
63 | actual_collector = get_collector.get_collector(mock_session,
64 | "collector name")
65 | self.assertEqual(actual_collector, expected_collector)
66 |
67 |
68 | if __name__ == "__main__":
69 | unittest.main()
70 |
--------------------------------------------------------------------------------
/forwarders/get_forwarder.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for retrieving a forwarder."""
18 |
19 | import argparse
20 | import json
21 | from typing import Mapping, Any
22 |
23 | from google.auth.transport import requests
24 |
25 | from common import chronicle_auth
26 | from common import regions
27 |
28 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
29 |
30 |
31 | def get_forwarder(http_session: requests.AuthorizedSession,
32 | name: str) -> Mapping[str, Any]:
33 | """Retrieves a forwarder.
34 |
35 | Args:
36 | http_session: Authorized session for HTTP requests.
37 | name: Resource name for the Forwarder (forwarders/{UUID}).
38 |
39 | Returns:
40 | Array containing each line of the forwarder's content.
41 |
42 | Raises:
43 | requests.exceptions.HTTPError: HTTP request resulted in an error
44 | (response.status_code >= 400).
45 | """
46 | url = f"{CHRONICLE_API_BASE_URL}/v2/{name}"
47 |
48 | response = http_session.request("GET", url)
49 | # Expected server response:
50 | # {
51 | # "name": "forwarders/{forwarderUUID}",
52 | # "displayName": "TestForwarder",
53 | # "config": {
54 | # "uploadCompression": true
55 | # },
56 | # "state": "ACTIVE"
57 | # }
58 |
59 | if response.status_code >= 400:
60 | print(response.text)
61 | response.raise_for_status()
62 | return response.json()
63 |
64 |
65 | if __name__ == "__main__":
66 | parser = argparse.ArgumentParser()
67 | chronicle_auth.add_argument_credentials_file(parser)
68 | regions.add_argument_region(parser)
69 | parser.add_argument(
70 | "-n",
71 | "--name",
72 | type=str,
73 | required=True,
74 | help="resource name for the Forwarder (forwarders/{UUID})")
75 |
76 | args = parser.parse_args()
77 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
78 | session = chronicle_auth.initialize_http_session(args.credentials_file)
79 | print(json.dumps(get_forwarder(session, args.name), indent=2))
80 |
--------------------------------------------------------------------------------
/forwarders/get_forwarder_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "get_forwarder" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_forwarder
23 |
24 |
25 | class GetForwarderTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | get_forwarder.get_forwarder(mock_session, "forwarder name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_forwarder = {
44 | "name": "forwarders/928b3c1e-1430-4511-892d-2202206b4d8c",
45 | "displayName": "TestForwarder",
46 | "config": {
47 | "uploadCompression": True
48 | },
49 | "state": "ACTIVE"
50 | }
51 |
52 | mock_response.json.return_value = expected_forwarder
53 |
54 | actual_forwarder = get_forwarder.get_forwarder(mock_session,
55 | "forwarder name")
56 | self.assertEqual(actual_forwarder, expected_forwarder)
57 |
58 |
59 | if __name__ == "__main__":
60 | unittest.main()
61 |
--------------------------------------------------------------------------------
/forwarders/list_forwarders.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2022 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for retrieving a list of forwarders."""
18 |
19 | import argparse
20 | import json
21 | from typing import Mapping, Any
22 |
23 | from google.auth.transport import requests
24 |
25 | from common import chronicle_auth
26 | from common import regions
27 |
28 | CHRONICLE_API_BASE_URL = "https://backstory.googleapis.com"
29 |
30 |
31 | def list_forwarders(
32 | http_session: requests.AuthorizedSession) -> Mapping[str, Any]:
33 | """Retrieves all forwarders for the tenant.
34 |
35 | Args:
36 | http_session: Authorized session for HTTP requests.
37 |
38 | Returns:
39 | Array containing each forwarder associated with the instance.
40 |
41 | Raises:
42 | requests.exceptions.HTTPError: HTTP request resulted in an error
43 | (response.status_code >= 400).
44 | """
45 | url = f"{CHRONICLE_API_BASE_URL}/v2/forwarders"
46 |
47 | response = http_session.request("GET", url)
48 | # Expected server response:
49 | # {
50 | # "forwarders": [
51 | # {
52 | # "name": "forwarders/{forwarderUUID}",
53 | # "displayName": "TestForwarder1",
54 | # "config": {
55 | # "uploadCompression": true,
56 | # },
57 | # "state": "ACTIVE"
58 | # }
59 | # ]
60 | # }
61 |
62 | if response.status_code >= 400:
63 | print(response.text)
64 | response.raise_for_status()
65 | return response.json()
66 |
67 |
68 | if __name__ == "__main__":
69 | parser = argparse.ArgumentParser()
70 | chronicle_auth.add_argument_credentials_file(parser)
71 | regions.add_argument_region(parser)
72 |
73 | args = parser.parse_args()
74 | CHRONICLE_API_BASE_URL = regions.url(CHRONICLE_API_BASE_URL, args.region)
75 | session = chronicle_auth.initialize_http_session(args.credentials_file)
76 | print(json.dumps(list_forwarders(session), indent=2))
77 |
--------------------------------------------------------------------------------
/forwarders/list_forwarders_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_forwarders" function."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_forwarders
23 |
24 |
25 | class ListForwardersTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | list_forwarders.list_forwarders(mock_session)
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_forwarders = {
44 | "forwarders": [{
45 | "name": "forwarders/af2235e5-08d0-45e6-aaf8-98dbfa83a178",
46 | "displayName": "TestForwarder1",
47 | "config": {
48 | "uploadCompression": True,
49 | },
50 | "state": "ACTIVE"
51 | }]
52 | }
53 |
54 | mock_response.json.return_value = expected_forwarders
55 |
56 | actual_forwarders = list_forwarders.list_forwarders(mock_session)
57 | self.assertEqual(actual_forwarders, expected_forwarders)
58 |
59 |
60 | if __name__ == "__main__":
61 | unittest.main()
62 |
--------------------------------------------------------------------------------
/forwarders/update_collector_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "update_collector" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import update_collector
23 |
24 |
25 | class UpdateCollectorTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | update_collector.update_collector(mock_session, "collector name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_collector = {
44 | "name": "forwarders/{forwarderUUID}/collectors/{collectorUUID}",
45 | "displayName": "UpdatedCollector",
46 | "config": {
47 | "logType": "WINDOWS_DHCP",
48 | "maxSecondsPerBatch": 10,
49 | "maxBytesPerBatch": "1048576",
50 | "fileSettings": {
51 | "filePath": "/new/path/to/file.txt"
52 | }
53 | },
54 | "state": "ACTIVE"
55 | }
56 |
57 | mock_response.json.return_value = expected_collector
58 |
59 | actual_collector = update_collector.update_collector(
60 | mock_session, "collector name")
61 | self.assertEqual(actual_collector, expected_collector)
62 |
63 |
64 | if __name__ == "__main__":
65 | unittest.main()
66 |
--------------------------------------------------------------------------------
/forwarders/update_forwarder_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2022 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "update_forwarder" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import update_forwarder
23 |
24 |
25 | class UpdateForwarderTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | update_forwarder.update_forwarder(mock_session, "forwarder name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_forwarder = {
44 | "name": "forwarders/c37e1d99-f6ba-43da-b591-788261d32cae",
45 | "displayName": "UpdatedForwarder",
46 | "config": {
47 | "uploadCompression": True
48 | },
49 | "state": "ACTIVE"
50 | }
51 |
52 | mock_response.json.return_value = expected_forwarder
53 |
54 | actual_forwarder = update_forwarder.update_forwarder(
55 | mock_session, "forwarder name")
56 | self.assertEqual(actual_forwarder, expected_forwarder)
57 |
58 |
59 | if __name__ == "__main__":
60 | unittest.main()
61 |
--------------------------------------------------------------------------------
/ingestion/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/ingestion/create_unstructured_log_entries_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the create_unstructured_log_entries binary."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import create_unstructured_log_entries
23 |
24 |
25 | class CreateUnstructuredLogEntryTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | create_unstructured_log_entries.create_logs(
37 | mock_session, "LOG_TYPE", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
38 | "log1\tlog1\r\nlog2\tlog2")
39 |
40 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
41 | @mock.patch.object(requests.requests, "Response", autospec=True)
42 | def test_happy_path(self, mock_response, mock_session):
43 | mock_session.request.return_value = mock_response
44 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
45 |
46 | create_unstructured_log_entries.create_logs(
47 | mock_session, "LOG_TYPE", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
48 | "log1\tlog1\r\nlog2\tlog2")
49 |
50 |
51 | if __name__ == "__main__":
52 | unittest.main()
53 |
--------------------------------------------------------------------------------
/ingestion/example_input/sample_entities.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "metadata":
4 | {
5 | "collected_timestamp": "2021-07-14T15:30:18.142265Z",
6 | "entity_type": "USER",
7 | "vendor_name": "ABC",
8 | "product_name": "product1"
9 | },
10 | "entity":
11 | {
12 | "user": {
13 | "userid": "employee",
14 | "product_object_id": "doejohn"
15 | }
16 | }
17 | },
18 | {
19 | "metadata":
20 | {
21 | "collected_timestamp": "2021-07-15T15:30:18.142265Z",
22 | "entity_type": "USER",
23 | "vendor_name": "ABC",
24 | "product_name": "product1"
25 | },
26 | "entity":
27 | {
28 | "user": {
29 | "userid": "employee1",
30 | "product_object_id": "doejohn1"
31 | }
32 | }
33 | }
34 | ]
35 |
--------------------------------------------------------------------------------
/ingestion/example_input/sample_udm_events.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "metadata":
4 | {
5 | "eventTimestamp": "2021-07-14T15:30:18.142265Z",
6 | "eventType": "USER_LOGIN"
7 | },
8 | "additional":
9 | {
10 | "id": "9876-54321"
11 | },
12 | "principal":
13 | {
14 | "hostname": "userhost",
15 | "ip": ["10.0.0.75"],
16 | "mac": ["28:80:23:0b:c9:b2"]
17 | },
18 | "target":
19 | {
20 | "hostname": "systemhost",
21 | "user":
22 | {
23 | "userid": "employee"
24 | },
25 | "ip": ["10.0.0.77"],
26 | "mac": ["28:80:23:0b:c9:b3"]
27 | },
28 | "securityResult":
29 | [
30 | {
31 | "action": ["ALLOW"]
32 | }
33 | ],
34 | "extensions":
35 | {
36 | "auth":
37 | {
38 | "type": "MACHINE",
39 | "mechanism": ["USERNAME_PASSWORD"]
40 | }
41 | }
42 | }
43 | ]
44 |
--------------------------------------------------------------------------------
/ingestion/example_input/sample_unstructured_log_entries.txt:
--------------------------------------------------------------------------------
1 | 2020-07-13 09:03:06.000000001 client 192.168.109.254#12345: query: google.com IN A + (1.2.3.4)
2 | 2020-07-13 09:03:07.000000001 client 192.168.109.252#12345: query: bbc.com IN A + (5.6.7.8)
3 |
--------------------------------------------------------------------------------
/ingestion/list_log_types.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 | #
17 | """Executable and reusable sample for listing supported log types.
18 |
19 | WARNING: This script makes use of the Ingestion API V2. V2 is currently only in
20 | preview for certain Chronicle customers. Please reach out to your Chronicle
21 | representative if you wish to use this API.
22 |
23 | API reference:
24 | https://cloud.google.com/chronicle/docs/reference/ingestion-api#logtypes
25 | """
26 |
27 | import argparse
28 | import json
29 | from typing import Any, Mapping
30 |
31 | from google.auth.transport import requests
32 |
33 | from common import chronicle_auth
34 | from common import regions
35 |
36 | INGESTION_API_BASE_URL = "https://malachiteingestion-pa.googleapis.com"
37 | AUTHORIZATION_SCOPES = ["https://www.googleapis.com/auth/malachite-ingestion"]
38 |
39 |
40 | def list_log_types(
41 | http_session: requests.AuthorizedSession) -> Mapping[str, Any]:
42 | """Retrieves a list of log types suitable for Chronicle ingestion.
43 |
44 | Args:
45 | http_session: Authorized session for HTTP requests.
46 |
47 | Returns:
48 | The log types suitable for Chronicle ingestion.
49 |
50 | Raises:
51 | requests.exceptions.HTTPError: HTTP request resulted in an error
52 | (response.status_code >= 400).
53 | """
54 | url = f"{INGESTION_API_BASE_URL}/v2/logtypes"
55 |
56 | response = http_session.request("GET", url)
57 | response.raise_for_status()
58 | return response.json()
59 |
60 |
61 | if __name__ == "__main__":
62 | parser = argparse.ArgumentParser()
63 | chronicle_auth.add_argument_credentials_file(parser)
64 | regions.add_argument_region(parser)
65 |
66 | args = parser.parse_args()
67 | INGESTION_API_BASE_URL = regions.url(INGESTION_API_BASE_URL, args.region)
68 | session = chronicle_auth.initialize_http_session(args.credentials_file,
69 | scopes=AUTHORIZATION_SCOPES)
70 | print(json.dumps(list_log_types(session), indent=2))
71 |
--------------------------------------------------------------------------------
/ingestion/list_log_types_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_log_types" function."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_log_types
23 |
24 |
25 | class GetFeedTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | list_log_types.list_log_types(mock_session)
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_log_types = {
44 | "logTypes": [
45 | {
46 | "logType": "FOO_LOGS",
47 | "description": "Foo Logs",
48 | },
49 | {
50 | "logType": "BAR_LOGS",
51 | "description": "Bar Logs",
52 | },
53 | ],
54 | }
55 | mock_response.json.return_value = expected_log_types
56 |
57 | actual_log_types = list_log_types.list_log_types(mock_session)
58 | self.assertEqual(actual_log_types, expected_log_types)
59 |
60 |
61 | if __name__ == "__main__":
62 | unittest.main()
63 |
--------------------------------------------------------------------------------
/lists/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/lists/create_list_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "create_list" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import create_list
23 |
24 |
25 | class CreateListTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | create_list.create_list(mock_session, "name", "description",
37 | ["content line"], "CONTENT_TYPE_DEFAULT_STRING")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 | expected_create_time = "2021-01-01T00:00:00Z"
45 | expected_list = {
46 | "name": "name",
47 | "description": "description",
48 | "lines": ["content line 1", "content line 2"],
49 | "contentType": "CONTENT_TYPE_DEFAULT_STRING",
50 | "createTime": expected_create_time
51 | }
52 | mock_response.json.return_value = expected_list
53 |
54 | actual_create_time = create_list.create_list(
55 | mock_session, "name", "description",
56 | ["content line 1", "content line 2"],
57 | "CONTENT_TYPE_DEFAULT_STRING",)
58 | self.assertEqual(actual_create_time, expected_create_time)
59 |
60 |
61 | if __name__ == "__main__":
62 | unittest.main()
63 |
--------------------------------------------------------------------------------
/lists/example_input/coldriver_sha256.txt:
--------------------------------------------------------------------------------
1 | 0f6b9d2ada67cebc8c0f03786c442c61c05cef5b92641ec4c1bdd8f5baeb2ee1
2 | A949ec428116489f5e77cefc67fea475017e0f50d2289e17c3eb053072adcf24
3 | C97acea1a6ef59d58a498f1e1f0e0648d6979c4325de3ee726038df1fc2e831d
4 | Ac270310b5410e7430fe7e36a079525cd8724b002b38e13a6ee6e09b326f4847
5 | 84523ddad722e205e2d52eedfb682026928b63f919a7bf1ce6f1ad4180d0f507
6 | 37c52481711631a5c73a6341bd8bea302ad57f02199db7624b580058547fb5a9
--------------------------------------------------------------------------------
/lists/example_input/foo.txt:
--------------------------------------------------------------------------------
1 | foo
2 | bar
3 | baz
4 | foo
5 | bar
6 | foo
--------------------------------------------------------------------------------
/lists/get_list_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "get_list" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_list
23 |
24 |
25 | class GetListTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | get_list.get_list(mock_session, "name")
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_list_lines = ["content line 1", "content line 2"]
44 | expected_list = {
45 | "name": "name",
46 | "description": "description",
47 | "lines": expected_list_lines,
48 | "createTime": "2021-01-01T00:00:00Z"
49 | }
50 | mock_response.json.return_value = expected_list
51 |
52 | actual_list_lines = get_list.get_list(mock_session, "name")
53 | self.assertEqual(actual_list_lines, expected_list_lines)
54 |
55 |
56 | if __name__ == "__main__":
57 | unittest.main()
58 |
--------------------------------------------------------------------------------
/lists/list_lists_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "list_lists" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_lists
23 |
24 |
25 | class ListListsTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | list_lists.list_lists(mock_session)
37 |
38 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
39 | @mock.patch.object(requests.requests, "Response", autospec=True)
40 | def test_happy_path(self, mock_response, mock_session):
41 | mock_session.request.return_value = mock_response
42 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
43 | expected_list = {
44 | "name": "name",
45 | "description": "description",
46 | "createTime": "2021-01-01T00:00:00Z"
47 | }
48 | expected_page_token = "page token here"
49 | mock_response.json.return_value = {
50 | "lists": [expected_list],
51 | "nextPageToken": expected_page_token,
52 | }
53 |
54 | lists, next_page_token = list_lists.list_lists(mock_session)
55 | self.assertEqual(len(lists), 1)
56 | self.assertEqual(lists[0], expected_list)
57 | self.assertEqual(next_page_token, expected_page_token)
58 |
59 |
60 | if __name__ == "__main__":
61 | unittest.main()
62 |
--------------------------------------------------------------------------------
/lists/update_list_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "update_list" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import update_list
23 |
24 |
25 | class UpdateListTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | update_list.update_list(mock_session, "name", "description",
37 | ["content line"], "CONTENT_TYPE_DEFAULT_STRING")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 | expected_time = "2021-01-01T00:00:00Z"
45 | expected_list = {
46 | "name": "name",
47 | "description": "description",
48 | "lines": ["content line 1", "content line 2"],
49 | "createTime": expected_time,
50 | "contentType": "CONTENT_TYPE_DEFAULT_STRING",
51 | }
52 | mock_response.json.return_value = expected_list
53 |
54 | actual_time = update_list.update_list(
55 | mock_session, "name", "description",
56 | ["content line 1", "content line 2"],
57 | "CONTENT_TYPE_DEFAULT_STRING",
58 | )
59 | self.assertEqual(actual_time, expected_time)
60 |
61 |
62 | if __name__ == "__main__":
63 | unittest.main()
64 |
--------------------------------------------------------------------------------
/lists/verify_list_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Unit tests for the "verify_list" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import verify_list
23 |
24 |
25 | class verifyListTest(unittest.TestCase):
26 |
27 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
28 | @mock.patch.object(requests.requests, "Response", autospec=True)
29 | def test_http_error(self, mock_response, mock_session):
30 | mock_session.request.return_value = mock_response
31 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
32 | mock_response.raise_for_status.side_effect = (
33 | requests.requests.exceptions.HTTPError())
34 |
35 | with self.assertRaises(requests.requests.exceptions.HTTPError):
36 | verify_list.verify_list(mock_session, ["content line"],
37 | "CONTENT_TYPE_DEFAULT_STRING")
38 |
39 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
40 | @mock.patch.object(requests.requests, "Response", autospec=True)
41 | def test_happy_path(self, mock_response, mock_session):
42 | mock_session.request.return_value = mock_response
43 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
44 | expected_resp = {
45 | "success": True,
46 | }
47 |
48 | mock_response.json.return_value = expected_resp
49 |
50 |
51 | if __name__ == "__main__":
52 | unittest.main()
53 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | google-auth
2 | requests < 2.32
3 |
--------------------------------------------------------------------------------
/search/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/search/list_iocs_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "list_iocs" module."""
16 |
17 | import datetime
18 | import unittest
19 | from unittest import mock
20 |
21 | from google.auth.transport import requests
22 |
23 | from . import list_iocs
24 |
25 |
26 | class ListIocsTest(unittest.TestCase):
27 |
28 | def test_initialize_command_line_args_local_time(self):
29 | actual = list_iocs.initialize_command_line_args(
30 | ["--start_time=2021-05-07T11:22:33", "--local_time"])
31 | self.assertIsNotNone(actual)
32 |
33 | def test_initialize_command_line_args_utc(self):
34 | actual = list_iocs.initialize_command_line_args(
35 | ["-ts=2021-05-07T11:22:33Z"])
36 | self.assertIsNotNone(actual)
37 |
38 | def test_initialize_command_line_args_page_size(self):
39 | actual = list_iocs.initialize_command_line_args(
40 | ["-ts=2021-05-07T11:22:33Z", "--page_size=1000"])
41 | self.assertIsNotNone(actual)
42 |
43 | def test_initialize_command_line_args_zero_page_size(self):
44 | actual = list_iocs.initialize_command_line_args(
45 | ["-ts=2021-05-07T11:22:33Z", "--page_size=0"])
46 | self.assertIsNone(actual)
47 |
48 | def test_initialize_command_line_args_large_page_size(self):
49 | actual = list_iocs.initialize_command_line_args(
50 | ["-ts=2021-05-07T11:22:33Z", "--page_size=10001"])
51 | self.assertIsNone(actual)
52 |
53 | def test_initialize_command_line_args_future(self):
54 | start_time = datetime.datetime.utcnow().astimezone(datetime.timezone.utc)
55 | start_time += datetime.timedelta(days=2)
56 | actual = list_iocs.initialize_command_line_args(
57 | [start_time.strftime("-ts=%Y-%m-%dT%H:%M:%SZ")])
58 | self.assertIsNone(actual)
59 |
60 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
61 | @mock.patch.object(requests.requests, "Response", autospec=True)
62 | def test_list_iocs(self, mock_response, mock_session):
63 | mock_session.request.return_value = mock_response
64 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
65 | mock_response.json.return_value = {"mock": "json"}
66 | actual = list_iocs.list_iocs(mock_session,
67 | datetime.datetime(2021, 5, 7, 11, 22, 33))
68 | self.assertEqual(actual, {"mock": "json"})
69 |
70 |
71 | if __name__ == "__main__":
72 | unittest.main()
73 |
--------------------------------------------------------------------------------
/service_management/README.md:
--------------------------------------------------------------------------------
1 | # Service Management APIs in Python
2 |
3 | Python samples and guidelines for using Chronicle Service Management APIs.
4 |
5 | ## Authentication Setup
6 |
7 | To access the Chronicle Service Management API programmatically, use Cloud Shell
8 | to authenticate a service account under your GCP organization.
9 |
10 | ### Environment Variables Setup
11 |
12 | 1. Go to the Google Cloud Console.
13 | 2. Activate Cloud Chell.
14 | 3. Set environment variables by running:
15 | a. Set your organization name:
16 | ```
17 | export ORG_ID=[YOUR_ORGANIZATION_ID]
18 | ```
19 | b. Set the project ID:
20 | ```
21 | export PROJECT_ID=[SERVICE_MANAGEMENT_ENABLED_PROJECT_ID]
22 | ```
23 | c. Set the service account name:
24 | ```
25 | export SERVICE_ACCOUNT=[SERVICE_ACCOUNT_NAME]
26 | ```
27 | d. Set the path in which the service account key should be stored:
28 | ```
29 | export KEY_LOCATION=[FULL_PATH]
30 | # This is used by client libraries to find the key.
31 | export GOOGLE_APPLICATION_CREDENTIALS=$KEY_LOCATION
32 | ```
33 |
34 | ### Service Account Setup
35 |
36 | 1. Create a service account that's associated with your project ID.
37 | ```
38 | gcloud iam service-accounts create $SERVICE_ACCOUNT --display-name \
39 | "Service Account for Chronicle Service Management APIs" --project $PROJECT_ID
40 | ```
41 | 2. Create a key to associate with the service account.
42 | ```
43 | gcloud iam service-accounts keys create $KEY_LOCATION --iam-account \
44 | $SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
45 | ```
46 | 3. Grant the service account the `chroniclesm.admin` role for the organization.
47 | ```
48 | gcloud organizations add-iam-policy-binding $ORG_ID \
49 | --member="serviceAccount:$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com" \
50 | --role='roles/chroniclesm.admin'
51 | ```
52 |
53 |
--------------------------------------------------------------------------------
/service_management/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/service_management/delete_gcp_association_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "delete_gcp_association" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import delete_gcp_association
23 |
24 |
25 | class DeleteGCPAssociationTest(unittest.TestCase):
26 |
27 | def test_initialize_command_line_args(self):
28 | actual = delete_gcp_association.initialize_command_line_args(
29 | ["--credentials_file=./foo.json", "--organization_id=123"])
30 | self.assertIsNotNone(actual)
31 |
32 | def test_initialize_command_line_args_organization_id_too_big(self):
33 | invalid_organization_id = 2**64
34 | actual = delete_gcp_association.initialize_command_line_args(
35 | [f"--organization_id={invalid_organization_id}"])
36 | self.assertIsNone(actual)
37 |
38 | def test_initialize_command_line_args_negative_organization_id(self):
39 | actual = delete_gcp_association.initialize_command_line_args(
40 | ["--organization_id=-1"])
41 | self.assertIsNone(actual)
42 |
43 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
44 | @mock.patch.object(requests.requests, "Response", autospec=True)
45 | def test_http_error(self, mock_response, mock_session):
46 | mock_session.request.return_value = mock_response
47 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
48 | mock_response.raise_for_status.side_effect = (
49 | requests.requests.exceptions.HTTPError())
50 |
51 | with self.assertRaises(requests.requests.exceptions.HTTPError):
52 | delete_gcp_association.delete_gcp_association(mock_session, 123)
53 |
54 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
55 | @mock.patch.object(requests.requests, "Response", autospec=True)
56 | def test_happy_path(self, mock_response, mock_session):
57 | mock_session.request.return_value = mock_response
58 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
59 |
60 | delete_gcp_association.delete_gcp_association(mock_session, 123)
61 |
62 |
63 | if __name__ == "__main__":
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/service_management/get_gcp_association_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "get_gcp_association" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_gcp_association
23 |
24 |
25 | class GetGCPAssociationTest(unittest.TestCase):
26 |
27 | def test_initialize_command_line_args(self):
28 | actual = get_gcp_association.initialize_command_line_args(
29 | ["--credentials_file=./foo.json", "--organization_id=123"])
30 | self.assertIsNotNone(actual)
31 |
32 | def test_initialize_command_line_args_organization_id_too_big(self):
33 | invalid_organization_id = 2**64
34 | actual = get_gcp_association.initialize_command_line_args(
35 | [f"--organization_id={invalid_organization_id}"])
36 | self.assertIsNone(actual)
37 |
38 | def test_initialize_command_line_args_negative_organization_id(self):
39 | actual = get_gcp_association.initialize_command_line_args(
40 | ["--organization_id=-1"])
41 | self.assertIsNone(actual)
42 |
43 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
44 | @mock.patch.object(requests.requests, "Response", autospec=True)
45 | def test_http_error(self, mock_response, mock_session):
46 | mock_session.request.return_value = mock_response
47 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
48 | mock_response.raise_for_status.side_effect = (
49 | requests.requests.exceptions.HTTPError())
50 |
51 | with self.assertRaises(requests.requests.exceptions.HTTPError):
52 | get_gcp_association.get_gcp_association(mock_session, 123)
53 |
54 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
55 | @mock.patch.object(requests.requests, "Response", autospec=True)
56 | def test_happy_path(self, mock_response, mock_session):
57 | mock_session.request.return_value = mock_response
58 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
59 |
60 | get_gcp_association.get_gcp_association(mock_session, 123)
61 |
62 |
63 | if __name__ == "__main__":
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/service_management/get_gcp_settings_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "get_gcp_settings" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_gcp_settings
23 |
24 |
25 | class GetGCPSettingsTest(unittest.TestCase):
26 |
27 | def test_initialize_command_line_args(self):
28 | actual = get_gcp_settings.initialize_command_line_args(
29 | ["--credentials_file=./foo.json", "--organization_id=123"])
30 | self.assertIsNotNone(actual)
31 |
32 | def test_initialize_command_line_args_organization_id_too_big(self):
33 | invalid_organization_id = 2**64
34 | actual = get_gcp_settings.initialize_command_line_args(
35 | [f"--organization_id={invalid_organization_id}"])
36 | self.assertIsNone(actual)
37 |
38 | def test_initialize_command_line_args_negative_organization_id(self):
39 | actual = get_gcp_settings.initialize_command_line_args(
40 | ["--organization_id=-1"])
41 | self.assertIsNone(actual)
42 |
43 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
44 | @mock.patch.object(requests.requests, "Response", autospec=True)
45 | def test_http_error(self, mock_response, mock_session):
46 | mock_session.request.return_value = mock_response
47 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
48 | mock_response.raise_for_status.side_effect = (
49 | requests.requests.exceptions.HTTPError())
50 |
51 | with self.assertRaises(requests.requests.exceptions.HTTPError):
52 | get_gcp_settings.get_gcp_settings(mock_session, 123)
53 |
54 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
55 | @mock.patch.object(requests.requests, "Response", autospec=True)
56 | def test_happy_path(self, mock_response, mock_session):
57 | mock_session.request.return_value = mock_response
58 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
59 |
60 | get_gcp_settings.get_gcp_settings(mock_session, 123)
61 |
62 |
63 | if __name__ == "__main__":
64 | unittest.main()
65 |
--------------------------------------------------------------------------------
/service_management/update_gcp_settings_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "update_gcp_settings" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import update_gcp_settings
23 |
24 |
25 | class UpdateGCPSettingsTest(unittest.TestCase):
26 |
27 | def test_initialize_command_line_args_enable_ingestion(self):
28 | actual = update_gcp_settings.initialize_command_line_args(
29 | ["--credentials_file=./foo.json", "--organization_id=123", "--enable"])
30 | self.assertIsNotNone(actual)
31 |
32 | def test_initialize_command_line_args_disable_ingestion(self):
33 | actual = update_gcp_settings.initialize_command_line_args(
34 | ["--credentials_file=./foo.json", "--organization_id=123", "--disable"])
35 | self.assertIsNotNone(actual)
36 |
37 | def test_initialize_command_line_args_organization_id_too_big(self):
38 | invalid_organization_id = 2**64
39 | actual = update_gcp_settings.initialize_command_line_args(
40 | [f"--organization_id={invalid_organization_id}"])
41 | self.assertIsNone(actual)
42 |
43 | def test_initialize_command_line_args_negative_organization_id(self):
44 | actual = update_gcp_settings.initialize_command_line_args(
45 | ["--organization_id=-1"])
46 | self.assertIsNone(actual)
47 |
48 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
49 | @mock.patch.object(requests.requests, "Response", autospec=True)
50 | def test_http_error(self, mock_response, mock_session):
51 | mock_session.request.return_value = mock_response
52 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
53 | mock_response.raise_for_status.side_effect = (
54 | requests.requests.exceptions.HTTPError())
55 |
56 | with self.assertRaises(requests.requests.exceptions.HTTPError):
57 | update_gcp_settings.update_gcp_settings(mock_session, 123, True)
58 |
59 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
60 | @mock.patch.object(requests.requests, "Response", autospec=True)
61 | def test_happy_path(self, mock_response, mock_session):
62 | mock_session.request.return_value = mock_response
63 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
64 |
65 | update_gcp_settings.update_gcp_settings(mock_session, 123, True)
66 |
67 |
68 | if __name__ == "__main__":
69 | unittest.main()
70 |
--------------------------------------------------------------------------------
/uppercase/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 |
--------------------------------------------------------------------------------
/uppercase/get_alert_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "get_alert" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import get_alert
23 |
24 |
25 | class GetAlertTest(unittest.TestCase):
26 |
27 | def test_initialize_command_line_args(self):
28 | actual = get_alert.initialize_command_line_args(["--id=1"])
29 | self.assertIsNotNone(actual)
30 |
31 | def test_initialize_command_line_args_short(self):
32 | actual = get_alert.initialize_command_line_args(["-i=1"])
33 | self.assertIsNotNone(actual)
34 |
35 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
36 | @mock.patch.object(requests.requests, "Response", autospec=True)
37 | def test_http_error(self, mock_response, mock_session):
38 | mock_session.request.return_value = mock_response
39 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
40 | mock_response.raise_for_status.side_effect = (
41 | requests.requests.exceptions.HTTPError())
42 |
43 | with self.assertRaises(requests.requests.exceptions.HTTPError):
44 | get_alert.get_alert(mock_session, "1")
45 |
46 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
47 | @mock.patch.object(requests.requests, "Response", autospec=True)
48 | def test_get_alert(self, mock_response, mock_session):
49 | mock_session.request.return_value = mock_response
50 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
51 | mock_response.json.return_value = {"mock": "json"}
52 | actual = get_alert.get_alert(mock_session, "1")
53 | self.assertEqual(actual, {"mock": "json"})
54 |
55 |
56 | if __name__ == "__main__":
57 | unittest.main()
58 |
--------------------------------------------------------------------------------
/uppercase/list_alerts_test.py:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | """Tests for the "list_alerts" module."""
16 |
17 | import unittest
18 | from unittest import mock
19 |
20 | from google.auth.transport import requests
21 |
22 | from . import list_alerts
23 |
24 |
25 | class ListAlertsTest(unittest.TestCase):
26 |
27 | def test_initialize_command_line_args(self):
28 | actual = list_alerts.initialize_command_line_args(["--page_size=5"])
29 | self.assertIsNotNone(actual)
30 |
31 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
32 | @mock.patch.object(requests.requests, "Response", autospec=True)
33 | def test_http_error(self, mock_response, mock_session):
34 | mock_session.request.return_value = mock_response
35 | type(mock_response).status_code = mock.PropertyMock(return_value=400)
36 | mock_response.raise_for_status.side_effect = (
37 | requests.requests.exceptions.HTTPError())
38 |
39 | with self.assertRaises(requests.requests.exceptions.HTTPError):
40 | list_alerts.list_alerts(mock_session)
41 |
42 | @mock.patch.object(requests, "AuthorizedSession", autospec=True)
43 | @mock.patch.object(requests.requests, "Response", autospec=True)
44 | def test_list_alerts(self, mock_response, mock_session):
45 | mock_session.request.return_value = mock_response
46 | type(mock_response).status_code = mock.PropertyMock(return_value=200)
47 |
48 | expected_alert = {"alertId": "1"}
49 | expected_page_token = "page token here"
50 | mock_response.json.return_value = {
51 | "uppercaseAlerts": [expected_alert],
52 | "nextPageToken": expected_page_token,
53 | }
54 |
55 | alerts, next_page_token = list_alerts.list_alerts(mock_session)
56 | self.assertCountEqual(alerts, [expected_alert])
57 | self.assertEqual(next_page_token, expected_page_token)
58 |
59 |
60 | if __name__ == "__main__":
61 | unittest.main()
62 |
--------------------------------------------------------------------------------