diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5784ed08f27..c0eabfc2be8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -434,6 +434,7 @@ /packages/openai/data_stream/moderations @elastic/obs-infraobs-integrations /packages/openai/data_stream/rate_limits @elastic/obs-infraobs-integrations /packages/openai/data_stream/vector_stores @elastic/obs-infraobs-integrations +/packages/openai_chatgpt_enterprise @elastic/security-service-integrations /packages/opencanary @elastic/security-service-integrations @elastic/sit-crest-contractors /packages/oracle @elastic/obs-infraobs-integrations /packages/oracle_otel @elastic/obs-infraobs-integrations diff --git a/packages/openai_chatgpt_enterprise/_dev/build/build.yml b/packages/openai_chatgpt_enterprise/_dev/build/build.yml new file mode 100644 index 00000000000..ac5efe47b5c --- /dev/null +++ b/packages/openai_chatgpt_enterprise/_dev/build/build.yml @@ -0,0 +1,3 @@ +dependencies: + ecs: + reference: git@v9.4.0 diff --git a/packages/openai_chatgpt_enterprise/_dev/build/docs/README.md b/packages/openai_chatgpt_enterprise/_dev/build/docs/README.md new file mode 100644 index 00000000000..8026478511f --- /dev/null +++ b/packages/openai_chatgpt_enterprise/_dev/build/docs/README.md @@ -0,0 +1,127 @@ +# OpenAI ChatGPT Enterprise Integration for Elastic + +## Overview + +[OpenAI ChatGPT Enterprise](https://openai.com/enterprise) is the enterprise offering of ChatGPT, giving organizations administrative controls, security, and compliance capabilities for their use of ChatGPT and Codex. The OpenAI Compliance Logs Platform exposes an API that lets enterprises export compliance logs of activity across their workspace or organization, including application authentication activity such as connecting (linking) and disconnecting (unlinking) apps and connectors. + +This integration for Elastic allows you to collect ChatGPT Enterprise compliance logs using the OpenAI Compliance Logs Platform API, then visualize the data in Kibana. + +### Compatibility + +This integration collects data from the [OpenAI Compliance Logs Platform API](https://help.openai.com/en/articles/9261474-compliance-api-for-enterprise-customers) and requires a ChatGPT Enterprise plan with the Compliance Logs Platform enabled. + +### How it works + +This integration periodically queries the OpenAI Compliance Logs Platform API to retrieve application authentication logs. Collection can be scoped to a single **workspace** or an entire **organization**, and follows a two-step (chained) flow: + +1. The integration calls the list endpoint (`GET /v1/compliance/{workspaces|organizations}/{resource_id}/logs`) with the `event_type`, and paginates forward using the `last_end_time` cursor and `has_more` flag returned by the API. This returns metadata for each available log file. +2. For each listed file, the integration downloads its contents (`GET /v1/compliance/{workspaces|organizations}/{resource_id}/logs/{log_file_id}`). This endpoint redirects to a signed download URL that serves the log file as JSON Lines, and each line is ingested as a separate event. + +On the first run, logs are pulled back as far as the configured initial interval. From the second collection onward, each run resumes from the `last_end_time` cursor returned by the previous request, so events are collected without gaps or duplication. + +> Note: OpenAI retains compliance logs for a limited window (up to 30 days). Configure the initial interval accordingly when first enabling the integration. + +## What data does this integration collect? + +This integration collects log messages of the following type: + +- `Application Authentication Log`: Collect ChatGPT Enterprise `APP_AUTH_LOG` events, covering application authentication activity such as linking and unlinking apps and connectors (endpoints: `/v1/compliance/{workspaces|organizations}/{resource_id}/logs` and `/v1/compliance/{workspaces|organizations}/{resource_id}/logs/{log_file_id}`). + +### Supported use cases + +Bringing ChatGPT Enterprise application authentication activity into Elastic lets security, compliance, and platform teams search, correlate, and investigate app-connection activity in one place instead of moving between separate tools. + +The **Application Authentication Log** data stream provides visibility into which apps and connectors are linked or unlinked, who performed the action, and the client and request context associated with it. Use it to audit connector lifecycle changes, monitor app-authorization activity, and surface anomalous or high-risk link/unlink actions to support security oversight and auditing. + +## What do I need to use this integration? + +### From Elastic + +You need Elasticsearch for storing and searching your data and Kibana for visualizing and managing it. You can use our hosted Elasticsearch Service on Elastic Cloud, which is recommended, or self-manage the Elastic Stack on your own hardware. + +### From OpenAI + +To collect data through the OpenAI Compliance Logs Platform API, you need to provide the **Compliance API key** and the **workspace or organization ID** whose logs you want to collect. Authentication is handled using the Compliance API key, which serves as the required credential. + +#### Retrieve the Compliance API key and resource ID: + +1. Ensure your organization has a **ChatGPT Enterprise** plan with the **Compliance Logs Platform** enabled. +2. Request a **Compliance API key** from OpenAI and ensure it is authorized for enterprise logs (contact OpenAI support to grant the key the `chatgpt.enterprise.compliance_export.read` scope). +3. Identify the **workspace ID** or **organization ID** whose compliance logs you want to collect. +4. Copy both the **Compliance API key** and the **resource ID** and store them securely for use in the integration configuration. + +See [Compliance API for enterprise customers](https://help.openai.com/en/articles/9261474-compliance-api-for-enterprise-customers) for more details. + +## How do I deploy this integration? + +This integration supports both Elastic Agentless-based and Agent-based installations. + +### Agent-based deployment + +Elastic Agent must be installed. For more details, check the Elastic Agent [installation instructions](docs-content://reference/fleet/install-elastic-agents.md). You can install only one Elastic Agent per host. + +### Agentless deployment + +Agentless deployments are only supported in Elastic Serverless and Elastic Cloud environments. Agentless deployments provide a means to ingest data while avoiding the orchestration, management, and maintenance needs associated with standard ingest infrastructure. Using an agentless deployment makes manual agent deployment unnecessary, allowing you to focus on your data instead of the agent that collects it. + +For more information, refer to [Agentless integrations](https://www.elastic.co/guide/en/serverless/current/security-agentless-integrations.html) and [Agentless integrations FAQ](https://www.elastic.co/guide/en/serverless/current/agentless-integration-troubleshooting.html). + +### Configure + +1. In the top search bar in Kibana, search for **Integrations**. +2. In the search bar, type **OpenAI ChatGPT Enterprise**. +3. Select the **OpenAI ChatGPT Enterprise** integration from the search results. +4. Select **Add OpenAI ChatGPT Enterprise** to add the integration. +5. Configure the integration: + + * To **Collect ChatGPT Enterprise compliance logs**, you'll need to: + + - Configure the **URL** (default `https://api.chatgpt.com`) and **Compliance API key**. + - Set the **Scope** to `workspace` or `organization`. + - Set the **Workspace / Organization ID** to the resource whose compliance logs are collected. + - Adjust the integration configuration parameters if required, including the Initial Interval, Interval, HTTP Client Timeout etc. to enable data collection. + +6. Select **Save and continue** to save the integration. + +### Validation + +#### Dashboard populated + +1. In the top search bar in Kibana, search for **Dashboards**. +2. In the search bar, type **OpenAI ChatGPT Enterprise**, and verify the dashboard information is populated. + +## Troubleshooting + +For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). + +## Scaling + +For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. + +## Reference + +### App Auth Log + +The `app_auth_log` data stream captures ChatGPT Enterprise `APP_AUTH_LOG` events (application authentication activity such as linking and unlinking apps and connectors). + +#### App Auth Log fields + +{{ fields "app_auth_log" }} + +### Example event + +#### App Auth Log + +{{ event "app_auth_log" }} + +### Inputs used + +{{ inputDocs }} + +### API usage + +These APIs are used with this integration: + +* Application Authentication Log: + * List log files (endpoint: `/v1/compliance/{workspaces|organizations}/{resource_id}/logs`) + * Download log file (endpoint: `/v1/compliance/{workspaces|organizations}/{resource_id}/logs/{log_file_id}`) diff --git a/packages/openai_chatgpt_enterprise/_dev/deploy/docker/docker-compose.yml b/packages/openai_chatgpt_enterprise/_dev/deploy/docker/docker-compose.yml new file mode 100644 index 00000000000..cee389e808b --- /dev/null +++ b/packages/openai_chatgpt_enterprise/_dev/deploy/docker/docker-compose.yml @@ -0,0 +1,15 @@ +version: '3.8' +services: + openai_chatgpt_enterprise: + image: docker.elastic.co/observability/stream:v0.22.0 + hostname: openai_chatgpt_enterprise + ports: + - 8090 + volumes: + - ./files:/files:ro + environment: + PORT: '8090' + command: + - http-server + - --addr=:8090 + - --config=/files/config.yml diff --git a/packages/openai_chatgpt_enterprise/_dev/deploy/docker/files/config.yml b/packages/openai_chatgpt_enterprise/_dev/deploy/docker/files/config.yml new file mode 100644 index 00000000000..6c13747f80b --- /dev/null +++ b/packages/openai_chatgpt_enterprise/_dev/deploy/docker/files/config.yml @@ -0,0 +1,171 @@ +rules: + # ========================================================================= + # PHASE 1 — list log files (paginated). Returns file metadata only; the + # JSONL content is fetched per-file in phase 2 (see the /logs/{id} rules). + # ========================================================================= + + # --- list page 2 (requested with ?after=2026-07-09T12:00:00Z) --- + - path: /v1/compliance/workspaces/ws-example-001/logs + methods: ['GET'] + query_params: + event_type: APP_AUTH_LOG + limit: 2 + after: "2026-07-09T12:00:00Z" + request_headers: + Authorization: + - "Bearer xxxx" + responses: + - status_code: 200 + headers: + Content-Type: + - 'application/json' + body: |- + {{ minify_json ` + { + "data": [ + { + "id": "eclf_c3", + "event_type": "APP_AUTH_LOG", + "end_time": "2026-07-09T13:00:00Z", + "file_name": "APP_AUTH_LOG_2026-07-09T13:00:00+00:00.jsonl", + "file_size": 512, + "file_sha256": "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3" + } + ], + "has_more": false, + "last_end_time": "2026-07-09T13:00:00Z" + } + `}} + + # --- list page 1 (initial request; no ?after) --- + - path: /v1/compliance/workspaces/ws-example-001/logs + methods: ['GET'] + query_params: + event_type: APP_AUTH_LOG + limit: 2 + request_headers: + Authorization: + - "Bearer xxxx" + responses: + - status_code: 200 + headers: + Content-Type: + - 'application/json' + body: |- + {{ minify_json ` + { + "data": [ + { + "id": "eclf_a1", + "event_type": "APP_AUTH_LOG", + "end_time": "2026-07-09T10:11:00Z", + "file_name": "APP_AUTH_LOG_2026-07-09T10:11:00+00:00.jsonl", + "file_size": 1024, + "file_sha256": "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1" + }, + { + "id": "eclf_b2", + "event_type": "APP_AUTH_LOG", + "end_time": "2026-07-09T10:12:00Z", + "file_name": "APP_AUTH_LOG_2026-07-09T10:12:00+00:00.jsonl", + "file_size": 768, + "file_sha256": "b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2" + } + ], + "has_more": true, + "last_end_time": "2026-07-09T12:00:00Z" + } + `}} + + # ========================================================================= + # PHASE 2 — download each log file's JSONL body. The real API 307-redirects + # to a signed URL; the mock serves the JSON Lines content directly (200). + # Each line is one APP_AUTH_LOG event and is emitted as a separate document. + # ========================================================================= + + # --- download file eclf_a1 (2 events) --- + - path: /v1/compliance/workspaces/ws-example-001/logs/eclf_a1 + methods: ['GET'] + request_headers: + Authorization: + - "Bearer xxxx" + responses: + - status_code: 200 + headers: + Content-Type: + - 'application/x-ndjson' + body: |- + {{ minify_json ` + { + "event_id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c001", + "type": "APP_AUTH_LOG", + "timestamp": "2026-07-09T10:10:00.000000Z", + "principal": { "id": "be545252-ad04-4cfa-9ca5-deca58416151", "type": "CHATGPT_WORKSPACE" }, + "actor": { "type": "ACCOUNT_USER", "user_id": "user-AbCdEf1234567890", "user_email": "user@example.org" }, + "app_id": "asdk_app_0123456789abcdef0123456789abcdef", + "link_id": "link_00112233445566778899aabbccddeeff", + "action": "link" + } + `}} + {{ minify_json ` + { + "event_id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c002", + "type": "APP_AUTH_LOG", + "timestamp": "2026-07-09T10:11:00.000000Z", + "principal": { "id": "be545252-ad04-4cfa-9ca5-deca58416151", "type": "CHATGPT_WORKSPACE" }, + "actor": { "type": "ACCOUNT_USER", "user_id": "user-AbCdEf1234567890", "user_email": "user@example.org" }, + "app_id": null, + "link_id": "link_00112233445566778899aabbccddeeff", + "action": "unlink" + } + `}} + + # --- download file eclf_b2 (1 event) --- + - path: /v1/compliance/workspaces/ws-example-001/logs/eclf_b2 + methods: ['GET'] + request_headers: + Authorization: + - "Bearer xxxx" + responses: + - status_code: 200 + headers: + Content-Type: + - 'application/x-ndjson' + body: |- + {{ minify_json ` + { + "event_id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c003", + "type": "APP_AUTH_LOG", + "timestamp": "2026-07-09T10:12:00.000000Z", + "principal": { "id": "be545252-ad04-4cfa-9ca5-deca58416151", "type": "CHATGPT_WORKSPACE" }, + "actor": { "type": "ACCOUNT_USER", "user_id": "user-ZyXwVu0987654321", "user_email": "admin@example.org" }, + "app_id": "connector_fedcba9876543210fedcba9876543210", + "link_id": "link_ffeeddccbbaa99887766554433221100", + "action": "link" + } + `}} + + # --- download file eclf_c3 (1 event) --- + - path: /v1/compliance/workspaces/ws-example-001/logs/eclf_c3 + methods: ['GET'] + request_headers: + Authorization: + - "Bearer xxxx" + responses: + - status_code: 200 + headers: + Content-Type: + - 'application/x-ndjson' + body: |- + {{ minify_json ` + { + "event_id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c004", + "type": "APP_AUTH_LOG", + "timestamp": "2026-07-09T13:00:00.000000Z", + "principal": { "id": "be545252-ad04-4cfa-9ca5-deca58416151", "type": "CHATGPT_WORKSPACE" }, + "actor": { "type": "ACCOUNT_USER", "user_id": "user-ZyXwVu0987654321", "user_email": "admin@example.org" }, + "app_id": null, + "link_id": "link_ffeeddccbbaa99887766554433221100", + "action": "unlink" + } + `}} diff --git a/packages/openai_chatgpt_enterprise/changelog.yml b/packages/openai_chatgpt_enterprise/changelog.yml new file mode 100644 index 00000000000..67a3b6f9293 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: '0.1.0' + changes: + - description: Initial release of the OpenAI ChatGPT Enterprise app_auth_log data stream. + type: enhancement + link: https://github.com/elastic/integrations/pull/20203 diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/pipeline/test-app-auth-log.log b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/pipeline/test-app-auth-log.log new file mode 100644 index 00000000000..2586789d08d --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/pipeline/test-app-auth-log.log @@ -0,0 +1,3 @@ +{"event_id":"7b3e4de1-2219-4d6c-9d6e-8f4d40c7c001","type":"APP_AUTH_LOG","principal":{"id":"be545252-ad04-4cfa-9ca5-deca58416151","type":"CHATGPT_WORKSPACE"},"actor":{"type":"ACCOUNT_USER","user_id":"user-AbCdEf1234567890","user_email":"user@example.org"},"timestamp":"2026-07-09T20:33:11.112450Z","app_id":"asdk_app_0123456789abcdef0123456789abcdef","link_id":"link_00112233445566778899aabbccddeeff","action":"link"} +{"event_id":"7b3e4de1-2219-4d6c-9d6e-8f4d40c7c002","type":"APP_AUTH_LOG","principal":{"id":"be545252-ad04-4cfa-9ca5-deca58416151","type":"CHATGPT_WORKSPACE"},"actor":{"type":"ACCOUNT_USER","user_id":"user-ZyXwVu0987654321","user_email":"admin@example.org"},"timestamp":"2026-07-09T23:10:55.000000Z","app_id":"connector_fedcba9876543210fedcba9876543210","link_id":"link_ffeeddccbbaa99887766554433221100","action":"link"} +{"event_id":"7b3e4de1-2219-4d6c-9d6e-8f4d40c7c003","type":"APP_AUTH_LOG","principal":{"id":"be545252-ad04-4cfa-9ca5-deca58416151","type":"CHATGPT_WORKSPACE"},"actor":{"type":"ACCOUNT_USER","user_id":"user-AbCdEf1234567890","user_email":"user@example.org"},"timestamp":"2026-07-10T08:15:02.500000Z","app_id":null,"link_id":"link_00112233445566778899aabbccddeeff","action":"unlink"} diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/pipeline/test-app-auth-log.log-expected.json b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/pipeline/test-app-auth-log.log-expected.json new file mode 100644 index 00000000000..a271d9ed586 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/pipeline/test-app-auth-log.log-expected.json @@ -0,0 +1,147 @@ +{ + "expected": [ + { + "@timestamp": "2026-07-09T20:33:11.112Z", + "ecs": { + "version": "9.3.0" + }, + "event": { + "action": "link", + "category": [ + "configuration" + ], + "id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c001", + "kind": "event", + "original": "{\"event_id\":\"7b3e4de1-2219-4d6c-9d6e-8f4d40c7c001\",\"type\":\"APP_AUTH_LOG\",\"principal\":{\"id\":\"be545252-ad04-4cfa-9ca5-deca58416151\",\"type\":\"CHATGPT_WORKSPACE\"},\"actor\":{\"type\":\"ACCOUNT_USER\",\"user_id\":\"user-AbCdEf1234567890\",\"user_email\":\"user@example.org\"},\"timestamp\":\"2026-07-09T20:33:11.112450Z\",\"app_id\":\"asdk_app_0123456789abcdef0123456789abcdef\",\"link_id\":\"link_00112233445566778899aabbccddeeff\",\"action\":\"link\"}", + "type": [ + "change" + ] + }, + "gen_ai": { + "system": "openai" + }, + "openai_chatgpt_enterprise": { + "app_auth_log": { + "actor": { + "type": "ACCOUNT_USER" + }, + "app_id": "asdk_app_0123456789abcdef0123456789abcdef", + "link_id": "link_00112233445566778899aabbccddeeff", + "principal": { + "type": "CHATGPT_WORKSPACE" + }, + "type": "APP_AUTH_LOG" + } + }, + "organization": { + "id": "be545252-ad04-4cfa-9ca5-deca58416151" + }, + "related": { + "user": [ + "user@example.org", + "user-AbCdEf1234567890" + ] + }, + "user": { + "domain": "example.org", + "email": "user@example.org", + "id": "user-AbCdEf1234567890" + } + }, + { + "@timestamp": "2026-07-09T23:10:55.000Z", + "ecs": { + "version": "9.3.0" + }, + "event": { + "action": "link", + "category": [ + "configuration" + ], + "id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c002", + "kind": "event", + "original": "{\"event_id\":\"7b3e4de1-2219-4d6c-9d6e-8f4d40c7c002\",\"type\":\"APP_AUTH_LOG\",\"principal\":{\"id\":\"be545252-ad04-4cfa-9ca5-deca58416151\",\"type\":\"CHATGPT_WORKSPACE\"},\"actor\":{\"type\":\"ACCOUNT_USER\",\"user_id\":\"user-ZyXwVu0987654321\",\"user_email\":\"admin@example.org\"},\"timestamp\":\"2026-07-09T23:10:55.000000Z\",\"app_id\":\"connector_fedcba9876543210fedcba9876543210\",\"link_id\":\"link_ffeeddccbbaa99887766554433221100\",\"action\":\"link\"}", + "type": [ + "change" + ] + }, + "gen_ai": { + "system": "openai" + }, + "openai_chatgpt_enterprise": { + "app_auth_log": { + "actor": { + "type": "ACCOUNT_USER" + }, + "app_id": "connector_fedcba9876543210fedcba9876543210", + "link_id": "link_ffeeddccbbaa99887766554433221100", + "principal": { + "type": "CHATGPT_WORKSPACE" + }, + "type": "APP_AUTH_LOG" + } + }, + "organization": { + "id": "be545252-ad04-4cfa-9ca5-deca58416151" + }, + "related": { + "user": [ + "admin@example.org", + "user-ZyXwVu0987654321" + ] + }, + "user": { + "domain": "example.org", + "email": "admin@example.org", + "id": "user-ZyXwVu0987654321" + } + }, + { + "@timestamp": "2026-07-10T08:15:02.500Z", + "ecs": { + "version": "9.3.0" + }, + "event": { + "action": "unlink", + "category": [ + "configuration" + ], + "id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c003", + "kind": "event", + "original": "{\"event_id\":\"7b3e4de1-2219-4d6c-9d6e-8f4d40c7c003\",\"type\":\"APP_AUTH_LOG\",\"principal\":{\"id\":\"be545252-ad04-4cfa-9ca5-deca58416151\",\"type\":\"CHATGPT_WORKSPACE\"},\"actor\":{\"type\":\"ACCOUNT_USER\",\"user_id\":\"user-AbCdEf1234567890\",\"user_email\":\"user@example.org\"},\"timestamp\":\"2026-07-10T08:15:02.500000Z\",\"app_id\":null,\"link_id\":\"link_00112233445566778899aabbccddeeff\",\"action\":\"unlink\"}", + "type": [ + "change" + ] + }, + "gen_ai": { + "system": "openai" + }, + "openai_chatgpt_enterprise": { + "app_auth_log": { + "actor": { + "type": "ACCOUNT_USER" + }, + "link_id": "link_00112233445566778899aabbccddeeff", + "principal": { + "type": "CHATGPT_WORKSPACE" + }, + "type": "APP_AUTH_LOG" + } + }, + "organization": { + "id": "be545252-ad04-4cfa-9ca5-deca58416151" + }, + "related": { + "user": [ + "user@example.org", + "user-AbCdEf1234567890" + ] + }, + "user": { + "domain": "example.org", + "email": "user@example.org", + "id": "user-AbCdEf1234567890" + } + } + ] +} diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/system/test-default-config.yml b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/system/test-default-config.yml new file mode 100644 index 00000000000..5dc80320cce --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/_dev/test/system/test-default-config.yml @@ -0,0 +1,16 @@ +wait_for_data_timeout: 1m +input: cel +service: openai_chatgpt_enterprise +vars: + url: http://{{Hostname}}:{{Port}} + admin_token: xxxx +data_stream: + vars: + scope: workspace + resource_id: ws-example-001 + interval: 2s + batch_size: 2 + initial_interval: 24h + preserve_original_event: true +assert: + hit_count: 4 diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/agent/stream/cel.yml.hbs b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/agent/stream/cel.yml.hbs new file mode 100644 index 00000000000..f3f000d881c --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/agent/stream/cel.yml.hbs @@ -0,0 +1,155 @@ +interval: {{interval}} +resource.tracer: + enabled: {{enable_request_tracer}} + filename: "../../logs/cel/http-request-trace-*.ndjson" + maxbackups: 5 +{{#if proxy_url}} +resource.proxy_url: {{proxy_url}} +{{/if}} +{{#if ssl}} +resource.ssl: {{ssl}} +{{/if}} +{{#if http_client_timeout}} +resource.timeout: {{http_client_timeout}} +{{/if}} +resource.url: {{url}} +state: + admin_token: {{admin_token}} + scope: {{scope}} + resource_id: {{resource_id}} + initial_interval: {{initial_interval}} + batch_size: {{batch_size}} +redact: + fields: + - admin_token +program: |- + ( + // PHASE 1 — list files (only when the worklist is empty). This is the only + // paginated call: it pages forward with `after` = last_end_time (ascending). + (has(state.worklist) && size(state.worklist) > 0) ? + state + : + state.?cursor.last_end_time.orValue(string(now - duration(state.initial_interval))).as(after, + state.with( + request( + "GET", + state.url.trim_right("/") + "/v1/compliance/" + ((state.scope == "organization") ? "organizations" : "workspaces") + "/" + string(state.resource_id) + "/logs?" + { + "event_type": ["APP_AUTH_LOG"], + "after": [after], + "limit": [string(int(state.batch_size))], + }.format_query() + ).with( + { + "Header": { + "Authorization": ["Bearer " + state.admin_token], + }, + } + ).do_request().as(resp, (resp.StatusCode == 200) ? + bytes(resp.Body).decode_json().as(body, + { + "worklist": body.?data.orValue([]), + "cursor": { + // Empty pages return last_end_time: null; keep the current + // `after` in that case so the cursor never becomes null. + "last_end_time": (has(body.last_end_time) && body.last_end_time != null) ? + body.last_end_time + : + after, + }, + // Termination is driven by has_more, not by an empty page: this + // API re-returns the boundary record when re-queried with the same + // `after`, so an empty response is never observed on has_more=false. + "list_has_more": body.?has_more.orValue(false), + "want_more": size(body.?data.orValue([])) > 0, + } + ) + : + { + "events": { + "error": { + "code": string(resp.StatusCode), + "id": resp.Status, + "message": "GET " + state.url.trim_right("/") + "/v1/compliance/logs: " + ( + (size(resp.Body) != 0) ? + string(resp.Body) + : + resp.Status + " (" + string(resp.StatusCode) + ")" + ), + }, + }, + "want_more": false, + } + ) + ) + ) + ).as(state, + // PHASE 2 — download the first queued file, then drop it from the worklist. + // This endpoint 307-redirects to a signed URL serving the JSON Lines body + // (redirect auto-followed). It has no pagination of its own. + !has(state.worklist) ? + state + : (size(state.worklist) > 0) ? + state.with( + request( + "GET", + state.url.trim_right("/") + "/v1/compliance/" + ((state.scope == "organization") ? "organizations" : "workspaces") + "/" + string(state.resource_id) + "/logs/" + string(state.worklist[0].id) + ).with( + { + "Header": { + "Authorization": ["Bearer " + state.admin_token], + }, + } + ).do_request().as(resp, (resp.StatusCode == 200) ? + { + "events": string(resp.Body).split("\n").map(l, l.trim(" \r\n\t")).filter(l, l != "").map(l, + { + "message": l, + } + ), + "worklist": tail(state.worklist), + // Keep going while files remain in this page, or the list reported + // more pages. Stops right after the last file of the last page. + "want_more": size(state.worklist) > 1 || state.?list_has_more.orValue(false), + } + : + { + "events": { + "error": { + "code": string(resp.StatusCode), + "id": resp.Status, + "message": "GET " + state.url.trim_right("/") + "/v1/compliance/logs/" + string(state.worklist[0].id) + ": " + ( + (size(resp.Body) != 0) ? + string(resp.Body) + : + resp.Status + " (" + string(resp.StatusCode) + ")" + ), + }, + }, + "worklist": tail(state.worklist), + "want_more": size(state.worklist) > 1 || state.?list_has_more.orValue(false), + } + ) + ) + : + state.with( + { + "events": [], + "want_more": false, + } + ) + ) + +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#each tags as |tag|}} + - {{tag}} +{{/each}} +{{#contains "forwarded" tags}} +publisher_pipeline.disable_host: true +{{/contains}} +{{#if processors}} +processors: +{{processors}} +{{/if}} diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/elasticsearch/ingest_pipeline/default.yml b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/elasticsearch/ingest_pipeline/default.yml new file mode 100644 index 00000000000..f815e00b184 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/elasticsearch/ingest_pipeline/default.yml @@ -0,0 +1,188 @@ +--- +description: Parse OpenAI ChatGPT Enterprise Application Authentication Logs events. +processors: + - set: + field: ecs.version + tag: set_ecs_version + value: '9.3.0' + - remove: + field: + - organization + - division + - team + ignore_missing: true + if: ctx.organization instanceof String && ctx.division instanceof String && ctx.team instanceof String + tag: remove_agentless_tags + description: >- + Removes the fields added by Agentless as metadata, + as they can collide with ECS fields. + - terminate: + tag: data_collection_error + if: ctx.error?.message != null && ctx.message == null && ctx.event?.original == null + description: error message set and no data to process. + - rename: + field: message + tag: rename_message_to_event_original + target_field: event.original + ignore_missing: true + description: >- + Renames the original `message` field to `event.original` to store a copy of the original message. + if: ctx.event?.original == null + - remove: + field: message + tag: remove_message + ignore_missing: true + description: The `message` field is no longer required if the document has an `event.original` field. + if: ctx.event?.original != null + - json: + field: event.original + target_field: openai_chatgpt_enterprise.app_auth_log + tag: parse_json + if: ctx.event?.original != null + - fingerprint: + fields: + - openai_chatgpt_enterprise.app_auth_log.event_id + target_field: _id + ignore_missing: true + tag: fingerprint_event_id + - set: + field: gen_ai.system + tag: set_gen_ai_system + value: openai + - set: + field: event.id + tag: set_event_id + copy_from: openai_chatgpt_enterprise.app_auth_log.event_id + ignore_empty_value: true + - date: + field: openai_chatgpt_enterprise.app_auth_log.timestamp + target_field: openai_chatgpt_enterprise.app_auth_log.timestamp + formats: + - ISO8601 + tag: date_timestamp + if: ctx.openai_chatgpt_enterprise?.app_auth_log?.timestamp != null && ctx.openai_chatgpt_enterprise.app_auth_log.timestamp != '' + on_failure: + - remove: + field: openai_chatgpt_enterprise.app_auth_log.timestamp + ignore_missing: true + tag: remove_timestamp_on_failure + - append: + field: error.message + value: 'Processor {{{_ingest.on_failure_processor_type}}} with tag {{{_ingest.on_failure_processor_tag}}} in pipeline {{{_ingest.on_failure_pipeline}}} failed with message: {{{_ingest.on_failure_message}}}' + - set: + field: '@timestamp' + tag: set_@timestamp_from_timestamp + copy_from: openai_chatgpt_enterprise.app_auth_log.timestamp + ignore_empty_value: true + - set: + field: event.kind + tag: set_event_kind + value: event + - set: + field: user.id + tag: set_user_id_from_actor + copy_from: openai_chatgpt_enterprise.app_auth_log.actor.user_id + ignore_empty_value: true + - set: + field: user.email + tag: set_user_email_from_actor + copy_from: openai_chatgpt_enterprise.app_auth_log.actor.user_email + ignore_empty_value: true + - dissect: + field: user.email + pattern: '%{}@%{user.domain}' + tag: dissect_user_domain_from_email + if: ctx.user?.email != null && ctx.user.email.contains('@') + - set: + field: organization.id + tag: set_organization_id_from_principal + copy_from: openai_chatgpt_enterprise.app_auth_log.principal.id + ignore_empty_value: true + if: ctx.openai_chatgpt_enterprise?.app_auth_log?.principal?.id != null + - set: + field: event.action + tag: set_event_action + copy_from: openai_chatgpt_enterprise.app_auth_log.action + ignore_empty_value: true + - lowercase: + field: event.action + tag: lowercase_event_action + ignore_missing: true + - append: + field: event.category + tag: append_event_category_app_auth_log + value: configuration + - append: + field: event.type + tag: append_event_type_app_auth_log + value: change + - append: + field: related.user + tag: append_user_email_to_related + value: '{{{user.email}}}' + allow_duplicates: false + if: ctx.user?.email != null + - append: + field: related.user + tag: append_user_id_to_related + value: '{{{user.id}}}' + allow_duplicates: false + if: ctx.user?.id != null + - remove: + field: + - openai_chatgpt_enterprise.app_auth_log.event_id + - openai_chatgpt_enterprise.app_auth_log.timestamp + - openai_chatgpt_enterprise.app_auth_log.actor.user_id + - openai_chatgpt_enterprise.app_auth_log.actor.user_email + - openai_chatgpt_enterprise.app_auth_log.principal.id + - openai_chatgpt_enterprise.app_auth_log.action + tag: remove_custom_fields_mapped_to_ecs + ignore_missing: true + description: Remove custom fields that were copied to ECS fields. + - script: + lang: painless + tag: remove_null_values + description: Remove null or empty values from the document. + source: |- + void handleMap(Map map) { + map.values().removeIf(v -> { + if (v instanceof Map) { + handleMap(v); + } else if (v instanceof List) { + handleList(v); + } + return v == null || v == '' || (v instanceof Map && v.size() == 0) || (v instanceof List && v.size() == 0) + }); + } + void handleList(List list) { + list.removeIf(v -> { + if (v instanceof Map) { + handleMap(v); + } else if (v instanceof List) { + handleList(v); + } + return v == null || v == '' || (v instanceof Map && v.size() == 0) || (v instanceof List && v.size() == 0) + }); + } + handleMap(ctx); + - append: + field: tags + tag: append_preserve_on_collector_error + value: preserve_original_event + allow_duplicates: false + if: ctx.error?.message != null +on_failure: + - set: + field: event.kind + tag: set_pipeline_error_to_event_kind + value: pipeline_error + - append: + field: error.message + value: >- + Processor '{{{ _ingest.on_failure_processor_type }}}' + {{{#_ingest.on_failure_processor_tag}}}with tag '{{{ _ingest.on_failure_processor_tag }}}' + {{{/_ingest.on_failure_processor_tag}}}failed with message '{{{ _ingest.on_failure_message }}}' + - append: + field: tags + value: preserve_original_event + allow_duplicates: false diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/base-fields.yml b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/base-fields.yml new file mode 100644 index 00000000000..f0ca2b91222 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/base-fields.yml @@ -0,0 +1,16 @@ +- name: data_stream.type + external: ecs +- name: data_stream.dataset + external: ecs +- name: data_stream.namespace + external: ecs +- name: event.module + type: constant_keyword + external: ecs + value: openai_chatgpt_enterprise +- name: event.dataset + type: constant_keyword + external: ecs + value: openai_chatgpt_enterprise.app_auth_log +- name: '@timestamp' + external: ecs diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/beats.yml b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/beats.yml new file mode 100644 index 00000000000..3382e376e77 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/beats.yml @@ -0,0 +1,3 @@ +- name: input.type + type: keyword + description: Type of filebeat input. diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/ecs.yml b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/ecs.yml new file mode 100644 index 00000000000..ce3656be6d5 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/ecs.yml @@ -0,0 +1,9 @@ +# Define ECS constant fields as constant_keyword +- external: ecs + name: observer.product + type: constant_keyword + value: ChatGPT Enterprise Compliance +- external: ecs + name: observer.vendor + type: constant_keyword + value: OpenAI diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/fields.yml b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/fields.yml new file mode 100644 index 00000000000..9d45e23c0b9 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/fields/fields.yml @@ -0,0 +1,25 @@ +- name: openai_chatgpt_enterprise.app_auth_log + type: group + description: OpenAI ChatGPT Enterprise Application Authentication Logs event fields. + fields: + - name: actor + type: group + fields: + - name: type + type: keyword + description: Type of actor that performed the action (e.g. ACCOUNT_USER, API_KEY). + - name: app_id + type: keyword + description: Identifier of the connected app / connector. + - name: link_id + type: keyword + description: Identifier of the app connection (link) that was linked/unlinked. + - name: principal + type: group + fields: + - name: type + type: keyword + description: Principal that owns the event (e.g. CHATGPT_WORKSPACE). + - name: type + type: keyword + description: Top-level event category (e.g. APP_AUTH_LOG). diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/manifest.yml b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/manifest.yml new file mode 100644 index 00000000000..1d37af56dba --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/manifest.yml @@ -0,0 +1,98 @@ +title: Application Authentication Logs +type: logs +streams: + - input: cel + title: Application Authentication Logs + enabled: true + description: Collect Application Authentication Logs from the OpenAI ChatGPT Enterprise Compliance Logs Platform. + template_path: cel.yml.hbs + vars: + - name: scope + type: select + title: Scope + description: >- + Whether to collect compliance logs at the `workspace` or `organization` level. + multi: false + required: true + show_user: true + default: workspace + options: + - value: workspace + text: Workspace + - value: organization + text: Organization + - name: resource_id + type: text + title: Workspace / Organization ID + description: >- + The ChatGPT Enterprise workspace ID (when Scope is `workspace`) or the organization ID (when Scope is `organization`) whose compliance logs will be collected. + multi: false + required: true + show_user: true + - name: initial_interval + type: text + title: Initial Interval + description: How far back to pull compliance logs on first run. Supported units are h/m/s. + multi: false + required: true + show_user: true + default: 24h + - name: interval + type: text + title: Interval + description: Duration between requests to the OpenAI Compliance Logs Platform API. Supported units for this parameter are h/m/s. + default: 24h + multi: false + required: true + show_user: true + - name: batch_size + type: integer + title: Batch Size + description: Number of log files to request per listing page. This should be between 1 and 100. + default: 100 + multi: false + required: true + show_user: false + - name: http_client_timeout + type: text + title: HTTP Client Timeout + description: Duration before declaring that the HTTP client connection has timed out. Valid time units are ns, us, ms, s, m, h. + multi: false + required: true + show_user: false + default: 30s + - name: enable_request_tracer + type: bool + title: Enable request tracing + default: false + multi: false + required: false + show_user: false + description: >- + The request tracer logs requests and responses to the agent's local file-system for debugging configurations. Enabling this request tracing compromises security and should only be used for debugging. Disabling the request tracer will delete any stored traces. See [documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-cel.html#_resource_tracer_enable) for details. + - name: tags + type: text + title: Tags + description: Tags for the data-stream. + multi: true + required: true + show_user: false + default: + - forwarded + - openai_chatgpt_enterprise-app_auth_log + - name: preserve_original_event + required: false + show_user: true + title: Preserve original event + description: Preserves a raw copy of the original event, added to the field `event.original`. + type: bool + multi: false + default: false + - name: processors + type: yaml + title: Processors + multi: false + required: false + show_user: false + description: >- + Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details. diff --git a/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/sample_event.json b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/sample_event.json new file mode 100644 index 00000000000..85763fffdd1 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/data_stream/app_auth_log/sample_event.json @@ -0,0 +1,76 @@ +{ + "@timestamp": "2026-07-09T10:10:00.000Z", + "agent": { + "ephemeral_id": "7eb4bbfe-bb77-43cd-8939-efadb1796d96", + "id": "d0be91b9-cbd1-4fdd-b4e1-aeea6dade4e7", + "name": "elastic-agent-98363", + "type": "filebeat", + "version": "8.19.0" + }, + "data_stream": { + "dataset": "openai_chatgpt_enterprise.app_auth_log", + "namespace": "39305", + "type": "logs" + }, + "ecs": { + "version": "9.3.0" + }, + "elastic_agent": { + "id": "d0be91b9-cbd1-4fdd-b4e1-aeea6dade4e7", + "snapshot": false, + "version": "8.19.0" + }, + "event": { + "action": "link", + "agent_id_status": "verified", + "category": [ + "configuration" + ], + "dataset": "openai_chatgpt_enterprise.app_auth_log", + "id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c001", + "ingested": "2026-07-19T08:36:02Z", + "kind": "event", + "original": "{\"event_id\":\"7b3e4de1-2219-4d6c-9d6e-8f4d40c7c001\",\"type\":\"APP_AUTH_LOG\",\"timestamp\":\"2026-07-09T10:10:00.000000Z\",\"principal\":{\"id\":\"be545252-ad04-4cfa-9ca5-deca58416151\",\"type\":\"CHATGPT_WORKSPACE\"},\"actor\":{\"type\":\"ACCOUNT_USER\",\"user_id\":\"user-AbCdEf1234567890\",\"user_email\":\"user@example.org\"},\"app_id\":\"asdk_app_0123456789abcdef0123456789abcdef\",\"link_id\":\"link_00112233445566778899aabbccddeeff\",\"action\":\"link\"}", + "type": [ + "change" + ] + }, + "gen_ai": { + "system": "openai" + }, + "input": { + "type": "cel" + }, + "openai_chatgpt_enterprise": { + "app_auth_log": { + "actor": { + "type": "ACCOUNT_USER" + }, + "app_id": "asdk_app_0123456789abcdef0123456789abcdef", + "link_id": "link_00112233445566778899aabbccddeeff", + "principal": { + "type": "CHATGPT_WORKSPACE" + }, + "type": "APP_AUTH_LOG" + } + }, + "organization": { + "id": "be545252-ad04-4cfa-9ca5-deca58416151" + }, + "related": { + "user": [ + "user@example.org", + "user-AbCdEf1234567890" + ] + }, + "tags": [ + "preserve_original_event", + "forwarded", + "openai_chatgpt_enterprise-app_auth_log" + ], + "user": { + "domain": "example.org", + "email": "user@example.org", + "id": "user-AbCdEf1234567890" + } +} diff --git a/packages/openai_chatgpt_enterprise/docs/README.md b/packages/openai_chatgpt_enterprise/docs/README.md new file mode 100644 index 00000000000..26eaf659378 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/docs/README.md @@ -0,0 +1,249 @@ +# OpenAI ChatGPT Enterprise Integration for Elastic + +## Overview + +[OpenAI ChatGPT Enterprise](https://openai.com/enterprise) is the enterprise offering of ChatGPT, giving organizations administrative controls, security, and compliance capabilities for their use of ChatGPT and Codex. The OpenAI Compliance Logs Platform exposes an API that lets enterprises export compliance logs of activity across their workspace or organization, including application authentication activity such as connecting (linking) and disconnecting (unlinking) apps and connectors. + +This integration for Elastic allows you to collect ChatGPT Enterprise compliance logs using the OpenAI Compliance Logs Platform API, then visualize the data in Kibana. + +### Compatibility + +This integration collects data from the [OpenAI Compliance Logs Platform API](https://help.openai.com/en/articles/9261474-compliance-api-for-enterprise-customers) and requires a ChatGPT Enterprise plan with the Compliance Logs Platform enabled. + +### How it works + +This integration periodically queries the OpenAI Compliance Logs Platform API to retrieve application authentication logs. Collection can be scoped to a single **workspace** or an entire **organization**, and follows a two-step (chained) flow: + +1. The integration calls the list endpoint (`GET /v1/compliance/{workspaces|organizations}/{resource_id}/logs`) with the `event_type`, and paginates forward using the `last_end_time` cursor and `has_more` flag returned by the API. This returns metadata for each available log file. +2. For each listed file, the integration downloads its contents (`GET /v1/compliance/{workspaces|organizations}/{resource_id}/logs/{log_file_id}`). This endpoint redirects to a signed download URL that serves the log file as JSON Lines, and each line is ingested as a separate event. + +On the first run, logs are pulled back as far as the configured initial interval. From the second collection onward, each run resumes from the `last_end_time` cursor returned by the previous request, so events are collected without gaps or duplication. + +> Note: OpenAI retains compliance logs for a limited window (up to 30 days). Configure the initial interval accordingly when first enabling the integration. + +## What data does this integration collect? + +This integration collects log messages of the following type: + +- `Application Authentication Log`: Collect ChatGPT Enterprise `APP_AUTH_LOG` events, covering application authentication activity such as linking and unlinking apps and connectors (endpoints: `/v1/compliance/{workspaces|organizations}/{resource_id}/logs` and `/v1/compliance/{workspaces|organizations}/{resource_id}/logs/{log_file_id}`). + +### Supported use cases + +Bringing ChatGPT Enterprise application authentication activity into Elastic lets security, compliance, and platform teams search, correlate, and investigate app-connection activity in one place instead of moving between separate tools. + +The **Application Authentication Log** data stream provides visibility into which apps and connectors are linked or unlinked, who performed the action, and the client and request context associated with it. Use it to audit connector lifecycle changes, monitor app-authorization activity, and surface anomalous or high-risk link/unlink actions to support security oversight and auditing. + +## What do I need to use this integration? + +### From Elastic + +You need Elasticsearch for storing and searching your data and Kibana for visualizing and managing it. You can use our hosted Elasticsearch Service on Elastic Cloud, which is recommended, or self-manage the Elastic Stack on your own hardware. + +### From OpenAI + +To collect data through the OpenAI Compliance Logs Platform API, you need to provide the **Compliance API key** and the **workspace or organization ID** whose logs you want to collect. Authentication is handled using the Compliance API key, which serves as the required credential. + +#### Retrieve the Compliance API key and resource ID: + +1. Ensure your organization has a **ChatGPT Enterprise** plan with the **Compliance Logs Platform** enabled. +2. Request a **Compliance API key** from OpenAI and ensure it is authorized for enterprise logs (contact OpenAI support to grant the key the `chatgpt.enterprise.compliance_export.read` scope). +3. Identify the **workspace ID** or **organization ID** whose compliance logs you want to collect. +4. Copy both the **Compliance API key** and the **resource ID** and store them securely for use in the integration configuration. + +See [Compliance API for enterprise customers](https://help.openai.com/en/articles/9261474-compliance-api-for-enterprise-customers) for more details. + +## How do I deploy this integration? + +This integration supports both Elastic Agentless-based and Agent-based installations. + +### Agent-based deployment + +Elastic Agent must be installed. For more details, check the Elastic Agent [installation instructions](docs-content://reference/fleet/install-elastic-agents.md). You can install only one Elastic Agent per host. + +### Agentless deployment + +Agentless deployments are only supported in Elastic Serverless and Elastic Cloud environments. Agentless deployments provide a means to ingest data while avoiding the orchestration, management, and maintenance needs associated with standard ingest infrastructure. Using an agentless deployment makes manual agent deployment unnecessary, allowing you to focus on your data instead of the agent that collects it. + +For more information, refer to [Agentless integrations](https://www.elastic.co/guide/en/serverless/current/security-agentless-integrations.html) and [Agentless integrations FAQ](https://www.elastic.co/guide/en/serverless/current/agentless-integration-troubleshooting.html). + +### Configure + +1. In the top search bar in Kibana, search for **Integrations**. +2. In the search bar, type **OpenAI ChatGPT Enterprise**. +3. Select the **OpenAI ChatGPT Enterprise** integration from the search results. +4. Select **Add OpenAI ChatGPT Enterprise** to add the integration. +5. Configure the integration: + + * To **Collect ChatGPT Enterprise compliance logs**, you'll need to: + + - Configure the **URL** (default `https://api.chatgpt.com`) and **Compliance API key**. + - Set the **Scope** to `workspace` or `organization`. + - Set the **Workspace / Organization ID** to the resource whose compliance logs are collected. + - Adjust the integration configuration parameters if required, including the Initial Interval, Interval, HTTP Client Timeout etc. to enable data collection. + +6. Select **Save and continue** to save the integration. + +### Validation + +#### Dashboard populated + +1. In the top search bar in Kibana, search for **Dashboards**. +2. In the search bar, type **OpenAI ChatGPT Enterprise**, and verify the dashboard information is populated. + +## Troubleshooting + +For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). + +## Scaling + +For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. + +## Reference + +### App Auth Log + +The `app_auth_log` data stream captures ChatGPT Enterprise `APP_AUTH_LOG` events (application authentication activity such as linking and unlinking apps and connectors). + +#### App Auth Log fields + +**Exported fields** + +| Field | Description | Type | +|---|---|---| +| @timestamp | Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events. | date | +| data_stream.dataset | The field can contain anything that makes sense to signify the source of the data. Examples include `nginx.access`, `prometheus`, `endpoint` etc. For data streams that otherwise fit, but that do not have dataset set we use the value "generic" for the dataset value. `event.dataset` should have the same value as `data_stream.dataset`. Beyond the Elasticsearch data stream naming criteria noted above, the `dataset` value has additional restrictions: \* Must not contain `-` \* No longer than 100 characters | constant_keyword | +| data_stream.namespace | A user defined namespace. Namespaces are useful to allow grouping of data. Many users already organize their indices this way, and the data stream naming scheme now provides this best practice as a default. Many users will populate this field with `default`. If no value is used, it falls back to `default`. Beyond the Elasticsearch index naming criteria noted above, `namespace` value has the additional restrictions: \* Must not contain `-` \* No longer than 100 characters | constant_keyword | +| data_stream.type | An overarching type for the data stream. Currently allowed values are "logs" and "metrics". We expect to also add "traces" and "synthetics" in the near future. | constant_keyword | +| event.dataset | Name of the dataset. If an event source publishes more than one type of log or events (e.g. access log, error log), the dataset is used to specify which one the event comes from. It's recommended but not required to start the dataset name with the module name, followed by a dot, then the dataset name. | constant_keyword | +| event.module | Name of the module this data is coming from. If your monitoring agent supports the concept of modules or plugins to process events of a given source (e.g. Apache logs), `event.module` should contain the name of this module. | constant_keyword | +| input.type | Type of filebeat input. | keyword | +| observer.product | The product name of the observer. | constant_keyword | +| observer.vendor | Vendor name of the observer. | constant_keyword | +| openai_chatgpt_enterprise.app_auth_log.actor.type | Type of actor that performed the action (e.g. ACCOUNT_USER, API_KEY). | keyword | +| openai_chatgpt_enterprise.app_auth_log.app_id | Identifier of the connected app / connector. | keyword | +| openai_chatgpt_enterprise.app_auth_log.link_id | Identifier of the app connection (link) that was linked/unlinked. | keyword | +| openai_chatgpt_enterprise.app_auth_log.principal.type | Principal that owns the event (e.g. CHATGPT_WORKSPACE). | keyword | +| openai_chatgpt_enterprise.app_auth_log.type | Top-level event category (e.g. APP_AUTH_LOG). | keyword | + + +### Example event + +#### App Auth Log + +An example event for `app_auth_log` looks as following: + +```json +{ + "@timestamp": "2026-07-09T10:10:00.000Z", + "agent": { + "ephemeral_id": "7eb4bbfe-bb77-43cd-8939-efadb1796d96", + "id": "d0be91b9-cbd1-4fdd-b4e1-aeea6dade4e7", + "name": "elastic-agent-98363", + "type": "filebeat", + "version": "8.19.0" + }, + "data_stream": { + "dataset": "openai_chatgpt_enterprise.app_auth_log", + "namespace": "39305", + "type": "logs" + }, + "ecs": { + "version": "9.3.0" + }, + "elastic_agent": { + "id": "d0be91b9-cbd1-4fdd-b4e1-aeea6dade4e7", + "snapshot": false, + "version": "8.19.0" + }, + "event": { + "action": "link", + "agent_id_status": "verified", + "category": [ + "configuration" + ], + "dataset": "openai_chatgpt_enterprise.app_auth_log", + "id": "7b3e4de1-2219-4d6c-9d6e-8f4d40c7c001", + "ingested": "2026-07-19T08:36:02Z", + "kind": "event", + "original": "{\"event_id\":\"7b3e4de1-2219-4d6c-9d6e-8f4d40c7c001\",\"type\":\"APP_AUTH_LOG\",\"timestamp\":\"2026-07-09T10:10:00.000000Z\",\"principal\":{\"id\":\"be545252-ad04-4cfa-9ca5-deca58416151\",\"type\":\"CHATGPT_WORKSPACE\"},\"actor\":{\"type\":\"ACCOUNT_USER\",\"user_id\":\"user-AbCdEf1234567890\",\"user_email\":\"user@example.org\"},\"app_id\":\"asdk_app_0123456789abcdef0123456789abcdef\",\"link_id\":\"link_00112233445566778899aabbccddeeff\",\"action\":\"link\"}", + "type": [ + "change" + ] + }, + "gen_ai": { + "system": "openai" + }, + "input": { + "type": "cel" + }, + "openai_chatgpt_enterprise": { + "app_auth_log": { + "actor": { + "type": "ACCOUNT_USER" + }, + "app_id": "asdk_app_0123456789abcdef0123456789abcdef", + "link_id": "link_00112233445566778899aabbccddeeff", + "principal": { + "type": "CHATGPT_WORKSPACE" + }, + "type": "APP_AUTH_LOG" + } + }, + "organization": { + "id": "be545252-ad04-4cfa-9ca5-deca58416151" + }, + "related": { + "user": [ + "user@example.org", + "user-AbCdEf1234567890" + ] + }, + "tags": [ + "preserve_original_event", + "forwarded", + "openai_chatgpt_enterprise-app_auth_log" + ], + "user": { + "domain": "example.org", + "email": "user@example.org", + "id": "user-AbCdEf1234567890" + } +} +``` + +### Inputs used + +These inputs can be used with this integration: +
+cel + +## Setup + +For more details about the CEL input settings, check the [Filebeat documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-cel.html). + +Before configuring the CEL input, make sure you have: +- Network connectivity to the target API endpoint +- Valid authentication credentials (API keys, tokens, or certificates as required) +- Appropriate permissions to read from the target data source + +### Collecting logs from CEL + +To configure the CEL input, you must specify the `request.url` value pointing to the API endpoint. The interval parameter controls how frequently requests are made and is the primary way to balance data freshness with API rate limits and costs. Authentication is often configured through the `request.headers` section using the appropriate method for the service. + +NOTE: To access the API service, make sure you have the necessary API credentials and that the Filebeat instance can reach the endpoint URL. Some services may require IP whitelisting or VPN access. + +To collect logs via API endpoint, configure the following parameters: + +- API Endpoint URL +- API credentials (tokens, keys, or username/password) +- Request interval (how often to fetch data) +
+ + +### API usage + +These APIs are used with this integration: + +* Application Authentication Log: + * List log files (endpoint: `/v1/compliance/{workspaces|organizations}/{resource_id}/logs`) + * Download log file (endpoint: `/v1/compliance/{workspaces|organizations}/{resource_id}/logs/{log_file_id}`) diff --git a/packages/openai_chatgpt_enterprise/img/app-auth-log-dashboard.png b/packages/openai_chatgpt_enterprise/img/app-auth-log-dashboard.png new file mode 100644 index 00000000000..5940b578539 Binary files /dev/null and b/packages/openai_chatgpt_enterprise/img/app-auth-log-dashboard.png differ diff --git a/packages/openai_chatgpt_enterprise/img/openai-logo.svg b/packages/openai_chatgpt_enterprise/img/openai-logo.svg new file mode 100644 index 00000000000..7d7d8701e6e --- /dev/null +++ b/packages/openai_chatgpt_enterprise/img/openai-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/openai_chatgpt_enterprise/kibana/dashboard/openai_chatgpt_enterprise-b7e6a4c2-1f83-4d59-9c0a-6e2b1d4f7a90.json b/packages/openai_chatgpt_enterprise/kibana/dashboard/openai_chatgpt_enterprise-b7e6a4c2-1f83-4d59-9c0a-6e2b1d4f7a90.json new file mode 100644 index 00000000000..75821553f03 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/kibana/dashboard/openai_chatgpt_enterprise-b7e6a4c2-1f83-4d59-9c0a-6e2b1d4f7a90.json @@ -0,0 +1,964 @@ +{ + "attributes": { + "controlGroupInput": { + "chainingSystem": "HIERARCHICAL", + "controlStyle": "oneLine", + "ignoreParentSettingsJSON": { + "ignoreFilters": false, + "ignoreQuery": false, + "ignoreTimerange": false, + "ignoreValidations": false + }, + "panelsJSON": { + "c1a2b3c4-0011-4a11-8b11-c00000000011": { + "explicitInput": { + "dataViewId": "logs-*", + "exclude": false, + "existsSelected": false, + "fieldName": "event.action", + "searchTechnique": "prefix", + "selectedOptions": [], + "sort": { + "by": "_count", + "direction": "desc" + }, + "title": "Action" + }, + "grow": true, + "order": 0, + "type": "optionsListControl", + "width": "medium" + }, + "c2a2b3c4-0012-4a12-8b12-c00000000012": { + "explicitInput": { + "dataViewId": "logs-*", + "exclude": false, + "existsSelected": false, + "fieldName": "user.email", + "searchTechnique": "prefix", + "selectedOptions": [], + "sort": { + "by": "_count", + "direction": "desc" + }, + "title": "User Email" + }, + "grow": true, + "order": 1, + "type": "optionsListControl", + "width": "medium" + }, + "c3a2b3c4-0013-4a13-8b13-c00000000013": { + "explicitInput": { + "dataViewId": "logs-*", + "exclude": false, + "existsSelected": false, + "fieldName": "openai_chatgpt_enterprise.app_auth_log.actor.type", + "searchTechnique": "prefix", + "selectedOptions": [], + "sort": { + "by": "_count", + "direction": "desc" + }, + "title": "Actor Type" + }, + "grow": true, + "order": 2, + "type": "optionsListControl", + "width": "medium" + } + }, + "showApplySelections": false + }, + "description": "Overview of application authentication logs in OpenAI ChatGPT Enterprise.", + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "openai_chatgpt_enterprise.app_auth_log" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "openai_chatgpt_enterprise.app_auth_log" + } + } + } + ], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "optionsJSON": { + "hidePanelTitles": false, + "syncColors": false, + "syncCursor": true, + "syncTooltips": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": { + "enhancements": {}, + "savedObjectId": "openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b" + }, + "gridData": { + "h": 15, + "i": "7a2b3c4d-0007-4a07-8b07-7000000000a7", + "w": 48, + "x": 0, + "y": 26 + }, + "panelIndex": "7a2b3c4d-0007-4a07-8b07-7000000000a7", + "panelRefName": "panel_7a2b3c4d-0007-4a07-8b07-7000000000a7", + "type": "search" + }, + { + "embeddableConfig": { + "attributes": { + "references": [ + { + "id": "logs-*", + "name": "indexpattern-datasource-layer-4a2b3c4d-0004-4a04-8b04-4000000000b4", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "681b801f-5529-4ee2-9f42-763f83ac4a29", + "type": "index-pattern" + } + ], + "state": { + "adHocDataViews": {}, + "datasourceStates": { + "formBased": { + "layers": { + "4a2b3c4d-0004-4a04-8b04-4000000000b4": { + "columnOrder": [ + "4a2b3c4d-0004-4a04-8b04-4000000000e4", + "4a2b3c4d-0004-4a04-8b04-4000000000c4", + "4a2b3c4d-0004-4a04-8b04-4000000000d4" + ], + "columns": { + "4a2b3c4d-0004-4a04-8b04-4000000000c4": { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": { + "dropPartials": false, + "includeEmptyRows": true, + "interval": "auto" + }, + "sourceField": "@timestamp" + }, + "4a2b3c4d-0004-4a04-8b04-4000000000d4": { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Count", + "operationType": "count", + "params": { + "emptyAsNull": false + }, + "sourceField": "___records___" + }, + "4a2b3c4d-0004-4a04-8b04-4000000000e4": { + "customLabel": true, + "dataType": "string", + "isBucketed": true, + "label": "Action", + "operationType": "terms", + "params": { + "missingBucket": false, + "orderBy": { + "columnId": "4a2b3c4d-0004-4a04-8b04-4000000000d4", + "type": "column" + }, + "orderDirection": "desc", + "otherBucket": true, + "parentFormat": { + "id": "terms" + }, + "size": 10 + }, + "sourceField": "event.action" + } + }, + "ignoreGlobalFilters": false, + "incompleteColumns": {}, + "sampling": 1 + } + } + }, + "indexpattern": { + "layers": {} + }, + "textBased": { + "layers": {} + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "681b801f-5529-4ee2-9f42-763f83ac4a29", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "openai_chatgpt_enterprise.app_auth_log" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "openai_chatgpt_enterprise.app_auth_log" + } + } + } + ], + "internalReferences": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "layers": [ + { + "accessors": [ + "4a2b3c4d-0004-4a04-8b04-4000000000d4" + ], + "layerId": "4a2b3c4d-0004-4a04-8b04-4000000000b4", + "layerType": "data", + "seriesType": "line", + "splitAccessor": "4a2b3c4d-0004-4a04-8b04-4000000000e4", + "xAccessor": "4a2b3c4d-0004-4a04-8b04-4000000000c4" + } + ], + "legend": { + "isVisible": true, + "position": "right", + "shouldTruncate": false + }, + "preferredSeriesType": "line", + "valueLabels": "hide" + } + }, + "title": "", + "type": "lens", + "visualizationType": "lnsXY" + }, + "enhancements": { + "dynamicActions": { + "events": [] + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "681b801f-5529-4ee2-9f42-763f83ac4a29", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "openai_chatgpt_enterprise.app_auth_log" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "openai_chatgpt_enterprise.app_auth_log" + } + } + } + ], + "query": { + "language": "kuery", + "query": "" + }, + "syncColors": false, + "syncCursor": true, + "syncTooltips": false, + "title": "Events over Time by Action" + }, + "gridData": { + "h": 11, + "i": "4a2b3c4d-0004-4a04-8b04-4000000000a4", + "w": 20, + "x": 28, + "y": 0 + }, + "panelIndex": "4a2b3c4d-0004-4a04-8b04-4000000000a4", + "type": "lens" + }, + { + "embeddableConfig": { + "attributes": { + "references": [ + { + "id": "logs-*", + "name": "indexpattern-datasource-layer-2a2b3c4d-0002-4a02-8b02-2000000000b2", + "type": "index-pattern" + } + ], + "state": { + "adHocDataViews": {}, + "datasourceStates": { + "formBased": { + "layers": { + "2a2b3c4d-0002-4a02-8b02-2000000000b2": { + "columnOrder": [ + "2a2b3c4d-0002-4a02-8b02-2000000000c2" + ], + "columns": { + "2a2b3c4d-0002-4a02-8b02-2000000000c2": { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Total Events", + "operationType": "count", + "params": { + "emptyAsNull": false + }, + "sourceField": "___records___" + } + }, + "ignoreGlobalFilters": false, + "incompleteColumns": {}, + "sampling": 1 + } + } + }, + "indexpattern": { + "layers": {} + }, + "textBased": { + "layers": {} + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "logs-*", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "openai_chatgpt_enterprise.app_auth_log" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "openai_chatgpt_enterprise.app_auth_log" + } + } + } + ], + "internalReferences": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "color": "#6092C0", + "layerId": "2a2b3c4d-0002-4a02-8b02-2000000000b2", + "layerType": "data", + "metricAccessor": "2a2b3c4d-0002-4a02-8b02-2000000000c2", + "showBar": false + } + }, + "title": "", + "type": "lens", + "visualizationType": "lnsMetric" + }, + "enhancements": {}, + "filters": [], + "hidePanelTitles": true, + "query": { + "language": "kuery", + "query": "" + }, + "title": "Total Events" + }, + "gridData": { + "h": 11, + "i": "2a2b3c4d-0002-4a02-8b02-2000000000a2", + "w": 8, + "x": 20, + "y": 0 + }, + "panelIndex": "2a2b3c4d-0002-4a02-8b02-2000000000a2", + "type": "lens" + }, + { + "embeddableConfig": { + "attributes": { + "references": [ + { + "id": "logs-*", + "name": "indexpattern-datasource-layer-3a2b3c4d-0003-4a03-8b03-3000000000b3", + "type": "index-pattern" + } + ], + "state": { + "adHocDataViews": {}, + "datasourceStates": { + "formBased": { + "layers": { + "3a2b3c4d-0003-4a03-8b03-3000000000b3": { + "columnOrder": [ + "3a2b3c4d-0003-4a03-8b03-3000000000c3" + ], + "columns": { + "3a2b3c4d-0003-4a03-8b03-3000000000c3": { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Unique Users", + "operationType": "unique_count", + "params": { + "emptyAsNull": false + }, + "sourceField": "user.id" + } + }, + "ignoreGlobalFilters": false, + "incompleteColumns": {}, + "sampling": 1 + } + } + }, + "indexpattern": { + "layers": {} + }, + "textBased": { + "layers": {} + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "logs-*", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "openai_chatgpt_enterprise.app_auth_log" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "openai_chatgpt_enterprise.app_auth_log" + } + } + } + ], + "internalReferences": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "color": "#6092C0", + "layerId": "3a2b3c4d-0003-4a03-8b03-3000000000b3", + "layerType": "data", + "metricAccessor": "3a2b3c4d-0003-4a03-8b03-3000000000c3", + "showBar": false + } + }, + "title": "", + "type": "lens", + "visualizationType": "lnsMetric" + }, + "enhancements": {}, + "filters": [], + "hidePanelTitles": true, + "query": { + "language": "kuery", + "query": "" + }, + "title": "Unique Users" + }, + "gridData": { + "h": 11, + "i": "3a2b3c4d-0003-4a03-8b03-3000000000a3", + "w": 8, + "x": 12, + "y": 0 + }, + "panelIndex": "3a2b3c4d-0003-4a03-8b03-3000000000a3", + "type": "lens" + }, + { + "embeddableConfig": { + "attributes": { + "references": [ + { + "id": "logs-*", + "name": "indexpattern-datasource-layer-5a2b3c4d-0005-4a05-8b05-5000000000b5", + "type": "index-pattern" + } + ], + "state": { + "adHocDataViews": {}, + "datasourceStates": { + "formBased": { + "layers": { + "5a2b3c4d-0005-4a05-8b05-5000000000b5": { + "columnOrder": [ + "5a2b3c4d-0005-4a05-8b05-5000000000c5", + "5a2b3c4d-0005-4a05-8b05-5000000000d5" + ], + "columns": { + "5a2b3c4d-0005-4a05-8b05-5000000000c5": { + "customLabel": true, + "dataType": "string", + "isBucketed": true, + "label": "Action", + "operationType": "terms", + "params": { + "missingBucket": false, + "orderBy": { + "columnId": "5a2b3c4d-0005-4a05-8b05-5000000000d5", + "type": "column" + }, + "orderDirection": "desc", + "otherBucket": false, + "parentFormat": { + "id": "terms" + }, + "size": 5 + }, + "sourceField": "event.action" + }, + "5a2b3c4d-0005-4a05-8b05-5000000000d5": { + "dataType": "number", + "isBucketed": false, + "label": "Count", + "operationType": "count", + "params": { + "emptyAsNull": false + }, + "sourceField": "___records___" + } + }, + "ignoreGlobalFilters": false, + "incompleteColumns": {}, + "sampling": 1 + } + } + }, + "indexpattern": { + "layers": {} + }, + "textBased": { + "layers": {} + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "logs-*", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "openai_chatgpt_enterprise.app_auth_log" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "openai_chatgpt_enterprise.app_auth_log" + } + } + } + ], + "internalReferences": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "layers": [ + { + "categoryDisplay": "default", + "layerId": "5a2b3c4d-0005-4a05-8b05-5000000000b5", + "layerType": "data", + "legendDisplay": "show", + "metrics": [ + "5a2b3c4d-0005-4a05-8b05-5000000000d5" + ], + "nestedLegend": false, + "numberDisplay": "percent", + "primaryGroups": [ + "5a2b3c4d-0005-4a05-8b05-5000000000c5" + ], + "truncateLegend": false + } + ], + "shape": "pie" + } + }, + "title": "", + "type": "lens", + "visualizationType": "lnsPie" + }, + "enhancements": {}, + "filters": [], + "query": { + "language": "kuery", + "query": "" + }, + "title": "Events by Action" + }, + "gridData": { + "h": 15, + "i": "5a2b3c4d-0005-4a05-8b05-5000000000a5", + "w": 16, + "x": 12, + "y": 11 + }, + "panelIndex": "5a2b3c4d-0005-4a05-8b05-5000000000a5", + "type": "lens" + }, + { + "embeddableConfig": { + "attributes": { + "references": [ + { + "id": "logs-*", + "name": "indexpattern-datasource-layer-6a2b3c4d-0006-4a06-8b06-6000000000b6", + "type": "index-pattern" + } + ], + "state": { + "adHocDataViews": {}, + "datasourceStates": { + "formBased": { + "currentIndexPatternId": "logs-*", + "layers": { + "6a2b3c4d-0006-4a06-8b06-6000000000b6": { + "columnOrder": [ + "6a2b3c4d-0006-4a06-8b06-6000000000c6", + "6a2b3c4d-0006-4a06-8b06-6000000000d6" + ], + "columns": { + "6a2b3c4d-0006-4a06-8b06-6000000000c6": { + "customLabel": true, + "dataType": "string", + "isBucketed": true, + "label": "Users", + "operationType": "terms", + "params": { + "missingBucket": false, + "orderBy": { + "columnId": "6a2b3c4d-0006-4a06-8b06-6000000000d6", + "type": "column" + }, + "orderDirection": "desc", + "otherBucket": false, + "parentFormat": { + "id": "terms" + }, + "size": 10 + }, + "sourceField": "user.email" + }, + "6a2b3c4d-0006-4a06-8b06-6000000000d6": { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Count", + "operationType": "count", + "params": { + "emptyAsNull": false + }, + "sourceField": "___records___" + } + }, + "ignoreGlobalFilters": false, + "incompleteColumns": {}, + "indexPatternId": "logs-*", + "sampling": 1 + } + } + }, + "indexpattern": { + "currentIndexPatternId": "logs-*", + "layers": {} + }, + "textBased": { + "indexPatternRefs": [ + { + "id": "logs-*", + "timeField": "@timestamp", + "title": "logs-*" + } + ], + "layers": {} + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "logs-*", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "openai_chatgpt_enterprise.app_auth_log" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "openai_chatgpt_enterprise.app_auth_log" + } + } + } + ], + "internalReferences": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "columns": [ + { + "columnId": "6a2b3c4d-0006-4a06-8b06-6000000000c6", + "isMetric": false, + "isTransposed": false + }, + { + "columnId": "6a2b3c4d-0006-4a06-8b06-6000000000d6", + "isMetric": true, + "isTransposed": false + } + ], + "layerId": "6a2b3c4d-0006-4a06-8b06-6000000000b6", + "layerType": "data" + } + }, + "title": "", + "type": "lens", + "visualizationType": "lnsDatatable" + }, + "enhancements": { + "dynamicActions": { + "events": [] + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "logs-*", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "openai_chatgpt_enterprise.app_auth_log" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "openai_chatgpt_enterprise.app_auth_log" + } + } + } + ], + "query": { + "language": "kuery", + "query": "" + }, + "syncColors": false, + "syncCursor": true, + "syncTooltips": false, + "title": "Top Users by Event Count" + }, + "gridData": { + "h": 15, + "i": "6a2b3c4d-0006-4a06-8b06-6000000000a6", + "w": 20, + "x": 28, + "y": 11 + }, + "panelIndex": "6a2b3c4d-0006-4a06-8b06-6000000000a6", + "type": "lens" + }, + { + "embeddableConfig": { + "enhancements": {}, + "savedVis": { + "data": { + "aggs": [], + "searchSource": { + "filter": [], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "description": "", + "params": { + "fontSize": 12, + "markdown": "#### OpenAI ChatGPT Enterprise\n\n**Application Authentication Logs**\n\nThis dashboard gives visibility into the application authentication activity of OpenAI ChatGPT Enterprise, showing how apps and connectors are linked and unlinked across the workspace or organization. It highlights total events and unique users, trends of link and unlink actions over time, and the share of each action. It surfaces the users most active in connecting apps, and can be filtered by action, user, and actor type using the controls above. Together, these panels support security oversight, auditing, and usage analysis.\n\n**[Integration Page](/app/integrations/detail/openai_chatgpt_enterprise/overview)**\n", + "openLinksInNewTab": false + }, + "title": "", + "type": "markdown", + "uiState": {} + }, + "title": "" + }, + "gridData": { + "h": 26, + "i": "1a2b3c4d-0001-4a01-8b01-1000000000a1", + "w": 12, + "x": 0, + "y": 0 + }, + "panelIndex": "1a2b3c4d-0001-4a01-8b01-1000000000a1", + "type": "visualization" + } + ], + "timeRestore": false, + "title": "[Logs OpenAI ChatGPT Enterprise] Application Authentication Logs", + "version": 3 + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2026-07-19T08:46:37.788Z", + "id": "openai_chatgpt_enterprise-b7e6a4c2-1f83-4d59-9c0a-6e2b1d4f7a90", + "references": [ + { + "id": "logs-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + }, + { + "id": "openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b", + "name": "7a2b3c4d-0007-4a07-8b07-7000000000a7:panel_7a2b3c4d-0007-4a07-8b07-7000000000a7", + "type": "search" + }, + { + "id": "openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b", + "name": "7a2b3c4d-0007-4a07-8b07-7000000000a7:panel_7a2b3c4d-0007-4a07-8b07-7000000000a7", + "type": "search" + }, + { + "id": "openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b", + "name": "7a2b3c4d-0007-4a07-8b07-7000000000a7:panel_7a2b3c4d-0007-4a07-8b07-7000000000a7", + "type": "search" + }, + { + "id": "openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b", + "name": "7a2b3c4d-0007-4a07-8b07-7000000000a7:panel_7a2b3c4d-0007-4a07-8b07-7000000000a7", + "type": "search" + }, + { + "id": "logs-*", + "name": "4a2b3c4d-0004-4a04-8b04-4000000000a4:indexpattern-datasource-layer-4a2b3c4d-0004-4a04-8b04-4000000000b4", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "4a2b3c4d-0004-4a04-8b04-4000000000a4:681b801f-5529-4ee2-9f42-763f83ac4a29", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "2a2b3c4d-0002-4a02-8b02-2000000000a2:indexpattern-datasource-layer-2a2b3c4d-0002-4a02-8b02-2000000000b2", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "3a2b3c4d-0003-4a03-8b03-3000000000a3:indexpattern-datasource-layer-3a2b3c4d-0003-4a03-8b03-3000000000b3", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "5a2b3c4d-0005-4a05-8b05-5000000000a5:indexpattern-datasource-layer-5a2b3c4d-0005-4a05-8b05-5000000000b5", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "6a2b3c4d-0006-4a06-8b06-6000000000a6:indexpattern-datasource-layer-6a2b3c4d-0006-4a06-8b06-6000000000b6", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "controlGroup_c1a2b3c4-0011-4a11-8b11-c00000000011:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "controlGroup_c2a2b3c4-0012-4a12-8b12-c00000000012:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "controlGroup_c3a2b3c4-0013-4a13-8b13-c00000000013:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + } + ], + "type": "dashboard", + "typeMigrationVersion": "10.3.0" +} \ No newline at end of file diff --git a/packages/openai_chatgpt_enterprise/kibana/search/openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b.json b/packages/openai_chatgpt_enterprise/kibana/search/openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b.json new file mode 100644 index 00000000000..efd234f4987 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/kibana/search/openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b.json @@ -0,0 +1,45 @@ +{ + "attributes": { + "columns": [ + "event.action", + "user.email", + "openai_chatgpt_enterprise.app_auth_log.actor.type", + "openai_chatgpt_enterprise.app_auth_log.app_id", + "openai_chatgpt_enterprise.app_auth_log.link_id" + ], + "description": "", + "grid": {}, + "hideChart": false, + "isTextBasedQuery": false, + "kibanaSavedObjectMeta": { + "searchSourceJSON": { + "filter": [], + "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.index", + "query": { + "language": "kuery", + "query": "data_stream.dataset : \"openai_chatgpt_enterprise.app_auth_log\"" + } + } + }, + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "timeRestore": false, + "title": "Latest App Authentication Events" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2026-07-19T08:45:43.620Z", + "id": "openai_chatgpt_enterprise-d2f4a6b8-3c15-4e27-8a9b-0c1d2e3f4a5b", + "references": [ + { + "id": "logs-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "typeMigrationVersion": "10.5.0" +} \ No newline at end of file diff --git a/packages/openai_chatgpt_enterprise/manifest.yml b/packages/openai_chatgpt_enterprise/manifest.yml new file mode 100644 index 00000000000..e225ddcb408 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/manifest.yml @@ -0,0 +1,97 @@ +format_version: 3.3.2 +name: openai_chatgpt_enterprise +title: OpenAI ChatGPT Enterprise +version: 0.1.0 +description: Collect ChatGPT Enterprise compliance logs from the OpenAI Compliance Logs Platform with Elastic Agent. +type: integration +categories: + - security + - observability +conditions: + kibana: + version: '^8.19.0 || ^9.1.0' + elastic: + subscription: 'basic' + +screenshots: + - src: /img/app-auth-log-dashboard.png + title: OpenAI ChatGPT Enterprise App Authentication logs + size: 1024x768 + type: image/png +icons: + - src: /img/openai-logo.svg + title: OpenAI logo + size: 512x138 + type: image/svg+xml +policy_templates: + - name: openai_chatgpt_enterprise + title: OpenAI ChatGPT Enterprise compliance logs + description: Collect ChatGPT Enterprise compliance logs from the OpenAI Compliance Logs Platform. + deployment_modes: + default: + enabled: true + agentless: + enabled: true + release: beta + organization: security + division: engineering + team: security-service-integrations + inputs: + - type: cel + title: Collect ChatGPT Enterprise compliance logs + description: Collect ChatGPT Enterprise compliance logs from the OpenAI Compliance Logs Platform. + vars: + - name: url + type: url + title: URL + description: Base URL of the OpenAI Compliance Logs Platform API. + default: https://api.chatgpt.com + required: true + show_user: true + - name: admin_token + type: password + title: Compliance API key + description: Compliance API key authorized for enterprise logs. See [Compliance API for enterprise customers](https://help.openai.com/en/articles/9261474-compliance-api-for-enterprise-customers) for more information. + multi: false + required: true + show_user: true + secret: true + - name: proxy_url + type: text + title: Proxy URL + multi: false + required: false + show_user: false + description: URL to proxy connections in the form of http[s]://:@:. Please ensure your username and password are in URL encoded format. + - name: ssl + type: yaml + title: SSL Configuration + description: SSL configuration options. See [documentation](https://www.elastic.co/guide/en/beats/filebeat/current/configuration-ssl.html#ssl-common-config) for details. + multi: false + required: false + show_user: false + default: | + #certificate_authorities: + # - | + # -----BEGIN CERTIFICATE----- + # MIIDCjCCAfKgAwIBAgITJ706Mu2wJlKckpIvkWxEHvEyijANBgkqhkiG9w0BAQsF + # ADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTkwNzIyMTkyOTA0WhgPMjExOTA2 + # MjgxOTI5MDRaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB + # BQADggEPADCCAQoCggEBANce58Y/JykI58iyOXpxGfw0/gMvF0hUQAcUrSMxEO6n + # fZRA49b4OV4SwWmA3395uL2eB2NB8y8qdQ9muXUdPBWE4l9rMZ6gmfu90N5B5uEl + # 94NcfBfYOKi1fJQ9i7WKhTjlRkMCgBkWPkUokvBZFRt8RtF7zI77BSEorHGQCk9t + # /D7BS0GJyfVEhftbWcFEAG3VRcoMhF7kUzYwp+qESoriFRYLeDWv68ZOvG7eoWnP + # PsvZStEVEimjvK5NSESEQa9xWyJOmlOKXhkdymtcUd/nXnx6UTCFgnkgzSdTWV41 + # CI6B6aJ9svCTI2QuoIq2HxX/ix7OvW1huVmcyHVxyUECAwEAAaNTMFEwHQYDVR0O + # BBYEFPwN1OceFGm9v6ux8G+DZ3TUDYxqMB8GA1UdIwQYMBaAFPwN1OceFGm9v6ux + # 8G+DZ3TUDYxqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG5D + # 874A4YI7YUwOVsVAdbWtgp1d0zKcPRR+r2OdSbTAV5/gcS3jgBJ3i1BN34JuDVFw + # 3DeJSYT3nxy2Y56lLnxDeF8CUTUtVQx3CuGkRg1ouGAHpO/6OqOhwLLorEmxi7tA + # H2O8mtT0poX5AnOAhzVy7QW0D/k4WaoLyckM5hUa6RtvgvLxOwA0U+VGurCDoctu + # 8F4QOgTAWyh8EZIwaKCliFRSynDpv3JTUwtfZkxo6K6nce1RhCWFAsMvDZL8Dgc0 + # yvgJ38BRsFOtkRuAGSf6ZUwTO8JJRRIFnpUzXflAnGivK9M13D5GEQMmIl6U9Pvk + # sxSmbIUfc2SGJGCJD4I= + # -----END CERTIFICATE----- +owner: + github: elastic/security-service-integrations + type: elastic diff --git a/packages/openai_chatgpt_enterprise/validation.yml b/packages/openai_chatgpt_enterprise/validation.yml new file mode 100644 index 00000000000..1189aa63c89 --- /dev/null +++ b/packages/openai_chatgpt_enterprise/validation.yml @@ -0,0 +1,3 @@ +errors: + exclude_checks: + - SVR00004 # References in dashboards.