diff --git a/plaso/parsers/jsonl_plugins/gcp_log.py b/plaso/parsers/jsonl_plugins/gcp_log.py index f33c43b7a2..c7ca8022af 100644 --- a/plaso/parsers/jsonl_plugins/gcp_log.py +++ b/plaso/parsers/jsonl_plugins/gcp_log.py @@ -38,6 +38,8 @@ class GCPLogEventData(events.EventData): recorded_time (dfdatetime.DateTimeValues): date and time the log entry was recorded. request_account_identifier (str): GCP account identifier of the request. + request_address (str): IP address assigned to a Google Cloud Engine (GCE) + instance. request_description (str): description of the request. request_direction (str): direction of the request. request_email (str): email address of the request. @@ -58,6 +60,7 @@ class GCPLogEventData(events.EventData): engine instance. status_code (str): operation success or failure code. status_message (str); operation success or failure message. + status_reasons (list[str]): reasons for operation failure. text_payload (str): text payload for logs not using a JSON or proto payload. user_agent (str): user agent used in the request. """ @@ -88,6 +91,7 @@ def __init__(self): self.principal_subject = None self.recorded_time = None self.request_account_identifier = None + self.request_address = None self.request_description = None self.request_direction = None self.request_email = None @@ -249,16 +253,30 @@ def _ParseProtoPayloadStatus(self, proto_payload, event_data): # # Empty `code` and `message` fields indicate the operation was successful. - event_data.status_code = self._GetJSONValue(status, 'code') + event_data.status_code = str(self._GetJSONValue(status, 'code', '')) event_data.status_message = self._GetJSONValue(status, 'message') - def _ParseComputeInstancesInsert(self, request, event_data): + # `protoPayload.status.details[].reason` contains reason for an operation + # failure. + status_reasons = [] + + for status_detail in self._GetJSONValue(status, 'details', []): + status_reason = self._GetJSONValue(status_detail, 'reason') + if status_reason: + status_reasons.append(status_reason) + + if status_reasons: + event_data.status_reasons = status_reasons + + def _ParseComputeInsertRequest(self, request, event_data): """Extracts compute.instances.insert information. Args: request (dict): JSON dictionary of the `protoPayload.request` field. event_data (GCPLogEventData): event data. """ + # source_images hold Google Cloud source disk path used in creating a GCE + # instance. source_images = [] for disk in self._GetJSONValue(request, 'disks', []): @@ -307,7 +325,7 @@ def _ParseComputeProtoPayload(self, proto_payload, event_data): return if request_type == 'type.googleapis.com/compute.instances.insert': - self._ParseComputeInstancesInsert(request, event_data) + self._ParseComputeInsertRequest(request, event_data) def _ParseProtoPayload(self, json_dict, event_data): """Extracts information from a protoPayload value. @@ -351,6 +369,7 @@ def _ParseProtoPayloadRequest(self, proto_payload, event_data): event_data.request_account_identifier = self._GetJSONValue( request, 'account_id') + event_data.request_address = self._GetJSONValue(request, 'address') event_data.request_description = self._GetJSONValue(request, 'description') event_data.request_direction = self._GetJSONValue(request, 'direction') event_data.request_email = self._GetJSONValue(request, 'email') diff --git a/test_data/gcp_logging.jsonl b/test_data/gcp_logging.jsonl index 1663a164dd..cf826f6ad2 100644 --- a/test_data/gcp_logging.jsonl +++ b/test_data/gcp_logging.jsonl @@ -8,3 +8,4 @@ {"insertId": "1k28f3cfv7aknt", "jsonPayload": {"content": "This is a json payload", "event_subtype": "test_subtype", "actor": {"user": "fakeemailxyz@gmail.com"}}, "logName": "projects/fake-project/logs/testlog", "receiveTimestamp": "2021-10-19T02:05:41.496590981Z", "resource": {"labels": {"location": "", "namespace": "", "node_id": "", "project_id": "fake-project", "instance_id": "866011396029255273"}, "type": "generic_node"}, "timestamp": "2021-10-19T02:05:41.496590981Z"} {"insertId": "1io3yo2fursxdi", "logName": "projects/fake-project/logs/testlog", "receiveTimestamp": "2021-10-19T02:04:00.272384509Z", "resource": {"labels": {"location": "", "namespace": "", "node_id": "", "project_id": "fake-project"}, "type": "generic_node"}, "textPayload": "This is a text payload", "timestamp": "2021-10-19T02:04:00.272384509Z"} {"insertId":"-duywnve29mpi","labels":{"compute.googleapis.com/root_trigger_id":"b0966c41-45b9-4484-b3b9-b436bfdde977"},"logName":"projects/fake-project/logs/cloudaudit.googleapis.com%2Factivity","operation":{"first":true,"id":"operation-1714162209773-617057d99d773-818c5509-b54204dc","producer":"compute.googleapis.com"},"protoPayload":{"@type":"type.googleapis.com/google.cloud.audit.AuditLog","authenticationInfo":{"principalEmail":"fake-account@fake-project.com", "principalSubject":"user:fake-account@fake-project.com","serviceAccountDelegationInfo":[{"firstPartyPrincipal":{"principalEmail":"service-account-one@fake-project.com"}},{"firstPartyPrincipal":{"principalEmail":"service-account-two@fake-project.com"}}]},"authorizationInfo":[{"granted":true,"permission":"compute.instances.create","permissionType":"ADMIN_WRITE","resource":"projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","resourceAttributes":{"name":"projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","service":"compute","type":"compute.instances"}},{"granted":true,"permission":"compute.disks.create","permissionType":"ADMIN_WRITE","resource":"projects/fake-project/zones/us-central1-b/disks/fake-compute-instance","resourceAttributes":{"name":"projects/fake-project/zones/us-central1-b/disks/fake-compute-instance","service":"compute","type":"compute.disks"}},{"granted":true,"permission":"compute.subnetworks.use","permissionType":"ADMIN_WRITE","resource":"projects/fake-project/regions/us-central1/subnetworks/default","resourceAttributes":{"name":"projects/fake-project/regions/us-central1/subnetworks/default","service":"compute","type":"compute.subnetworks"}},{"granted":true,"permission":"compute.subnetworks.useExternalIp","permissionType":"ADMIN_WRITE","resource":"projects/fake-project/regions/us-central1/subnetworks/default","resourceAttributes":{"name":"projects/fake-project/regions/us-central1/subnetworks/default","service":"compute","type":"compute.subnetworks"}},{"granted":true,"permission":"compute.instances.setMetadata","permissionType":"ADMIN_WRITE","resource":"projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","resourceAttributes":{"name":"projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","service":"compute","type":"compute.instances"}},{"granted":true,"permission":"compute.instances.setLabels","permissionType":"ADMIN_WRITE","resource":"projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","resourceAttributes":{"name":"projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","service":"compute","type":"compute.instances"}},{"granted":true,"permission":"compute.instances.setServiceAccount","permissionType":"ADMIN_WRITE","resource":"projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","resourceAttributes":{"name":"projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","service":"compute","type":"compute.instances"}}],"methodName":"beta.compute.instances.insert","request":{"@type":"type.googleapis.com/compute.instances.insert","description":"Fake GCE instance","disks":[{"autoDelete":true,"boot":true,"initializeParams":{"diskSizeGb":"100","diskType":"zones/us-central1-b/diskTypes/pd-ssd","sourceImage":"projects/fake-project/global/images/fake-source-image"}}],"labels":[],"machineType":"zones/us-central1-b/machineTypes/n1-highmem-16","name":"fake-compute-instance","networkInterfaces":[{"accessConfigs":[{"name":"External NAT","type":"ONE_TO_ONE_NAT"}],"network":"global/networks/default"}],"scheduling":{"automaticRestart":true},"serviceAccounts":[{"email":"fake-service-account@fake-project.com","scopes":["https://www.googleapis.com/auth/cloud-platform"]}]},"requestMetadata":{"callerIp":"1.1.1.1","callerSuppliedUserAgent":"fake-user-agent-string command/gcloud.compute.instances.insert invocation-id/a1b2c3d4e5f6 environment/GCE","destinationAttributes":{},"requestAttributes":{"auth":{},"time":"2024-04-26T20:10:11.171739Z"}},"resourceLocation":{"currentLocations":["us-central1-b"]},"resourceName":"projects/1234567890/zones/us-central1-b/instances/fake-compute-instance","response":{"@type":"type.googleapis.com/operation","id":"30220247688656077","insertTime":"2024-04-26T13:10:11.055-07:00","name":"operation-1714162209773-617057d99d773-818c5509-b54204dc","operationType":"insert","progress":"0","selfLink":"https://www.googleapis.com/compute/beta/projects/fake-project/zones/us-central1-b/operations/operation-1714162209773-617057d99d773-818c5509-b54204dc","selfLinkWithId":"https://www.googleapis.com/compute/beta/projects/fake-project/zones/us-central1-b/operations/30220247688656077","startTime":"2024-04-26T13:10:11.056-07:00","status":"RUNNING","targetId":"9876543210","targetLink":"https://www.googleapis.com/compute/beta/projects/fake-project/zones/us-central1-b/instances/fake-compute-instance","user":"fake-service-account@fake-project.com","zone":"https://www.googleapis.com/compute/beta/projects/fake-project/zones/us-central1-b"},"serviceName":"compute.googleapis.com"},"receiveTimestamp":"2024-04-26T20:10:11.491740716Z","resource":{"labels":{"instance_id":"9876543210","project_id":"fake-project","zone":"us-central1-b"},"type":"gce_instance"},"severity":"NOTICE","timestamp":"2024-04-26T20:10:10.024055Z"} +{"insertId":"1awjxggeaxqgz","logName":"projects/ketchup/logs/cloudaudit.googleapis.com%2Factivity","protoPayload":{"@type":"type.googleapis.com/google.cloud.audit.AuditLog","status":{"code":7,"message":"Permission \"iam.serviceAccounts.create\" denied on resource (or it may not exist).","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"IAM_PERMISSION_DENIED","domain":"iam.googleapis.com","metadata":{"permission":"iam.serviceAccounts.create"}}]},"authenticationInfo":{"principalEmail":"dvwa-service-account@ketchup.iam.gserviceaccount.com","serviceAccountDelegationInfo":[{"firstPartyPrincipal":{"principalEmail":"service-1234567890@compute-system.iam.gserviceaccount.com"}}],"principalSubject":"serviceAccount:dvwa-service-account@ketchup.iam.gserviceaccount.com"},"requestMetadata":{"callerIp":"34.72.217.225","callerSuppliedUserAgent":"(gzip),gzip(gfe)","requestAttributes":{"time":"2024-12-03T17:58:45.019694350Z","auth":{}},"destinationAttributes":{}},"serviceName":"iam.googleapis.com","methodName":"google.iam.admin.v1.CreateServiceAccount","authorizationInfo":[{"resource":"projects/ketchup","permission":"iam.serviceAccounts.create","resourceAttributes":{"type":"iam.googleapis.com/ServiceAccount"},"permissionType":"ADMIN_WRITE"}],"resourceName":"projects/ketchup","request":{"service_account":{"display_name":"This is the attacker account"},"account_id":"theattacker","name":"projects/ketchup","@type":"type.googleapis.com/google.iam.admin.v1.CreateServiceAccountRequest"},"response":{"@type":"type.googleapis.com/google.iam.admin.v1.ServiceAccount"}},"receiveTimestamp":"2024-12-03T17:58:45.716564605Z","resource":{"type":"service_account","labels":{"unique_id":"","project_id":"ketchup","email_id":""}},"timestamp":"2024-12-03T17:58:44.882119699Z","severity":"ERROR"} diff --git a/tests/parsers/jsonl_parser.py b/tests/parsers/jsonl_parser.py index 2b676b3207..d4de35cfde 100644 --- a/tests/parsers/jsonl_parser.py +++ b/tests/parsers/jsonl_parser.py @@ -37,7 +37,7 @@ def testParse(self): number_of_event_data = storage_writer.GetNumberOfAttributeContainers( 'event_data') - self.assertEqual(number_of_event_data, 10) + self.assertEqual(number_of_event_data, 11) number_of_warnings = storage_writer.GetNumberOfAttributeContainers( 'extraction_warning') diff --git a/tests/parsers/jsonl_plugins/gcp_log.py b/tests/parsers/jsonl_plugins/gcp_log.py index aa5f7d596d..a95d695216 100644 --- a/tests/parsers/jsonl_plugins/gcp_log.py +++ b/tests/parsers/jsonl_plugins/gcp_log.py @@ -19,7 +19,7 @@ def testProcess(self): number_of_event_data = storage_writer.GetNumberOfAttributeContainers( 'event_data') - self.assertEqual(number_of_event_data, 10) + self.assertEqual(number_of_event_data, 11) number_of_warnings = storage_writer.GetNumberOfAttributeContainers( 'extraction_warning') @@ -133,6 +133,65 @@ def testComputeInstancesInsert(self): event_data = storage_writer.GetAttributeContainerByIndex('event_data', 9) self.CheckEventData(event_data, expected_event_values) + def testServiceAccountCreateFailure(self): + """Tests service account creation failure log.""" + plugin = gcp_log.GCPLogJSONLPlugin() + storage_writer = self._ParseJSONLFileWithPlugin( + ['gcp_logging.jsonl'], plugin) + + expected_event_values = { + 'caller_ip': '34.72.217.225', + 'container': None, + 'dcsa_emails': None, + 'dcsa_scopes': None, + 'delegation_chain': ('service-1234567890@compute-system.iam.' + 'gserviceaccount.com'), + 'event_subtype': 'google.iam.admin.v1.CreateServiceAccount', + 'event_type': None, + 'filename': None, + 'firewall_rules': None, + 'firewall_source_ranges': None, + 'gcloud_command_identifier': None, + 'gcloud_command_pattern': None, + 'log_name': ('projects/ketchup/logs/cloudaudit.googleapis.com%2F' + 'activity'), + 'message': None, + 'method_name': 'google.iam.admin.v1.CreateServiceAccount', + 'permissions': ['iam.serviceAccounts.create'], + 'policy_deltas': None, + 'principal_email': ('dvwa-service-account@ketchup.iam.' + 'gserviceaccount.com'), + 'principal_subject': ('serviceAccount:dvwa-service-account@ketchup.' + 'iam.gserviceaccount.com'), + 'recorded_time': '2024-12-03T17:58:44.882119+00:00', + 'request_account_identifier': 'theattacker', + 'request_address': None, + 'request_description': None, + 'request_direction': None, + 'request_email': None, + 'request_member': None, + 'request_metadata': None, + 'request_name': 'projects/ketchup', + 'request_target': None, + 'request_labels': None, + 'resource_name': 'projects/ketchup', + 'service_account_delegation': [ + 'service-1234567890@compute-system.iam.gserviceaccount.com'], + 'service_account_display_name': 'This is the attacker account', + 'service_account_key_name': None, + 'service_name': 'iam.googleapis.com', + 'severity': 'ERROR', + 'source_images': None, + 'status_code': '7', + 'status_message': ('Permission "iam.serviceAccounts.create" denied on' + ' resource (or it may not exist).'), + 'status_reasons': ['IAM_PERMISSION_DENIED'], + 'text_payload': None, + 'user_agent': '(gzip),gzip(gfe)', + } + + event_data = storage_writer.GetAttributeContainerByIndex('event_data', 10) + self.CheckEventData(event_data, expected_event_values) if __name__ == '__main__': unittest.main()