> ## Documentation Index
> Fetch the complete documentation index at: https://docs.assetinfinity.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Connecting your SIEM

> An OpenTelemetry Collector configuration for Splunk, Microsoft Sentinel, Elastic and QRadar, for the security events this organisation sends.

This page shows you how to set up an OpenTelemetry Collector that receives this organisation's
[security events](/config/security-events) and forwards them to your SIEM. There is one setup for
each of Splunk, Microsoft Sentinel, Elastic and QRadar. They all share the same receiving half.

The product sends standard OpenTelemetry logs, so there is nothing product-specific to install. You
need the Collector's **contrib** distribution, `otelcol-contrib`, which includes an exporter for each
SIEM below. These configurations were tested with version 0.162.0.

<Note>
  **The exporters belong to their vendors.** Each vendor maintains its own exporter and changes it
  on its own schedule. If a setting below stops being accepted, check that exporter's page in the
  [Collector contrib repository](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter).
  It will be more up to date than this page.
</Note>

## The receiving half

Every setup below starts with this. It accepts OTLP over HTTPS and turns away any request without
the right token:

```yaml theme={null}
extensions:
  bearertokenauth:
    token: ${env:ASSET_INFINITY_STREAM_TOKEN}

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
        tls:
          cert_file: /etc/otelcol/tls/collector.crt
          key_file: /etc/otelcol/tls/collector.key
        auth:
          authenticator: bearertokenauth

processors:
  batch: {}
```

Make up a long random token and give it to the Collector as `ASSET_INFINITY_STREAM_TOKEN`. Then, on
**Security events**:

| Field | Value |
| - | - |
| **Your collector's address** | The Collector's public address followed by `/v1/logs`, such as `https://collector.example.com:4318/v1/logs` |
| **Header** | `Authorization` |
| **Credential** | `Bearer ` followed by the same token |

The address must start with `https://` and be reachable from the internet. The certificate must be
one a public client trusts, because the product will not send to a certificate it cannot verify.

Each setup below adds an `exporters` section and a `service` section to this. Save both halves in
one file, or pass them as two files with two `--config` options.

## Splunk

The `splunk_hec` exporter sends to Splunk's HTTP Event Collector. In Splunk, create an HTTP Event
Collector token that can write to the index you want to use. Then add:

```yaml theme={null}
exporters:
  splunk_hec:
    endpoint: https://splunk.example.com:8088/services/collector
    token: ${env:SPLUNK_HEC_TOKEN}
    source: asset-infinity
    sourcetype: asset-infinity:audit
    index: security

service:
  extensions: [bearertokenauth]
  pipelines:
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [splunk_hec]
```

Each event arrives with the sentence as the event text, and with every attribute as an indexed field:

```text theme={null}
index=security sourcetype="asset-infinity:audit" "asset_infinity.audit.event_type"=SIGN_IN_FAILED
| stats count by "user.email", "client.address"
```

## Microsoft Sentinel

The `azuremonitor` exporter sends to Application Insights. Create a workspace-based Application
Insights resource in the Log Analytics workspace that Sentinel uses, and copy its connection string.
Then add:

```yaml theme={null}
exporters:
  azuremonitor:
    connection_string: ${env:APPLICATIONINSIGHTS_CONNECTION_STRING}

service:
  extensions: [bearertokenauth]
  pipelines:
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [azuremonitor]
```

Events land in the workspace's `AppTraces` table. The sentence is in `Message`, and every attribute
is in `Properties`. Analytics rules query them like any other table:

```kusto theme={null}
AppTraces
| where Properties["asset_infinity.audit.event_type"] == "SIGN_IN_FAILED"
| summarize attempts = count() by tostring(Properties["user.email"]), tostring(Properties["client.address"]), bin(TimeGenerated, 15m)
| where attempts > 5
```

If your team already sends syslog to Sentinel through the Azure Monitor Agent, the
[QRadar setup](#qradar-and-other-syslog-siems) works too. Point it at the agent's syslog port
instead.

## Elastic

The `elasticsearch` exporter writes to Elasticsearch, including Elastic Cloud. Create an API key
that can write to `logs-*` data streams. Then add:

```yaml theme={null}
exporters:
  elasticsearch:
    endpoint: https://elastic.example.com:9200
    api_key: ${env:ELASTIC_API_KEY}
    mapping:
      mode: otel

service:
  extensions: [bearertokenauth]
  pipelines:
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [elasticsearch]
```

Events are written to the `logs-generic.otel-default` data stream in the OpenTelemetry layout.
Record attributes are under `attributes`, and the organisation is under `resource.attributes`:

```text theme={null}
attributes.asset_infinity.audit.event_type : "ACCOUNT_LOCKED"
```

## QRadar and other syslog SIEMs

The `syslog` exporter sends RFC 5424 syslog over TLS. It does not send the event itself, only a few
named attributes. So a `transform` processor first gives each event a syslog shape:

* the product as the app name
* the organisation's code as the host name
* the event type as the message ID
* a `key=value` line as the message

```yaml theme={null}
processors:
  transform/syslog:
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          - set(attributes["appname"], "asset-infinity")
          - set(attributes["hostname"], resource.attributes["asset_infinity.organization.code"])
          - set(attributes["msg_id"], attributes["asset_infinity.audit.event_type"])
          # Something the system did by itself has no person and no address.
          # "-" is syslog's own word for a value that is not there.
          - set(cache["user"], attributes["user.email"])
          - set(cache["user"], "-") where cache["user"] == nil
          - set(cache["src"], attributes["client.address"])
          - set(cache["src"], "-") where cache["src"] == nil
          - set(attributes["message"], Concat([
              "event=", attributes["asset_infinity.audit.event_type"],
              " user=", cache["user"],
              " src=", cache["src"],
              " entity=", attributes["asset_infinity.audit.entity_type"],
              " id=", attributes["log.record.uid"],
              " msg=", body], ""))

exporters:
  syslog:
    endpoint: qradar.example.com
    port: 6514
    network: tcp
    protocol: rfc5424
    tls:
      insecure: false

service:
  extensions: [bearertokenauth]
  pipelines:
    logs:
      receivers: [otlp]
      processors: [transform/syslog, batch]
      exporters: [syslog]
```

`processors` is written out again here because this adds to the one in the receiving half. Merge
the two, keeping `batch`.

QRadar receives lines like this one:

```text theme={null}
<165>1 2026-09-30T09:18:22.203582Z NORTHWIND asset-infinity - SIGN_IN_FAILED - event=SIGN_IN_FAILED user=ada@example.com src=203.0.113.9 entity=identity.users id=11111111-1111-4111-8111-111111111111 msg=Sign-in refused: wrong password (attempt 2 of 5)
```

In QRadar, add a log source for the Collector that uses the **TLS Syslog** protocol. Then map `src`
and `user` as custom properties, so rules can use them.

## A SIEM that accepts OpenTelemetry directly

Some SIEMs and log platforms accept OTLP over HTTPS themselves. For one of those, you don't need a
Collector. Put the platform's OTLP logs address, header and credential straight into **Security
events**.

## Sending only the security events

Changes to data are most of the trail. If your SIEM should receive only sign-ins, refusals and
lockouts, add a filter before the exporter. It drops every event whose type starts with `RECORD_`:

```yaml theme={null}
processors:
  filter/security-only:
    error_mode: ignore
    logs:
      log_record:
        - IsMatch(attributes["asset_infinity.audit.event_type"], "^RECORD_")
```

Add `filter/security-only` to the start of the pipeline's `processors` list.

## What each event carries

Each event is one OpenTelemetry log record. Its body is the one-line sentence, such as "Locked after
5 failed sign-ins, until 2026-09-30 14:05 UTC". Its event name is `asset_infinity.audit.` followed by
the event type. Its severity is always `INFO`, because an audit event records what happened, and your
SIEM's own rules decide which events matter.

Attributes follow the OpenTelemetry semantic conventions where one exists. Everything else is under
`asset_infinity.`.

| Attribute | Contents |
| - | - |
| `log.record.uid` | The event's ID. Use it to spot duplicates |
| `user.id` | The account that acted |
| `user.full_name` | The account's name |
| `user.email` | The account's email address |
| `client.address` | The address the request came from |
| `asset_infinity.audit.event_type` | `SIGNED_IN`, `SIGN_IN_FAILED`, `RECORD_MODIFIED` and so on. See [what your SIEM receives](/config/security-events#what-your-siem-receives) |
| `asset_infinity.audit.action` | What was done: `INSERT`, `UPDATE`, `DELETE` or `EXECUTE` |
| `asset_infinity.audit.entity_type` | The kind of record it touched |
| `asset_infinity.audit.entity_id` | The record's ID |
| `asset_infinity.audit.changed_fields` | The names of the fields that changed, as a list |
| `asset_infinity.audit.reason` | The reason, where the action asked for one |
| `asset_infinity.audit.source` | The kind of client: the web app, the field app, the API, the scheduler, an import |
| `asset_infinity.audit.cursor` | The event's position in the feed |
| `asset_infinity.actor.service` | The service account, when a machine acted |
| `asset_infinity.actor.device_id` | The field app device, when there was one |
| `asset_infinity.request.id` | The request the event was part of |

An attribute that has no value for a particular event is left out, not sent empty.

Three resource attributes say where every event in a request came from:

| Resource attribute | Contents |
| - | - |
| `service.name` | `asset-infinity` |
| `asset_infinity.organization.id` | This organisation's ID |
| `asset_infinity.organization.code` | This organisation's code. If one Collector serves several organisations, route on this |

## Checking it works

1. Start the Collector with your configuration.
2. On **Security events**, save the address, header and credential. Tick **Send events to this
   collector**.
3. Click **Send a test event**. The event reads "Test event from Asset Infinity. If this reached
   your SIEM, the stream is connected." Search your SIEM for it.

If the test is refused, the reason from your Collector appears under the form:

| Reason | Usual cause |
| - | - |
| A 401 | The credential on the screen does not match the Collector's token. Check it starts with `Bearer ` |
| A certificate error | The Collector's certificate is self-signed, expired, or for a different name |
| The address was refused | The address is not reachable from the internet |
| A timeout | A firewall is dropping the connection before it reaches the Collector |

A test that is accepted but never shows up in your SIEM is a problem between the Collector and the
SIEM. The Collector's own log will say which.
