Google SecOps
Synopsis
The Google SecOps target forwards events to Google Security Operations using the Ingestion API. Supports both V1 and V2 API versions with unstructured logs or UDM (Unified Data Model) format submission.
Schema
- name: <string>
description: <string>
type: gcpsecops
pipelines: <pipeline[]>
status: <boolean>
properties:
api_version: <string>
send_events_as: <string>
udm_type: <string>
region: <string>
customer_id: <string>
authentication_method: <credentials|secret|apikey|apikeysecret>
service_account_credentials: <string>
service_account_credentials_secret: <string>
api_key: <string>
api_key_secret: <string>
default_log_type: <string>
log_text_field: <string>
namespace: <string>
custom_log_types:
- log_type: <string>
description: <string>
labels:
- key: <string>
value: <string>
batch_size: <integer>
timeout: <integer>
request_timeout: <integer>
max_bytes: <integer>
field_format: <string>
debug:
status: <boolean>
dont_send_logs: <boolean>
Configuration
The following fields are used to define the target:
| Field | Required | Default | Description |
|---|---|---|---|
name | Y | Target name | |
description | N | - | Optional description |
type | Y | Must be gcpsecops | |
pipelines | N | - | Optional post-processor pipelines |
status | N | true | Enable/disable the target |
API Configuration
| Field | Required | Default | Description |
|---|---|---|---|
api_version | N | v2 | API version: v1, v2 |
send_events_as | N | unstructured | Data format: unstructured, udm |
udm_type | N* | logs | UDM submission type: logs, entities |
region | Y | - | SecOps ingestion region. See Regions below |
customer_id | Y* | - | Google SecOps customer ID |
max_bytes | N | 1048576 (1 MB) for unstructured, 4194304 (4 MB) for udm | Maximum size of a single request body or batch, in bytes. A value above the maximum the endpoint documents is clamped back to it |
max_bytes caps the size of a single request body or batch. It behaves differently from max_size on the file-writing targets, and the difference is easy to get wrong:
- An explicit
max_bytes: 0IS honoured and removes the ceiling on targets that have no ceiling of their own. The value is read withInt64, which returns what you wrote — unlikemax_size, which is read withMustInt64and silently substitutes its default for a zero. Where a target has a fixed ceiling of its own, its page says so and a zero is clamped to that ceiling instead. - A negative value is rejected at configuration time with max_bytes must not be negative.
The ceiling is validated when the target is built and enforced again on every batch, so a value the receiving service will refuse fails early rather than per request.
* = Conditionally required. udm_type applies when send_events_as: udm with V2 API. customer_id is required for V2 API.
Authentication
| Field | Required | Default | Description |
|---|---|---|---|
authentication_method | N | credentials | Authentication method: credentials, secret, apikey, apikeysecret |
service_account_credentials | Y* | - | Inline JSON service account credentials |
service_account_credentials_secret | Y* | - | Service account JSON supplied through a reference such as ${VAR}, $env{VAR} or $secret{store=...,ref=...}. The reference must resolve to the JSON itself, not to the name of a variable |
api_key | Y* | - | Inline API key (V1 only) |
api_key_secret | Y* | - | API key supplied through a reference such as ${VAR}, $env{VAR} or $secret{store=...,ref=...} (V1 only). The reference must resolve to the key itself |
* = Conditionally required. The applicable field depends on authentication_method: credentials → service_account_credentials; secret → service_account_credentials_secret; apikey → api_key (V1 only); apikeysecret → api_key_secret (V1 only).
Unstructured Log Configuration
| Field | Required | Default | Description |
|---|---|---|---|
default_log_type | Y* | - | Default log type name |
log_text_field | N | - | Field name to use as log text. If empty, entire JSON event is used |
namespace | N | - | SecOps namespace for log organization |
custom_log_types | N | - | Array of custom log type definitions |
custom_log_types[].log_type | Y | - | Log type name |
custom_log_types[].description | N | - | Log type description |
* = default_log_type is required when send_events_as: unstructured (the default).
Labels
| Field | Required | Default | Description |
|---|---|---|---|
labels | N | - | Array of custom labels to attach to events |
labels[].key | Y | - | Label key |
labels[].value | Y | - | Label value |
Batch Configuration
| Field | Required | Default | Description |
|---|---|---|---|
batch_size | N | 1000 | Maximum events per batch |
timeout | N | 30 | Request timeout in seconds |
request_timeout | N | 30 | HTTP client timeout in seconds |
Processing
| Field | Required | Default | Description |
|---|---|---|---|
field_format | N | - | Data normalization format. See applicable Normalization section |
Scheduling
See Scheduling and Pool Behavior for interval and cron fields shared by all targets.
Debug Options
| Field | Required | Default | Description |
|---|---|---|---|
debug.status | N | false | Enable debug logging |
debug.dont_send_logs | N | false | Process logs but don't send to target (testing) |
Details
Data Sending Options
DataStream supports two approaches for submitting events to Google SecOps:
Unstructured Logs:
- Send raw log data to SecOps
- SecOps applies built-in parsers to convert logs into UDM format
- Simpler configuration requiring only
default_log_type - SecOps handles parsing and normalization
- May encounter parsing issues with proprietary or uncommon log formats
- Best for standard log types with existing SecOps parser support
Pre-Structured UDM Events:
- Transform data into UDM format before sending
- Bypass SecOps parsing entirely
- Complete control over UDM field mapping
- Requires understanding of UDM schema structure
- Eliminates parsing errors and inconsistencies
- Best for custom data sources or precise field control
The choice between approaches depends on data complexity and control requirements. Unstructured logs offer simpler setup with SecOps-managed parsing. Pre-structured UDM provides precision but requires upstream transformation.
Working with Unstructured Data
When using send_events_as: unstructured, log type assignment follows a hierarchical resolution:
Log Type Assignment Hierarchy:
- Event-level override: Events with
__logTypefield use that value - Default assignment: Events without
__logTypeusedefault_log_typeconfiguration - Custom log types: Use
custom_log_typesarray for unsupported or deprecated types
Standard Log Types:
SecOps maintains a dynamic list of supported log types (e.g., FIREWALL_LOG, WINDOWS_EVENT, SYSLOG). The supported types evolve over time as SecOps adds new parsers and deprecates old ones.
For the complete list of supported default parsers, see Google SecOps Parser List.
Custom Log Type Definition:
For log sources without SecOps parser support, define custom log types:
custom_log_types:
- log_type: PROPRIETARY_APP_LOG
description: Internal application security logs
- log_type: LEGACY_FIREWALL
description: Deprecated firewall format
Custom log types bypass standard SecOps parsing. Events are stored but may require custom SecOps rules for analysis.
Log Text Field Selection:
By default, the entire JSON event is sent as log text. Use log_text_field to extract a specific field:
log_text_field: message # Send only the 'message' field content
This is useful when the raw log text exists in a specific field while other fields contain metadata.
Regions
Valid SecOps ingestion regions:
| Region Code | Location |
|---|---|
us | United States |
europe | Europe (multi-region) |
northamerica-northeast2 | Toronto, Canada |
southamerica-east1 | S�o Paulo, Brazil |
europe-west2 | London, UK |
europe-west3 | Frankfurt, Germany |
europe-west6 | Zurich, Switzerland |
europe-west9 | Paris, France |
europe-west12 | Turin, Italy |
europe-central2 | Warsaw, Poland |
africa-south1 | Johannesburg, South Africa |
asia-northeast1 | Tokyo, Japan |
asia-south1 | Mumbai, India |
asia-southeast1 | Singapore |
asia-southeast2 | Jakarta, Indonesia |
australia-southeast1 | Sydney, Australia |
me-central1 | Doha, Qatar |
me-central2 | Dammam, Saudi Arabia |
me-west1 | Tel Aviv, Israel |
turin | Turin, Italy (alias for europe-west12) |
zurich | Zurich, Switzerland (alias for europe-west6) |
API Version Selection
V2 API (Recommended):
- Requires
customer_idfield - Supports unstructured logs and UDM
- Regional endpoint support
- Enhanced entity submission
V1 API:
- API key authentication supported
- Legacy endpoint compatibility
- Limited to unstructured logs and UDM events
Authentication Methods
Service Account (OAuth2):
credentials: Inline JSON service accountsecret: Service account JSON resolved from a${VAR},$env{VAR}or$secret{...}reference
API Key (V1 only):
apikey: Inline API keyapikeysecret: API key resolved from a${VAR},$env{VAR}or$secret{...}reference
Service account requires the Chronicle API Writer role or equivalent permissions to ingest data into SecOps.
Data Format Options
Unstructured Logs:
- Raw log text submission
- Requires
default_log_typefield - Optional custom log types for classification
- Best for text-based logs or pre-formatted data
UDM (Unified Data Model):
- Structured event submission using SecOps UDM schema
- Use
udm_type: logsfor security events - Use
udm_type: entitiesfor entity context (V2 only) - Requires events in UDM format
When using send_events_as: udm, ensure events conform to Google SecOps UDM schema. Invalid UDM events will be rejected.
Dynamic Field Overrides
Events can override target settings using reserved fields:
__logType: Override default log type per event__namespace: Override namespace per event
These fields are read as overrides, but they are not stripped from the event. They remain in the submitted log text, so remove them in a pipeline if they must not reach SecOps.
Examples
Basic V2 Unstructured
Sending unstructured logs to SecOps V2 API, with the service account JSON read from an environment variable reference... | |
V2 with UDM Events
Forwarding UDM-formatted security events to SecOps using inline service account credentials... | |
V1 with API Key
Using V1 API with API key authentication, with the key read from an environment variable reference... | |
With Custom Log Types
Defining multiple custom log types for different Windows event categories... | |
With Namespace and Labels
Organizing logs with namespace and custom labels for production environment tracking... | |
With Log Text Field
Extracting specific field as log text instead of submitting entire JSON structure... | |
V2 UDM Entities
Submitting entity context data using V2 UDM entities endpoint... | |
Production Configuration
Production-ready configuration with custom log types, labels, and performance tuning... | |
Troubleshooting
This section covers the errors you are most likely to see with the gcpsecops target, what causes each one, and how to fix it.
Where to look:
- Director logs. Target errors are tagged with the target name and carry
"Section":"SenderPool". The part afterReason:, or after the last colon, is the actual cause. - The target's connection status in the web interface. It shows the same reason as the log line, prefixed with
connection failed for <target name>:.
See Target Delivery Errors for how Director logs and retries target failures.
Which permission is missing?
Match the error you see against this table first.
| Error text | What is missing | Where it applies |
|---|---|---|
secops api returned status 403, whose body typically names PERMISSION_DENIED | Authorization for the service account to ingest into the SecOps tenant, or the ingestion API is not enabled for the project | The tenant identified by customer_id |
secops api returned status 401 with authentication_method: credentials or secret | A service account key the ingestion API still accepts. Deleted and disabled keys fail here | The key in service_account_credentials or service_account_credentials_secret |
secops api returned status 401 or 403 with authentication_method: apikey or apikeysecret | A valid, unrestricted ingestion API key issued for this tenant | The value of api_key or api_key_secret |
failed to get oauth2 token: oauth2: cannot fetch token: 400 Bad Request | A key Google will still exchange for a token. This is checked before any permission is | The service account key itself |
There is no single role name to check against. What the ingestion API requires is that the identity you configured is authorized to ingest into the tenant named by customer_id: either a service account your Google SecOps administrator has granted an ingestion role, or an ingestion API key issued for that tenant. Google decides this at its end, and the role name differs between Google SecOps editions, so treat Authentication Methods above as the reference for what to set up, rather than any role name quoted in an error message. A 401 or a 403 is not fatal to your data: the batch is redelivered about every five seconds until the grant is in place, so fix it and the backlog drains on its own.
"service_account_credentials is required when using credentials authentication"
[Error] [director] [target-<target id>] [secops-unstructured] ValidateConfig failed for target "secops-unstructured": service_account_credentials is required when using credentials authentication
Cause: the authentication method was not found, so the target fell back to credentials, and no service_account_credentials value was set. The usual reason is a configuration that nests the method under an authentication block. That form is not read. The method is the flat property authentication_method, and the credential property sits beside it, directly under properties.
Fix: write both at the same level.
properties:
authentication_method: secret
service_account_credentials_secret: "${GCP_SECOPS_CREDENTIALS}"
The same mistake produces a different message for each method, so read the message to see which method the target actually resolved.
service_account_credentials_secret is required when using secret authenticationapi_key is required when using apikey authenticationapi_key_secret is required when using apikeysecret authenticationinvalid authentication_method: oauth, when the value is not one ofcredentials,secret,apikeyorapikeysecret. The comparison is case sensitive.api key authentication is only supported with v1 api, whenapikeyorapikeysecretis used without also settingapi_version: v1
No data is lost. The target never starts, so nothing is sent and incoming events wait in the Director queue until the configuration is valid.
"failed to create credentials: invalid character ... looking for beginning of value"
Failed to reinitialize target "secops-unstructured" (attempt 1). Reason: failed to create credentials: invalid character 'G' looking for beginning of value
Cause: the value that reached the target is not service account JSON. The most common case is the name of an environment variable where its content was expected. GCP_SECOPS_CREDENTIALS is passed through unchanged and then parsed as JSON, which fails on its first letter. Two variants of the same problem are unexpected end of JSON input, when the reference resolved to an empty value, and missing 'type' field in credentials, when the JSON is valid but is not a service account key.
Fix: supply the key file content, either inline or through a reference that gets expanded: ${GCP_SECOPS_CREDENTIALS}, $env{GCP_SECOPS_CREDENTIALS} or $secret{store=my-store,ref=secops-key}. The resolved value must begin with { and contain "type": "service_account". Inline JSON has to keep the private_key newlines intact, so paste it as a YAML block scalar rather than as a single line.
No data is lost. The target retries initialization until the credential parses, and events queue in the meantime.
"failed to resolve service account credentials: credential: ..."
Failed to reinitialize target "secops-unstructured" (attempt 1). Reason: failed to resolve service account credentials: credential: env variable "GCP_SECOPS_CREDENTIALS" is not set
Cause: the reference is well formed but could not be resolved. credential: env variable "..." is not set means the variable is not exported to the Director process, which is not the same as being exported in your own shell. credential: store "..." not found in configuration and credential: secret with id "..." not found mean the $secret{...} token points at a store or an entry that does not exist.
Fix: export the variable in the service environment and restart Director so it inherits it, or correct the store name and the reference. The same wording appears as failed to resolve service account credentials secret, failed to resolve api key and failed to resolve api key secret for the other three methods.
No data is lost. Initialization is retried until the reference resolves.
"failed to get oauth2 token"
Failed to reinitialize target "secops-udm" (attempt 4). Reason: failed to get oauth2 token: oauth2: cannot fetch token: 400 Bad Request
Cause: Google refused to exchange the service account key for a token. Google's response follows on the next line and typically names invalid_grant, which usually means the key was deleted or disabled, or the host clock has drifted. A damaged key fails differently, with private key should be a PEM or plain PKCS1 or PKCS8 or private key is invalid, which points at private_key newlines lost while pasting.
Fix: issue a new key for the service account and put its content in the target, check the Director host clock against a time source, and allow outbound HTTPS to oauth2.googleapis.com on port 443. The same text appears later as Finalize failed on flush ... failed to get oauth2 token when a key is revoked while the target is running, because the token is refreshed about once an hour.
No data is lost. Events queue while the target retries, and the queue drains once a token is issued.
"secops api returned status 401" or "secops api returned status 403"
Sender worker 0 Finalize failed on flush for target "secops-production": secops api returned status 403: <the response body Google returned>
Cause: the target authenticated and reached the ingestion API, but Google refused the request. A 403 body typically names PERMISSION_DENIED and means the identity is not authorized for this tenant, or the ingestion API is not enabled. A 401 means the credential itself was rejected. One case sends no credential at all: when api_key_secret resolves to an empty value, the request goes out unauthenticated and 401 repeats forever with nothing else in the logs.
Fix: work through Which permission is missing? above, then confirm that customer_id is the tenant the credential belongs to. With apikeysecret, check that the secret resolves to a non-empty value.
No data is lost. The batch is redelivered about every five seconds until Google accepts it, so expect the queue to grow until the grant is fixed.
"record rejected by target: secops api returned status 400"
Sender worker 1 Finalize failed on flush for target "secops-udm": record rejected by target: secops api returned status 400: <the response body Google returned>
Sender worker 1 dropping <payload> for target "secops-udm" after 4 rejected flush attempts — the endpoint refuses this batch deterministically, so its records are given up on
Cause: Google rejected the batch itself, not the credential. Statuses 400, 413 and 422 are treated as permanent. Typical causes are a default_log_type or __logType value SecOps has no parser for, UDM events that do not match the SecOps schema, and a customer_id that is not the tenant identifier. A customer_id written as ${SECOPS_CUSTOMER_ID} with the variable unset passes the configuration checks and is then sent as that literal text, which lands here.
Fix: check the log type against the Google SecOps Parser List, validate UDM events against the SecOps schema before sending, and confirm customer_id and every ${...} reference on the Director host.
Data is lost here. The whole batch is given up on after four delivery attempts, and the drop line above is the only record of it.
"record size ... exceeds configured max_bytes ..."
Sender worker 3 deterministic failure for secops-unstructured after 4 attempts — dropping (giving up): record rejected by target: record size 1205311 exceeds configured max_bytes 1048479
Cause: a single event, once encoded, is larger than the room left in one request body. The second number is smaller than the value you configured because the request envelope is measured and subtracted first. The ceiling follows the maximum Google documents for the endpoint, typically 1 MB for unstructured log entries and 4 MB for UDM.
Fix: trim the event in a pipeline before it reaches the target, or switch to send_events_as: udm, where the larger ceiling applies. Raising max_bytes past the maximum does not help. It is clamped back, with a warning at startup that says the value exceeds the ...-byte maximum this endpoint documents and was clamped to it. Setting it very low fails at startup instead, with max_bytes must exceed the 97-byte secops request envelope, got 64.
Only the oversized event is lost, after four delivery attempts. The rest of the batch is delivered normally.
"secops api returned status 429"
Sender worker 0 execute() failed for secops-v1: secops api returned status 429: <the response body Google returned>
Cause: Google is throttling ingestion for the tenant. The body typically names RESOURCE_EXHAUSTED. Statuses 408 and 5xx behave the same way and usually mean a transient problem at Google's end.
Fix: nothing has to change in the target, which applies no rate limiting of its own. If 429 persists for hours rather than minutes, ask your Google SecOps administrator to review the ingestion quota for the tenant, and lower the volume routed to this target until it is raised.
No data is lost. Batches are redelivered about every five seconds until they are accepted.
"failed to send request", certificate errors, and timeouts
Sender worker 2 execute() failed for secops-labeled: failed to send request: Post "https://europe-west2-malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate": tls: failed to verify certificate: x509: certificate signed by unknown authority
Cause: Director could not complete the HTTPS call. The text after the URL names the layer that failed. no such host is DNS. connection refused and i/o timeout are a firewall. x509: certificate signed by unknown authority is a TLS inspecting proxy presenting its own certificate. proxyconnect tcp is a proxy that is configured but unreachable. context deadline exceeded and Client.Timeout exceeded while awaiting headers are the configured timeouts.
Fix: allow outbound HTTPS on port 443 from the Director host to two names, not one: the region ingestion host shown in the error, and oauth2.googleapis.com for the token. Add a TLS inspecting proxy's certificate authority to the operating system trust store, because this target reads no certificate authority setting of its own. Set HTTP_PROXY, HTTPS_PROXY and NO_PROXY for the Director service if a proxy is required. Raise timeout and request_timeout together, since the shorter of the two wins.
Startup only contacts the token host. A host that can reach oauth2.googleapis.com but not the ingestion host reports a healthy connection and fails on the first flush instead, so do not read a green connection status as proof that delivery works.
No data is lost. Delivery is retried until the network path is open.
"invalid region", "customer_id is required for v2 api", and other rejected settings
ValidateConfig failed for target "secops-unstructured": invalid region: eu
Cause: a setting did not pass validation. Values are compared case sensitively, and an unset ${...} reference survives as literal text, which is why a missing variable often shows up as an invalid value rather than as an empty one.
| Error text | Fix |
|---|---|
region is required or invalid region: eu | Use a code from Regions, such as us, europe or europe-west2 |
customer_id is required for v2 api | Set customer_id, and make sure any ${...} reference is exported on the Director host |
default_log_type is required when sending unstructured data | Set default_log_type, for example SYSLOG. It is required whenever send_events_as is left at unstructured |
invalid api_version: v3 | Use v1 or v2 |
invalid send_events_as: UDM | Use unstructured or udm, in lower case |
invalid udm_type: entity | Use logs or entities |
batch_size must be greater than 0, got -5 | Remove the setting to get the default of 1000, or set a positive value |
max_bytes must not be negative, got -1 | Remove the setting, or set a positive value |
No data is lost. The target is not started until the configuration is valid, and events wait in the queue.
The target is healthy but the data in SecOps is wrong or missing
Work through these in order. None of them produces an error line.
debug.dont_send_logsis enabled. Events are processed by the pipeline and never sent. Withdebug.statusalso enabled, startup logsLog sending is disabled for this target (secops-unstructured). Logs will be processed by the pipeline but will not be sent to the target.Nothing else marks the loss. Remove the flag.- Plain text arrives unreadable. Without
log_text_field, a record that is not JSON is wrapped in a JSON object whose value is encoded, so the SecOps parser never sees the original line. Setlog_text_field: messagefor plain text sources such as syslog. - Everything lands under one unexpected log type. When
custom_log_typesis configured and an event carries no__logType, every entry is tagged with the first entry in that list rather than withdefault_log_type. Removecustom_log_typesunless that override is what you want, or set__logTypeper event in a pipeline. log_text_fieldnames a field the events do not have. The whole JSON event is used as the log text instead, silently. Field names are case sensitive.- Entities are stored as events. With
api_version: v1,udm_type: entitiesis not rejected and the data is submitted to the events endpoint. Useapi_version: v2for entities. - A label or a custom log type disappeared. Entries in
labelswith an emptykeyorvalue, and entries incustom_log_typeswith an emptylog_type, are skipped without a message. __logTypeand__namespaceshow up in the data. They are read as overrides but are also left in the submitted log text. Strip them with aremoveprocessor in a pipeline if they must not reach SecOps.- A literal
${...}appears in SecOps. An unset reference incustomer_id,default_log_typeornamespaceis sent as written. Incustomer_idit ends as a rejected batch instead.
Dropped batches do not move a counter of their own. The dropping ... after 4 rejected flush attempts and deterministic failure ... dropping (giving up) lines in the Director log are the only trace, so alert on those two phrases if you need to know when data is discarded.