Skip to content

Commit c379a6e

Browse files
Chronicle Teamcopybara-github
authored andcommitted
Add sample code for GetGCPLogFlowFilter.
PiperOrigin-RevId: 407411819
1 parent 1a28d86 commit c379a6e

2 files changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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 getting GCP log flow filter.
18+
19+
In Chronicle, customers can associate their GCP organizations with their
20+
Chronicle instances to make Chronicle ingestion their logs. On top of that,
21+
customers can control what kinds of logs will go into Chronicle by filters.
22+
This example provides a programmatic way to get such filters.
23+
"""
24+
25+
import argparse
26+
import re
27+
import sys
28+
from typing import Optional, Sequence
29+
30+
from google.auth.transport import requests
31+
32+
from common import chronicle_auth
33+
34+
SERVICE_MANAGEMENT_API_BASE_URL = "https://chronicleservicemanager.googleapis.com"
35+
36+
AUTHORIZATION_SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
37+
38+
PATTERN = re.compile(r"[a-z\d]{8}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{12}")
39+
40+
41+
def initialize_command_line_args(
42+
args: Optional[Sequence[str]] = None) -> Optional[argparse.Namespace]:
43+
"""Initializes and checks all the command-line arguments."""
44+
parser = argparse.ArgumentParser()
45+
chronicle_auth.add_argument_credentials_file(parser)
46+
parser.add_argument(
47+
"--organization_id",
48+
type=int,
49+
required=True,
50+
help="the GCP organization ID that the filter belongs to")
51+
parser.add_argument(
52+
"--filter_id", type=str, required=True, help="UUID of the filter")
53+
54+
# Sanity checks for the command-line arguments.
55+
parsed_args = parser.parse_args(args)
56+
if parsed_args.organization_id >= 2**64 or parsed_args.organization_id < 0:
57+
print("Error: organization ID should not be bigger than 2^64")
58+
return None
59+
if PATTERN.fullmatch(parsed_args.filter_id) is None:
60+
print("Error: filter ID is invalid")
61+
return None
62+
63+
return parsed_args
64+
65+
66+
def get_gcp_log_flow_filter(http_session: requests.AuthorizedSession,
67+
organization_id: int, filter_id: str) -> None:
68+
"""Gets GCP log flow filter for the given GCP organization.
69+
70+
Args:
71+
http_session: Authorized session for HTTP requests.
72+
organization_id: GCP organization ID that the filter belongs to.
73+
filter_id: UUID for the filter. One can get such UUID by calling
74+
GetGCPSettings API.
75+
76+
Raises:
77+
requests.exceptions.HTTPError: HTTP request resulted in an error
78+
(response.status_code >= 400).
79+
"""
80+
name = f"organizations/{organization_id}/gcpAssociations/{organization_id}/gcpLogFlowFilters/{filter_id}"
81+
url = f"{SERVICE_MANAGEMENT_API_BASE_URL}/v1/{name}"
82+
83+
response = http_session.request("GET", url)
84+
85+
if response.status_code >= 400:
86+
print(response.text)
87+
response.raise_for_status()
88+
89+
90+
if __name__ == "__main__":
91+
cli = initialize_command_line_args()
92+
if not cli:
93+
sys.exit(1) # A sanity check failed.
94+
95+
session = chronicle_auth.initialize_http_session(
96+
cli.credentials_file, scopes=AUTHORIZATION_SCOPES)
97+
get_gcp_log_flow_filter(session, cli.organization_id, cli.filter_id)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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_log_flow_filter" module."""
16+
17+
import unittest
18+
from unittest import mock
19+
20+
from google.auth.transport import requests
21+
22+
from . import get_gcp_log_flow_filter
23+
24+
DUMMY_FILTER_ID = "00000000-0000-0000-0000-000000000000"
25+
26+
27+
class GetGCPLogFlowFilter(unittest.TestCase):
28+
29+
def test_initialize_command_line_args(self):
30+
actual = get_gcp_log_flow_filter.initialize_command_line_args([
31+
"--credentials_file=./foo.json", "--organization_id=123",
32+
f"--filter_id={DUMMY_FILTER_ID}"
33+
])
34+
self.assertIsNotNone(actual)
35+
36+
def test_initialize_command_line_args_organization_id_too_big(self):
37+
invalid_organization_id = 2**64
38+
actual = get_gcp_log_flow_filter.initialize_command_line_args([
39+
f"--organization_id={invalid_organization_id}",
40+
f"--filter_id={DUMMY_FILTER_ID}"
41+
])
42+
self.assertIsNone(actual)
43+
44+
def test_initialize_command_line_args_negative_organization_id(self):
45+
actual = get_gcp_log_flow_filter.initialize_command_line_args(
46+
["--organization_id=-1", f"--filter_id={DUMMY_FILTER_ID}"])
47+
self.assertIsNone(actual)
48+
49+
def test_initialize_command_line_args_invalid_filter_id(self):
50+
actual = get_gcp_log_flow_filter.initialize_command_line_args(
51+
["--organization_id=123", "--filter_id=123"])
52+
self.assertIsNone(actual)
53+
54+
@mock.patch.object(requests, "AuthorizedSession", autospec=True)
55+
@mock.patch.object(requests.requests, "Response", autospec=True)
56+
def test_http_error(self, mock_response, mock_session):
57+
mock_session.request.return_value = mock_response
58+
type(mock_response).status_code = mock.PropertyMock(return_value=400)
59+
mock_response.raise_for_status.side_effect = (
60+
requests.requests.exceptions.HTTPError())
61+
62+
with self.assertRaises(requests.requests.exceptions.HTTPError):
63+
get_gcp_log_flow_filter.get_gcp_log_flow_filter(mock_session, 123,
64+
DUMMY_FILTER_ID)
65+
66+
@mock.patch.object(requests, "AuthorizedSession", autospec=True)
67+
@mock.patch.object(requests.requests, "Response", autospec=True)
68+
def test_happy_path(self, mock_response, mock_session):
69+
mock_session.request.return_value = mock_response
70+
type(mock_response).status_code = mock.PropertyMock(return_value=200)
71+
72+
get_gcp_log_flow_filter.get_gcp_log_flow_filter(mock_session, 123,
73+
DUMMY_FILTER_ID)
74+
75+
76+
if __name__ == "__main__":
77+
unittest.main()

0 commit comments

Comments
 (0)