diff --git a/packages/zoom/_dev/build/docs/README.md b/packages/zoom/_dev/build/docs/README.md index 77f046bd610..bac50c551c2 100644 --- a/packages/zoom/_dev/build/docs/README.md +++ b/packages/zoom/_dev/build/docs/README.md @@ -1,18 +1,123 @@ -# Zoom Webhook Integration +# Zoom Integration for Elastic -This integration creates an HTTP listener that accepts incoming webhook -callbacks from Zoom. +## Overview -To configure Zoom to send webhooks to this integration, please follow the -[Zoom Documentation](https://developers.zoom.us/docs/api/rest/webhook-only-app). +[Zoom](https://www.zoom.com/) is a unified communications platform that provides meetings, webinars, phone, team chat, and Zoom Rooms. The Zoom integration for Elastic enables you to collect Zoom event and audit data so you can monitor user activity, investigate security incidents, and analyze platform usage in Elastic. -The agent running this integration must be able to accept requests from the -Internet in order for Zoom to be able connect. Zoom requires that the webhook -accept requests over HTTPS. So you must either configure the integration with -a valid TLS certificate or use a reverse proxy in front of the integration. +This integration collects data using two complementary methods: -## Compatibility +- **Webhook**: a real-time HTTP listener that receives event notifications pushed by Zoom (meeting, webinar, recording, user, account, phone, team chat, and Zoom Room events). +- **REST API**: a periodic poll of the Zoom REST API to collect the sign in / sign out **activity** report for an account. -This integration is compatible with the Zoom Platform API as of September 2020. +### Compatibility + +- The **activity** data stream uses the Zoom REST API [`GET /report/activities`](https://developers.zoom.us/docs/api/meetings/#tag/reports/get/report/activities) endpoint and requires a Zoom Pro (or higher) plan. + +### How it works + +The **webhook** data stream creates an HTTP listener that accepts incoming webhook callbacks from Zoom. The Elastic Agent running this integration must be reachable from the internet so that Zoom can connect to it. Zoom requires that webhooks are delivered over HTTPS, so you must either configure the integration with a valid TLS certificate or place a reverse proxy that terminates TLS in front of the integration. Incoming events are then routed to the appropriate ingest pipeline based on the Zoom event type. + +The **activity** data stream periodically queries the Zoom REST API using Server-to-Server OAuth. On each interval it requests sign in / sign out activity within a date window (a maximum of one month per request, within the last six months of available history), paginates through the results, and advances a cursor so that subsequent runs collect only new activity. + +## What data does this integration collect? + +The Zoom integration collects the following data: + +- `webhook`: real-time Zoom event notifications, including account, team chat (channel and message), meeting, phone, recording, user, webinar, and Zoom Room events. +- `activity`: account-wide sign in and sign out activity logs from the Zoom REST API reports endpoint. + +### Supported use cases + +Integrating Zoom with Elastic SIEM provides centralized visibility into collaboration and authentication activity. Webhook events support real-time monitoring and detection across meetings, recordings, users, and administrative changes, while the sign in / sign out activity report provides an account-wide authentication audit trail for investigating user access, detecting anomalous logins, and meeting compliance requirements. + +## What do I need to use this integration? + +### From Zoom + +#### Collecting data via Webhook + +1. Create a Webhook-only app in the [Zoom App Marketplace](https://marketplace.zoom.us/) by following the [Zoom webhook documentation](https://developers.zoom.us/docs/api/webhooks/). +2. Add the event types you want to receive and set the event notification endpoint URL to the public HTTPS address where this integration is reachable. +3. Note the **Secret Token** generated by Zoom. It is used for CRC endpoint validation and to verify the authenticity of incoming events. + +#### Collecting data from the Zoom REST API + +1. Create a **Server-to-Server OAuth** app in the [Zoom App Marketplace](https://marketplace.zoom.us/) by following the [Server-to-Server OAuth documentation](https://developers.zoom.us/docs/internal-apps/s2s-oauth/). +2. Record the app's **Account ID**, **Client ID**, and **Client Secret**. +3. Add the `report:read:admin` scope (or the granular `report:read:user_activities:admin` scope) to the app and activate it. A Zoom Pro or higher plan is required. + +## How do I deploy this integration? + +### 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. + +Elastic Agent is required to receive the Zoom webhook callbacks or to poll the Zoom REST API, and to ship the data to Elastic, where the events are then processed via the integration's ingest pipelines. + +### Onboard / configure + +1. In the top search bar in Kibana, search for **Integrations**. +2. In the search bar, type **Zoom**. +3. Select the **Zoom** integration from the search results. +4. Select **Add Zoom** to add the integration. +5. Enable and configure only the collection methods which you will use. + + * To **Collect Zoom logs via Webhook**, you'll need to: + + - Configure the **Listen Address**, **Listen Port**, and **Webhook path** where the integration accepts requests. + - Optionally enable **CRC validation** and provide the **Zoom Secret Token**, and/or configure a custom header to verify incoming requests. + - Provide a valid **TLS** certificate (or front the integration with a TLS-terminating reverse proxy), since Zoom requires HTTPS. + + * To **Collect Zoom logs via REST API**, you'll need to: + + - Configure the **Account ID**, **Client ID**, and **Client Secret** of your Server-to-Server OAuth app. + - Adjust the integration configuration parameters if required, including the **Interval** and **Initial Interval** (lookback), to enable data collection. + +6. Select **Save and continue** to save the integration. + +### Validation + +#### Dashboards populated + +1. In the top search bar in Kibana, search for **Dashboards**. +2. In the search bar, type **Zoom**. +3. Select a dashboard for the dataset you are collecting, 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 + +### webhook + +This is the `webhook` data stream. It collects real-time event notifications pushed by Zoom over an HTTP endpoint. + +{{event "webhook"}} {{fields "webhook"}} + +### activity + +This is the `activity` data stream. It collects sign in / sign out activity logs from the Zoom REST API. + +{{event "activity"}} + +{{fields "activity"}} + +### Inputs used + +These inputs are used in this integration: + +- [http_endpoint](https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-http_endpoint) +- [cel](https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel) + +### API usage + +This integration uses the following APIs: + +- `activity`: [Get sign in / sign out activity report](https://developers.zoom.us/docs/api/meetings/#tag/reports/get/report/activities). diff --git a/packages/zoom/_dev/deploy/docker/docker-compose.yml b/packages/zoom/_dev/deploy/docker/docker-compose.yml index 3fb7fade588..0e83d1d8cde 100644 --- a/packages/zoom/_dev/deploy/docker/docker-compose.yml +++ b/packages/zoom/_dev/deploy/docker/docker-compose.yml @@ -21,3 +21,16 @@ services: - STREAM_ADDR=https://elastic-agent:7443/zoom - STREAM_WEBHOOK_HEADER=Authorization=abc123 command: log --start-signal=SIGHUP --delay=5s /sample_logs/account-ndjson.log + zoom-activity: + image: docker.elastic.co/observability/stream:v0.20.0 + hostname: zoom-activity + ports: + - 8090 + volumes: + - ./files:/files:ro + environment: + PORT: '8090' + command: + - http-server + - --addr=:8090 + - --config=/files/config-activity.yml diff --git a/packages/zoom/_dev/deploy/docker/files/config-activity.yml b/packages/zoom/_dev/deploy/docker/files/config-activity.yml new file mode 100644 index 00000000000..d493be2b3d3 --- /dev/null +++ b/packages/zoom/_dev/deploy/docker/files/config-activity.yml @@ -0,0 +1,140 @@ +rules: + # Zoom S2S OAuth token endpoint (account_credentials grant). + - path: /oauth/token + methods: [POST] + query_params: + grant_type: account_credentials + account_id: test-account-id + request_headers: + Authorization: + - "Basic dGVzdC1jbGllbnQtaWQ6dGVzdC1jbGllbnQtc2VjcmV0" + Content-Type: + - "application/x-www-form-urlencoded" + responses: + - status_code: 200 + headers: + Content-Type: + - "application/json" + body: | + {"access_token":"test-access-token","token_type":"Bearer","expires_in":3600,"scope":"report:read:admin"} + # Pagination page 2 — token from page 1. + - path: /v2/report/activities + methods: [GET] + query_params: + from: "{from:.*}" + to: "{to:.*}" + page_size: "{page_size:.*}" + next_page_token: mock-page-2-token + responses: + - status_code: 200 + headers: + Content-Type: + - "application/json" + body: |- + { + "activity_logs": [ + { + "client_type": "Browser", + "email": "bob@example.com", + "ip_address": "198.51.100.42", + "time": "{{ .request.vars.from }}T14:30:00Z", + "type": "Sign in", + "version": "6.0.0.1000" + } + ], + "from": "{{ .request.vars.from }}", + "to": "{{ .request.vars.to }}", + "page_size": 300, + "next_page_token": "mock-page-3-token" + } + # Pagination page 3 — terminal page for round 1. + - path: /v2/report/activities + methods: [GET] + query_params: + from: "{from:.*}" + to: "{to:.*}" + page_size: "{page_size:.*}" + next_page_token: mock-page-3-token + responses: + - status_code: 200 + headers: + Content-Type: + - "application/json" + body: |- + { + "activity_logs": [ + { + "client_type": "Mac", + "email": "carol@example.com", + "ip_address": "203.0.113.55", + "time": "{{ .request.vars.to }}T08:45:00Z", + "type": "Sign out", + "version": "6.0.0.1000" + } + ], + "from": "{{ .request.vars.from }}", + "to": "{{ .request.vars.to }}", + "page_size": 300 + } + # Initial page (round 1) and cursor-resumed page (round 2). + - path: /v2/report/activities + methods: [GET] + query_params: + from: "{from:.*}" + to: "{to:.*}" + page_size: "{page_size:.*}" + responses: + - status_code: 200 + headers: + Content-Type: + - "application/json" + body: |- + {{ if eq .req_num 1 }} + { + "activity_logs": [ + { + "client_type": "Browser", + "email": "alice@example.com", + "ip_address": "192.0.2.10", + "time": "{{ .request.vars.from }}T10:00:00Z", + "type": "Sign in", + "version": "6.0.0.1000" + }, + { + "client_type": "Windows", + "email": "dave@example.com", + "ip_address": "192.0.2.20", + "time": "{{ .request.vars.from }}T11:15:00Z", + "type": "Sign out", + "version": "6.0.0.1000" + } + ], + "from": "{{ .request.vars.from }}", + "to": "{{ .request.vars.to }}", + "page_size": 300, + "next_page_token": "mock-page-2-token" + } + {{ else if eq .req_num 2 }} + { + "activity_logs": [ + { + "client_type": "Browser", + "email": "eve@example.com", + "ip_address": "198.51.100.77", + "time": "{{ .request.vars.from }}T12:00:00Z", + "type": "Sign in", + "version": "6.0.0.1000" + } + ], + "from": "{{ .request.vars.from }}", + "to": "{{ .request.vars.to }}", + "page_size": 300 + } + {{ else }} + { + "activity_logs": [], + "from": "{{ .request.vars.from }}", + "to": "{{ .request.vars.to }}", + "page_size": 300 + } + {{ end }} diff --git a/packages/zoom/changelog.yml b/packages/zoom/changelog.yml index 783498ecf0b..004817c86f4 100644 --- a/packages/zoom/changelog.yml +++ b/packages/zoom/changelog.yml @@ -1,4 +1,9 @@ # newer versions go on top +- version: "1.24.0" + changes: + - description: Add support for the `activity` data stream. + type: enhancement + link: https://github.com/elastic/integrations/pull/19481 - version: "1.23.0" changes: - description: Map `user.email` and `source.ip` from available fields. diff --git a/packages/zoom/data_stream/activity/_dev/test/pipeline/test-common-config.yml b/packages/zoom/data_stream/activity/_dev/test/pipeline/test-common-config.yml new file mode 100644 index 00000000000..4da22641654 --- /dev/null +++ b/packages/zoom/data_stream/activity/_dev/test/pipeline/test-common-config.yml @@ -0,0 +1,3 @@ +fields: + tags: + - preserve_original_event diff --git a/packages/zoom/data_stream/activity/_dev/test/pipeline/test-zoom-activity.log b/packages/zoom/data_stream/activity/_dev/test/pipeline/test-zoom-activity.log new file mode 100644 index 00000000000..cc03459a1f5 --- /dev/null +++ b/packages/zoom/data_stream/activity/_dev/test/pipeline/test-zoom-activity.log @@ -0,0 +1,5 @@ +{"client_type":"Browser","email":"alice@example.com","ip_address":"192.0.2.10","time":"2024-06-07T10:00:00Z","type":"Sign in","version":"6.0.0.1000"} +{"client_type":"Windows","email":"dave@example.com","ip_address":"192.0.2.20","time":"2024-06-07T11:15:00Z","type":"Sign out","version":"6.0.0.1000"} +{"client_type":"Mac","email":"carol@example.com","ip_address":"203.0.113.55","time":"2024-06-07T15:45:00Z","type":"Sign out","version":"6.0.0.1000"} +{"email":"bob@example.com","time":"2024-06-07T14:30:00Z","type":"Sign in"} +{"client_type":"Browser","email":"frank@example.com","ip_address":"10.0.0.50","time":"2024-06-08T09:00:00Z","type":"Sign in","version":"6.0.0.1000"} diff --git a/packages/zoom/data_stream/activity/_dev/test/pipeline/test-zoom-activity.log-expected.json b/packages/zoom/data_stream/activity/_dev/test/pipeline/test-zoom-activity.log-expected.json new file mode 100644 index 00000000000..607d66fa303 --- /dev/null +++ b/packages/zoom/data_stream/activity/_dev/test/pipeline/test-zoom-activity.log-expected.json @@ -0,0 +1,270 @@ +{ + "expected": [ + { + "@timestamp": "2024-06-07T10:00:00.000Z", + "ecs": { + "version": "8.11.0" + }, + "event": { + "action": "sign-in", + "category": [ + "authentication", + "session" + ], + "kind": "event", + "original": "{\"client_type\":\"Browser\",\"email\":\"alice@example.com\",\"ip_address\":\"192.0.2.10\",\"time\":\"2024-06-07T10:00:00Z\",\"type\":\"Sign in\",\"version\":\"6.0.0.1000\"}", + "outcome": "success", + "type": [ + "start" + ] + }, + "related": { + "ip": [ + "192.0.2.10" + ], + "user": [ + "alice@example.com" + ] + }, + "source": { + "as": { + "number": 64500, + "organization": { + "name": "Documentation ASN" + } + }, + "geo": { + "city_name": "Las Vegas", + "continent_name": "North America", + "country_iso_code": "US", + "country_name": "United States", + "location": { + "lat": 36.17497, + "lon": -115.13722 + }, + "region_iso_code": "US-NV", + "region_name": "Nevada" + }, + "ip": "192.0.2.10" + }, + "tags": [ + "preserve_original_event" + ], + "user": { + "email": "alice@example.com", + "name": "alice@example.com" + }, + "zoom": { + "activity": { + "client_type": "Browser", + "client_version": "6.0.0.1000", + "type": "Sign in" + } + } + }, + { + "@timestamp": "2024-06-07T11:15:00.000Z", + "ecs": { + "version": "8.11.0" + }, + "event": { + "action": "sign-out", + "category": [ + "authentication", + "session" + ], + "kind": "event", + "original": "{\"client_type\":\"Windows\",\"email\":\"dave@example.com\",\"ip_address\":\"192.0.2.20\",\"time\":\"2024-06-07T11:15:00Z\",\"type\":\"Sign out\",\"version\":\"6.0.0.1000\"}", + "outcome": "success", + "type": [ + "end" + ] + }, + "related": { + "ip": [ + "192.0.2.20" + ], + "user": [ + "dave@example.com" + ] + }, + "source": { + "as": { + "number": 64500, + "organization": { + "name": "Documentation ASN" + } + }, + "geo": { + "city_name": "Las Vegas", + "continent_name": "North America", + "country_iso_code": "US", + "country_name": "United States", + "location": { + "lat": 36.17497, + "lon": -115.13722 + }, + "region_iso_code": "US-NV", + "region_name": "Nevada" + }, + "ip": "192.0.2.20" + }, + "tags": [ + "preserve_original_event" + ], + "user": { + "email": "dave@example.com", + "name": "dave@example.com" + }, + "zoom": { + "activity": { + "client_type": "Windows", + "client_version": "6.0.0.1000", + "type": "Sign out" + } + } + }, + { + "@timestamp": "2024-06-07T15:45:00.000Z", + "ecs": { + "version": "8.11.0" + }, + "event": { + "action": "sign-out", + "category": [ + "authentication", + "session" + ], + "kind": "event", + "original": "{\"client_type\":\"Mac\",\"email\":\"carol@example.com\",\"ip_address\":\"203.0.113.55\",\"time\":\"2024-06-07T15:45:00Z\",\"type\":\"Sign out\",\"version\":\"6.0.0.1000\"}", + "outcome": "success", + "type": [ + "end" + ] + }, + "related": { + "ip": [ + "203.0.113.55" + ], + "user": [ + "carol@example.com" + ] + }, + "source": { + "as": { + "number": 64502, + "organization": { + "name": "Documentation ASN" + } + }, + "geo": { + "city_name": "Madrid", + "continent_name": "Europe", + "country_iso_code": "ES", + "country_name": "Spain", + "location": { + "lat": 40.41639, + "lon": -3.7025 + }, + "region_iso_code": "ES-M", + "region_name": "Madrid" + }, + "ip": "203.0.113.55" + }, + "tags": [ + "preserve_original_event" + ], + "user": { + "email": "carol@example.com", + "name": "carol@example.com" + }, + "zoom": { + "activity": { + "client_type": "Mac", + "client_version": "6.0.0.1000", + "type": "Sign out" + } + } + }, + { + "@timestamp": "2024-06-07T14:30:00.000Z", + "ecs": { + "version": "8.11.0" + }, + "event": { + "action": "sign-in", + "category": [ + "authentication", + "session" + ], + "kind": "event", + "original": "{\"email\":\"bob@example.com\",\"time\":\"2024-06-07T14:30:00Z\",\"type\":\"Sign in\"}", + "outcome": "success", + "type": [ + "start" + ] + }, + "related": { + "user": [ + "bob@example.com" + ] + }, + "tags": [ + "preserve_original_event" + ], + "user": { + "email": "bob@example.com", + "name": "bob@example.com" + }, + "zoom": { + "activity": { + "type": "Sign in" + } + } + }, + { + "@timestamp": "2024-06-08T09:00:00.000Z", + "ecs": { + "version": "8.11.0" + }, + "event": { + "action": "sign-in", + "category": [ + "authentication", + "session" + ], + "kind": "event", + "original": "{\"client_type\":\"Browser\",\"email\":\"frank@example.com\",\"ip_address\":\"10.0.0.50\",\"time\":\"2024-06-08T09:00:00Z\",\"type\":\"Sign in\",\"version\":\"6.0.0.1000\"}", + "outcome": "success", + "type": [ + "start" + ] + }, + "related": { + "ip": [ + "10.0.0.50" + ], + "user": [ + "frank@example.com" + ] + }, + "source": { + "ip": "10.0.0.50" + }, + "tags": [ + "preserve_original_event" + ], + "user": { + "email": "frank@example.com", + "name": "frank@example.com" + }, + "zoom": { + "activity": { + "client_type": "Browser", + "client_version": "6.0.0.1000", + "type": "Sign in" + } + } + } + ] +} diff --git a/packages/zoom/data_stream/activity/_dev/test/system/test-default-config.yml b/packages/zoom/data_stream/activity/_dev/test/system/test-default-config.yml new file mode 100644 index 00000000000..19b441fb495 --- /dev/null +++ b/packages/zoom/data_stream/activity/_dev/test/system/test-default-config.yml @@ -0,0 +1,19 @@ +wait_for_data_timeout: 1m +input: cel +service: zoom-activity +vars: + url: http://{{Hostname}}:{{Port}} + token_url: http://{{Hostname}}:{{Port}} + account_id: test-account-id + client_id: test-client-id + client_secret: test-client-secret +data_stream: + vars: + interval: 2s + initial_interval: 24h + page_size: 300 + tags: + - forwarded + - zoom-activity +assert: + hit_count: 5 diff --git a/packages/zoom/data_stream/activity/agent/stream/cel.yml.hbs b/packages/zoom/data_stream/activity/agent/stream/cel.yml.hbs new file mode 100644 index 00000000000..b0c262e7152 --- /dev/null +++ b/packages/zoom/data_stream/activity/agent/stream/cel.yml.hbs @@ -0,0 +1,183 @@ +config_version: 2 +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}} +auth.oauth2: + client.id: {{client_id}} + client.secret: {{client_secret}} + token_url: {{token_url}}/oauth/token + endpoint_params: + grant_type: account_credentials + account_id: {{account_id}} +state: + page_size: {{page_size}} + initial_interval: {{initial_interval}} + want_more: false +redact: + fields: ~ +program: |- + ( + // Workflow overview: Zoom GET /v2/report/activities returns sign-in / sign-out + // records for a [from, to] date window (date granularity; max 1-month window; + // ~6 months of history). Each poll drains one window page-by-page via + // next_page_token, then advances cursor.poll_start so the next interval resumes + // after the data already collected. Only `cursor` persists across runs; `next` + // holds the in-progress page token within a single poll. Dates are + // "YYYY-MM-DD" strings, which sort chronologically and so can be compared and + // min/max'd directly. + // + // Step 1 - pick the window start. If a page token is in flight we are mid-way + // through a window: keep state as-is so the token drives the next page and + // from_date stays frozen. Otherwise begin the next window (or the first one): + // drop any stale token and seed from_date from the saved cursor, or from + // `now - initial_interval` on the first run. Keying on the token (not want_more) + // lets a finished window advance to the next window while want_more is still + // true for backfill. + (state.?next.page_token.orValue("") != "") ? + state + : + state.drop(["next"]).with( + { + "from_date": state.?cursor.poll_start.orValue( + (now - duration(state.initial_interval)).format("2006-01-02") + ), + } + ) + ).as(state, + // Step 2 - clamp the window. + // from_date: requested start, floored at 6 months ago (Zoom retention). + // to_date: from_date + 1 month, capped at today (1-month max, no future). + [ + state.from_date, + (now - duration("4320h")).format("2006-01-02"), + ].max().as(from_date, + [ + (timestamp(from_date + "T00:00:00Z") + duration("720h")).format("2006-01-02"), + now.format("2006-01-02"), + ].min().as(to_date, + // Step 3 - if the window start already passed the end, everything up to now + // is collected: emit a dropped placeholder (no API call) and hold the cursor. + (from_date > to_date) ? + { + "events": dyn([{"retry": true}]), + "want_more": false, + "cursor": {"poll_start": from_date}, + } + : + // Step 4 - fetch one page of the window. next_page_token is sent only when + // resuming pagination within the same poll. + state.with( + request( + "GET", + state.url.trim_right("/") + "/v2/report/activities?" + { + "from": [from_date], + "to": [to_date], + "page_size": [string(int(state.page_size))], + ?"next_page_token": state.?next.page_token.optFlatMap(v, + (v != "") ? optional.of([v]) : optional.none() + ), + }.format_query() + ).do_request().as(resp, + (resp.StatusCode == 200) ? + resp.Body.decode_json().as(body, + body.?activity_logs.orValue([]).as(logs, + body.?next_page_token.orValue("").as(next_token, + { + // Step 5 - one event per record; emit a dropped placeholder for + // an empty page so want_more / cursor still take effect. + "events": (size(logs) > 0) ? + dyn(logs.map(e, {"message": e.encode_json()})) + : + dyn([{"retry": true}]), + // Keep going while Zoom returns a token (more pages in this + // window) OR the window has not yet reached today (more + // windows to backfill). This drains the whole lookback in one + // polling cycle. + "want_more": next_token != "" || to_date != now.format("2006-01-02"), + // Carry the token within this poll; cleared when the chain ends. + "next": { + ?"page_token": (next_token != "") ? + optional.of(next_token) + : + optional.none(), + }, + // Step 6 - cursor advancement. While still paging, freeze + // poll_start at from_date so the whole window is drained first. + // When the chain ends, advance to the day after the latest + // in-window event (or the day after to_date for an empty + // window), clamped to today so the cursor never lands in the + // future. The candidate is always >= from_date (and from_date + // never regresses), so this cannot move the cursor backwards. + "cursor": { + "poll_start": (next_token != "") ? + from_date + : + [ + (size(logs) > 0 && logs.map(e, e.time).max() >= from_date + "T00:00:00Z") ? + (timestamp(logs.map(e, e.time).max()) + duration("24h")).format("2006-01-02") + : + (timestamp(to_date + "T00:00:00Z") + duration("24h")).format("2006-01-02"), + now.format("2006-01-02"), + ].min(), + }, + } + ) + ) + ) + : + // Non-200: report the error and stop this cycle without advancing the + // cursor (object error form => the agent retries the same window). + // Clear the in-flight page token so the retry restarts the window + // from its first page rather than reusing a stale (possibly expired) + // token. + { + "events": { + "error": { + "code": string(resp.StatusCode), + "id": string(resp.Status), + "message": "GET " + state.url.trim_right("/") + "/v2/report/activities: " + ( + (size(resp.Body) != 0) ? + string(resp.Body) + : + string(resp.Status) + " (" + string(resp.StatusCode) + ")" + ), + }, + }, + "want_more": false, + "next": {}, + } + ) + ) + ) + ) + ) +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#each tags as |tag|}} + - {{tag}} +{{/each}} +{{#contains "forwarded" tags}} +publisher_pipeline.disable_host: true +{{/contains}} +processors: + - drop_event: + when: + equals: + retry: true +{{#if processors}} +{{processors}} +{{/if}} diff --git a/packages/zoom/data_stream/activity/elasticsearch/ingest_pipeline/default.yml b/packages/zoom/data_stream/activity/elasticsearch/ingest_pipeline/default.yml new file mode 100644 index 00000000000..4a8ad2d938d --- /dev/null +++ b/packages/zoom/data_stream/activity/elasticsearch/ingest_pipeline/default.yml @@ -0,0 +1,230 @@ +--- +description: Parse Zoom activity report events. +processors: + - set: + field: ecs.version + tag: set_ecs_version + value: '8.11.0' + - 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. + The `event.original` field is not touched if the document already has one; it may happen when Logstash sends the document. + 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: zoom.activity + tag: parse_json_from_event_original + if: ctx.event?.original != null + - fingerprint: + tag: fingerprint_event_original + fields: + - event.original + target_field: _id + ignore_missing: true + + # zoom.activity.* + - date: + field: zoom.activity.time + target_field: '@timestamp' + formats: + - ISO8601 + tag: date_activity_time + if: ctx.zoom?.activity?.time != null && ctx.zoom.activity.time != '' + on_failure: + - remove: + field: zoom.activity.time + ignore_missing: true + tag: remove_activity_time_on_date_failure + - append: + field: error.message + value: 'Failed to parse zoom.activity.time: {{{ _ingest.on_failure_message }}}' + tag: append_date_activity_time_failure + - rename: + field: zoom.activity.version + target_field: zoom.activity.client_version + ignore_missing: true + tag: rename_activity_version_to_client_version + + # event.* + - set: + field: event.kind + tag: set_event_kind + value: event + - set: + field: event.action + tag: set_event_action_sign_in + value: sign-in + if: ctx.zoom?.activity?.type == 'Sign in' + - set: + field: event.action + tag: set_event_action_sign_out + value: sign-out + if: ctx.zoom?.activity?.type == 'Sign out' + - set: + field: event.category + tag: set_event_category_auth_session + value: + - authentication + - session + if: ctx.zoom?.activity?.type == 'Sign in' || ctx.zoom?.activity?.type == 'Sign out' + - set: + field: event.type + tag: set_event_type_start + value: + - start + if: ctx.zoom?.activity?.type == 'Sign in' + - set: + field: event.type + tag: set_event_type_end + value: + - end + if: ctx.zoom?.activity?.type == 'Sign out' + - set: + field: event.outcome + tag: set_event_outcome_success + value: success + if: ctx.zoom?.activity?.type == 'Sign in' || ctx.zoom?.activity?.type == 'Sign out' + + # user.* + - rename: + field: zoom.activity.email + target_field: user.email + ignore_missing: true + tag: rename_activity_email_to_user_email + - set: + field: user.name + tag: set_user_name_from_email + copy_from: user.email + ignore_empty_value: true + + # source.* + - convert: + field: zoom.activity.ip_address + type: ip + target_field: source.ip + ignore_missing: true + tag: convert_activity_ip_address_to_source_ip + if: ctx.zoom?.activity?.ip_address != null && ctx.zoom.activity.ip_address != '' + on_failure: + - remove: + field: zoom.activity.ip_address + ignore_missing: true + tag: remove_invalid_activity_ip_address + - append: + field: error.message + value: 'Failed to convert zoom.activity.ip_address to IP: {{{ _ingest.on_failure_message }}}' + tag: append_convert_activity_ip_address_failure + - geoip: + field: source.ip + target_field: source.geo + ignore_missing: true + tag: geoip_source_geo + if: ctx.source?.ip != null + - geoip: + database_file: GeoLite2-ASN.mmdb + field: source.ip + target_field: source.as + properties: + - asn + - organization_name + ignore_missing: true + tag: geoip_source_asn + if: ctx.source?.ip != null + - rename: + field: source.as.asn + target_field: source.as.number + ignore_missing: true + tag: rename_source_as_asn_to_number + - rename: + field: source.as.organization_name + target_field: source.as.organization.name + ignore_missing: true + tag: rename_source_as_organization_name + + # related.* + - append: + field: related.user + tag: append_user_email_to_related_user + value: '{{{user.email}}}' + allow_duplicates: false + if: ctx.user?.email != null + - append: + field: related.ip + tag: append_source_ip_to_related_ip + value: '{{{source.ip}}}' + allow_duplicates: false + if: ctx.source?.ip != null + + # cleanup + - remove: + field: + - zoom.activity.time + - zoom.activity.ip_address + - zoom.activity.email + ignore_missing: true + tag: remove_fields + - script: + tag: script_to_drop_null_values + lang: painless + description: This script processor iterates over the whole document to remove fields with null values. + 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 == 'N/A' || (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); + - set: + field: event.kind + tag: set_pipeline_error_into_event_kind + value: pipeline_error + if: ctx.error?.message != null + - append: + field: tags + value: preserve_original_event + allow_duplicates: false + if: ctx.error?.message != null +on_failure: + - 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 }}}' + - set: + field: event.kind + tag: set_pipeline_error_to_event_kind + value: pipeline_error + - append: + field: tags + value: preserve_original_event + allow_duplicates: false diff --git a/packages/zoom/data_stream/activity/fields/base-fields.yml b/packages/zoom/data_stream/activity/fields/base-fields.yml new file mode 100644 index 00000000000..f897842b3a4 --- /dev/null +++ b/packages/zoom/data_stream/activity/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 + external: ecs + type: constant_keyword + value: zoom +- name: event.dataset + external: ecs + type: constant_keyword + value: zoom.activity +- name: '@timestamp' + external: ecs diff --git a/packages/zoom/data_stream/activity/fields/beats.yml b/packages/zoom/data_stream/activity/fields/beats.yml new file mode 100644 index 00000000000..4084f1dc7f5 --- /dev/null +++ b/packages/zoom/data_stream/activity/fields/beats.yml @@ -0,0 +1,6 @@ +- name: input.type + type: keyword + description: Type of filebeat input. +- name: log.offset + type: long + description: Log offset. diff --git a/packages/zoom/data_stream/activity/fields/fields.yml b/packages/zoom/data_stream/activity/fields/fields.yml new file mode 100644 index 00000000000..14d122b4dbf --- /dev/null +++ b/packages/zoom/data_stream/activity/fields/fields.yml @@ -0,0 +1,12 @@ +- name: zoom.activity + type: group + fields: + - name: client_type + type: keyword + description: The client interface type using which the activity was performed. + - name: client_version + type: keyword + description: Zoom client version of the user. + - name: type + type: keyword + description: The type of activity. diff --git a/packages/zoom/data_stream/activity/manifest.yml b/packages/zoom/data_stream/activity/manifest.yml new file mode 100644 index 00000000000..cae9c286b23 --- /dev/null +++ b/packages/zoom/data_stream/activity/manifest.yml @@ -0,0 +1,71 @@ +title: Activity +type: logs +streams: + - input: cel + title: Activity + description: Collect activity logs report of users under a Zoom account via the REST API. + template_path: cel.yml.hbs + enabled: false + vars: + - name: interval + type: text + title: Interval + description: Duration between requests to the Zoom API. Supported units for this parameter are h/m/s. + required: true + show_user: true + default: 24h + - name: initial_interval + type: text + title: Initial Interval + description: How far back to pull the Activity logs from Zoom API. Must not exceed six months of available report history. Supported units for this parameter are h/m/s. + required: true + show_user: true + default: 720h + - name: page_size + type: integer + title: Page Size + description: Page size for the response of the Zoom API . + required: true + show_user: false + default: 100 + - 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. See [documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-cel.html#_resource_tracer_enable) for details. + - name: http_client_timeout + type: text + title: HTTP Client Timeout + description: Duration before declaring that the HTTP client connection has timed out. Supported time units are ns, us, ms, s, m, h. + multi: false + required: true + show_user: false + default: 30s + - name: tags + type: text + title: Tags + multi: true + required: true + show_user: false + default: + - forwarded + - zoom-activity + - 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. diff --git a/packages/zoom/data_stream/activity/sample_event.json b/packages/zoom/data_stream/activity/sample_event.json new file mode 100644 index 00000000000..80d207e635b --- /dev/null +++ b/packages/zoom/data_stream/activity/sample_event.json @@ -0,0 +1,85 @@ +{ + "@timestamp": "2026-06-09T10:00:00.000Z", + "agent": { + "ephemeral_id": "a9d2a051-4223-46e9-afc5-775e4a5954b1", + "id": "0c2143ea-24c2-41e8-813a-451f8e9d3511", + "name": "elastic-agent-57378", + "type": "filebeat", + "version": "8.19.0" + }, + "data_stream": { + "dataset": "zoom.activity", + "namespace": "93925", + "type": "logs" + }, + "ecs": { + "version": "8.11.0" + }, + "elastic_agent": { + "id": "0c2143ea-24c2-41e8-813a-451f8e9d3511", + "snapshot": false, + "version": "8.19.0" + }, + "event": { + "action": "sign-in", + "agent_id_status": "verified", + "category": [ + "authentication", + "session" + ], + "dataset": "zoom.activity", + "ingested": "2026-06-10T10:04:51Z", + "kind": "event", + "outcome": "success", + "type": [ + "start" + ] + }, + "input": { + "type": "cel" + }, + "related": { + "ip": [ + "192.0.2.10" + ], + "user": [ + "alice@example.com" + ] + }, + "source": { + "as": { + "number": 64500, + "organization": { + "name": "Documentation ASN" + } + }, + "geo": { + "city_name": "Las Vegas", + "continent_name": "North America", + "country_iso_code": "US", + "country_name": "United States", + "location": { + "lat": 36.17497, + "lon": -115.13722 + }, + "region_iso_code": "US-NV", + "region_name": "Nevada" + }, + "ip": "192.0.2.10" + }, + "tags": [ + "forwarded", + "zoom-activity" + ], + "user": { + "email": "alice@example.com", + "name": "alice@example.com" + }, + "zoom": { + "activity": { + "client_type": "Browser", + "client_version": "6.0.0.1000", + "type": "Sign in" + } + } +} diff --git a/packages/zoom/docs/README.md b/packages/zoom/docs/README.md index 08b8480199e..e69121d085e 100644 --- a/packages/zoom/docs/README.md +++ b/packages/zoom/docs/README.md @@ -1,19 +1,191 @@ -# Zoom Webhook Integration +# Zoom Integration for Elastic -This integration creates an HTTP listener that accepts incoming webhook -callbacks from Zoom. +## Overview -To configure Zoom to send webhooks to this integration, please follow the -[Zoom Documentation](https://developers.zoom.us/docs/api/rest/webhook-only-app). +[Zoom](https://www.zoom.com/) is a unified communications platform that provides meetings, webinars, phone, team chat, and Zoom Rooms. The Zoom integration for Elastic enables you to collect Zoom event and audit data so you can monitor user activity, investigate security incidents, and analyze platform usage in Elastic. -The agent running this integration must be able to accept requests from the -Internet in order for Zoom to be able connect. Zoom requires that the webhook -accept requests over HTTPS. So you must either configure the integration with -a valid TLS certificate or use a reverse proxy in front of the integration. +This integration collects data using two complementary methods: -## Compatibility +- **Webhook**: a real-time HTTP listener that receives event notifications pushed by Zoom (meeting, webinar, recording, user, account, phone, team chat, and Zoom Room events). +- **REST API**: a periodic poll of the Zoom REST API to collect the sign in / sign out **activity** report for an account. -This integration is compatible with the Zoom Platform API as of September 2020. +### Compatibility + +- The **activity** data stream uses the Zoom REST API [`GET /report/activities`](https://developers.zoom.us/docs/api/meetings/#tag/reports/get/report/activities) endpoint and requires a Zoom Pro (or higher) plan. + +### How it works + +The **webhook** data stream creates an HTTP listener that accepts incoming webhook callbacks from Zoom. The Elastic Agent running this integration must be reachable from the internet so that Zoom can connect to it. Zoom requires that webhooks are delivered over HTTPS, so you must either configure the integration with a valid TLS certificate or place a reverse proxy that terminates TLS in front of the integration. Incoming events are then routed to the appropriate ingest pipeline based on the Zoom event type. + +The **activity** data stream periodically queries the Zoom REST API using Server-to-Server OAuth. On each interval it requests sign in / sign out activity within a date window (a maximum of one month per request, within the last six months of available history), paginates through the results, and advances a cursor so that subsequent runs collect only new activity. + +## What data does this integration collect? + +The Zoom integration collects the following data: + +- `webhook`: real-time Zoom event notifications, including account, team chat (channel and message), meeting, phone, recording, user, webinar, and Zoom Room events. +- `activity`: account-wide sign in and sign out activity logs from the Zoom REST API reports endpoint. + +### Supported use cases + +Integrating Zoom with Elastic SIEM provides centralized visibility into collaboration and authentication activity. Webhook events support real-time monitoring and detection across meetings, recordings, users, and administrative changes, while the sign in / sign out activity report provides an account-wide authentication audit trail for investigating user access, detecting anomalous logins, and meeting compliance requirements. + +## What do I need to use this integration? + +### From Zoom + +#### Collecting data via Webhook + +1. Create a Webhook-only app in the [Zoom App Marketplace](https://marketplace.zoom.us/) by following the [Zoom webhook documentation](https://developers.zoom.us/docs/api/webhooks/). +2. Add the event types you want to receive and set the event notification endpoint URL to the public HTTPS address where this integration is reachable. +3. Note the **Secret Token** generated by Zoom. It is used for CRC endpoint validation and to verify the authenticity of incoming events. + +#### Collecting data from the Zoom REST API + +1. Create a **Server-to-Server OAuth** app in the [Zoom App Marketplace](https://marketplace.zoom.us/) by following the [Server-to-Server OAuth documentation](https://developers.zoom.us/docs/internal-apps/s2s-oauth/). +2. Record the app's **Account ID**, **Client ID**, and **Client Secret**. +3. Add the `report:read:admin` scope (or the granular `report:read:user_activities:admin` scope) to the app and activate it. A Zoom Pro or higher plan is required. + +## How do I deploy this integration? + +### 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. + +Elastic Agent is required to receive the Zoom webhook callbacks or to poll the Zoom REST API, and to ship the data to Elastic, where the events are then processed via the integration's ingest pipelines. + +### Onboard / configure + +1. In the top search bar in Kibana, search for **Integrations**. +2. In the search bar, type **Zoom**. +3. Select the **Zoom** integration from the search results. +4. Select **Add Zoom** to add the integration. +5. Enable and configure only the collection methods which you will use. + + * To **Collect Zoom logs via Webhook**, you'll need to: + + - Configure the **Listen Address**, **Listen Port**, and **Webhook path** where the integration accepts requests. + - Optionally enable **CRC validation** and provide the **Zoom Secret Token**, and/or configure a custom header to verify incoming requests. + - Provide a valid **TLS** certificate (or front the integration with a TLS-terminating reverse proxy), since Zoom requires HTTPS. + + * To **Collect Zoom logs via REST API**, you'll need to: + + - Configure the **Account ID**, **Client ID**, and **Client Secret** of your Server-to-Server OAuth app. + - Adjust the integration configuration parameters if required, including the **Interval** and **Initial Interval** (lookback), to enable data collection. + +6. Select **Save and continue** to save the integration. + +### Validation + +#### Dashboards populated + +1. In the top search bar in Kibana, search for **Dashboards**. +2. In the search bar, type **Zoom**. +3. Select a dashboard for the dataset you are collecting, 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 + +### webhook + +This is the `webhook` data stream. It collects real-time event notifications pushed by Zoom over an HTTP endpoint. + +An example event for `webhook` looks as following: + +```json +{ + "@timestamp": "2019-07-01T17:03:04.527Z", + "agent": { + "ephemeral_id": "25caa0a1-dfe6-4499-8945-78d3f7b50b5c", + "id": "a2a6d6ce-cd38-4a30-8877-bf698b0d346b", + "name": "docker-fleet-agent", + "type": "filebeat", + "version": "8.8.1" + }, + "data_stream": { + "dataset": "zoom.webhook", + "namespace": "ep", + "type": "logs" + }, + "ecs": { + "version": "8.11.0" + }, + "elastic_agent": { + "id": "a2a6d6ce-cd38-4a30-8877-bf698b0d346b", + "snapshot": false, + "version": "8.8.1" + }, + "event": { + "action": "account.updated", + "agent_id_status": "verified", + "category": [ + "iam" + ], + "dataset": "zoom.webhook", + "ingested": "2023-06-22T16:37:08Z", + "kind": [ + "event" + ], + "original": "{\"event\":\"account.updated\",\"payload\":{\"account_id\":\"abKKcd_IGRCq63yEy673lCA\",\"object\":{\"account_alias\":\"MH\",\"account_name\":\"Michael Harris\",\"id\":\"eFs_EGRCq6ByEyA73qCA\"},\"old_object\":{\"account_alias\":\"\",\"account_name\":\"Mike Harris\",\"id\":\"eFs_EGRCq6ByEyA73qCA\"},\"operator\":\"theoperatoremail@someemail.com\",\"operator_id\":\"iKoRgfbaTazDX6r2Q_eQsQL\",\"time_stamp\":1562000584527}}", + "timezone": "+00:00", + "type": [ + "user", + "change" + ] + }, + "input": { + "type": "http_endpoint" + }, + "observer": { + "product": "Webhook", + "vendor": "Zoom" + }, + "related": { + "user": [ + "iKoRgfbaTazDX6r2Q_eQsQL", + "eFs_EGRCq6ByEyA73qCA" + ] + }, + "tags": [ + "preserve_original_event", + "zoom-webhook", + "forwarded" + ], + "user": { + "changes": { + "full_name": "Michael Harris", + "name": "MH" + }, + "email": "theoperatoremail@someemail.com", + "id": "iKoRgfbaTazDX6r2Q_eQsQL", + "target": { + "full_name": "Mike Harris", + "id": "eFs_EGRCq6ByEyA73qCA" + } + }, + "zoom": { + "account": { + "account_alias": "MH", + "account_name": "Michael Harris" + }, + "master_account_id": "abKKcd_IGRCq63yEy673lCA", + "old_values": { + "account_name": "Mike Harris", + "id": "eFs_EGRCq6ByEyA73qCA" + }, + "operator": "theoperatoremail@someemail.com", + "operator_id": "iKoRgfbaTazDX6r2Q_eQsQL", + "sub_account_id": "eFs_EGRCq6ByEyA73qCA" + } +} +``` **Exported fields** @@ -192,3 +364,127 @@ This integration is compatible with the Zoom Platform API as of September 2020. | zoom.zoomroom.resource_email | Email address associated with the calendar in use by the Zoom room | keyword | | zoom.zoomroom.room_name | The configured name of the Zoom room | keyword | + +### activity + +This is the `activity` data stream. It collects sign in / sign out activity logs from the Zoom REST API. + +An example event for `activity` looks as following: + +```json +{ + "@timestamp": "2026-06-09T10:00:00.000Z", + "agent": { + "ephemeral_id": "a9d2a051-4223-46e9-afc5-775e4a5954b1", + "id": "0c2143ea-24c2-41e8-813a-451f8e9d3511", + "name": "elastic-agent-57378", + "type": "filebeat", + "version": "8.19.0" + }, + "data_stream": { + "dataset": "zoom.activity", + "namespace": "93925", + "type": "logs" + }, + "ecs": { + "version": "8.11.0" + }, + "elastic_agent": { + "id": "0c2143ea-24c2-41e8-813a-451f8e9d3511", + "snapshot": false, + "version": "8.19.0" + }, + "event": { + "action": "sign-in", + "agent_id_status": "verified", + "category": [ + "authentication", + "session" + ], + "dataset": "zoom.activity", + "ingested": "2026-06-10T10:04:51Z", + "kind": "event", + "outcome": "success", + "type": [ + "start" + ] + }, + "input": { + "type": "cel" + }, + "related": { + "ip": [ + "192.0.2.10" + ], + "user": [ + "alice@example.com" + ] + }, + "source": { + "as": { + "number": 64500, + "organization": { + "name": "Documentation ASN" + } + }, + "geo": { + "city_name": "Las Vegas", + "continent_name": "North America", + "country_iso_code": "US", + "country_name": "United States", + "location": { + "lat": 36.17497, + "lon": -115.13722 + }, + "region_iso_code": "US-NV", + "region_name": "Nevada" + }, + "ip": "192.0.2.10" + }, + "tags": [ + "forwarded", + "zoom-activity" + ], + "user": { + "email": "alice@example.com", + "name": "alice@example.com" + }, + "zoom": { + "activity": { + "client_type": "Browser", + "client_version": "6.0.0.1000", + "type": "Sign in" + } + } +} +``` + +**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 | +| log.offset | Log offset. | long | +| zoom.activity.client_type | The client interface type using which the activity was performed. | keyword | +| zoom.activity.client_version | Zoom client version of the user. | keyword | +| zoom.activity.type | The type of activity. | keyword | + + +### Inputs used + +These inputs are used in this integration: + +- [http_endpoint](https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-http_endpoint) +- [cel](https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-cel) + +### API usage + +This integration uses the following APIs: + +- `activity`: [Get sign in / sign out activity report](https://developers.zoom.us/docs/api/meetings/#tag/reports/get/report/activities). diff --git a/packages/zoom/img/zoom-activity-dashboard.png b/packages/zoom/img/zoom-activity-dashboard.png new file mode 100644 index 00000000000..435b55de34c Binary files /dev/null and b/packages/zoom/img/zoom-activity-dashboard.png differ diff --git a/packages/zoom/kibana/dashboard/zoom-e9055606-5d33-4f84-b883-4498c22b6dbc.json b/packages/zoom/kibana/dashboard/zoom-e9055606-5d33-4f84-b883-4498c22b6dbc.json new file mode 100644 index 00000000000..577a27be650 --- /dev/null +++ b/packages/zoom/kibana/dashboard/zoom-e9055606-5d33-4f84-b883-4498c22b6dbc.json @@ -0,0 +1,785 @@ +{ + "attributes": { + "controlGroupInput": { + "chainingSystem": "HIERARCHICAL", + "controlStyle": "oneLine", + "ignoreParentSettingsJSON": { + "ignoreFilters": false, + "ignoreQuery": false, + "ignoreTimerange": false, + "ignoreValidations": false + }, + "panelsJSON": { + "42bd5a1a-4910-464c-a891-0de8b20fb8a0": { + "explicitInput": { + "dataViewId": "logs-*", + "exclude": false, + "existsSelected": false, + "fieldName": "user.name", + "searchTechnique": "prefix", + "selectedOptions": [], + "sort": { + "by": "_count", + "direction": "desc" + }, + "title": "User" + }, + "grow": true, + "order": 1, + "type": "optionsListControl", + "width": "medium" + }, + "93d82b38-3bad-48d6-9d43-c8359b7bbab3": { + "explicitInput": { + "dataViewId": "logs-*", + "exclude": false, + "existsSelected": false, + "fieldName": "zoom.activity.client_type", + "searchTechnique": "prefix", + "selectedOptions": [], + "sort": { + "by": "_count", + "direction": "desc" + }, + "title": "Client Type" + }, + "grow": true, + "order": 0, + "type": "optionsListControl", + "width": "medium" + } + }, + "showApplySelections": false + }, + "description": "", + "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": "zoom.activity" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "zoom.activity" + } + } + } + ], + "query": { + "language": "kuery", + "query": "" + } + } + }, + "optionsJSON": { + "hidePanelTitles": false, + "syncColors": false, + "syncCursor": true, + "syncTooltips": false, + "useMargins": true + }, + "panelsJSON": [ + { + "embeddableConfig": { + "attributes": { + "references": [ + { + "id": "logs-*", + "name": "indexpattern-datasource-layer-90a95ce9-7d3d-4dac-a55a-a565d116f655", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "a81a0430-465f-4346-a7a2-7bdc78053f46", + "type": "index-pattern" + } + ], + "state": { + "adHocDataViews": {}, + "datasourceStates": { + "formBased": { + "layers": { + "90a95ce9-7d3d-4dac-a55a-a565d116f655": { + "columnOrder": [ + "d497ef74-547c-461b-942f-90723ab6daa6", + "b3448b77-0c90-42fd-85c3-5d1703b03215" + ], + "columns": { + "b3448b77-0c90-42fd-85c3-5d1703b03215": { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Count", + "operationType": "count", + "params": { + "emptyAsNull": false, + "format": { + "id": "number", + "params": { + "decimals": 0 + } + } + }, + "sourceField": "___records___" + }, + "d497ef74-547c-461b-942f-90723ab6daa6": { + "dataType": "date", + "isBucketed": true, + "label": "@timestamp", + "operationType": "date_histogram", + "params": { + "dropPartials": false, + "includeEmptyRows": true, + "interval": "auto" + }, + "sourceField": "@timestamp" + } + }, + "ignoreGlobalFilters": false, + "incompleteColumns": {}, + "sampling": 1 + } + } + }, + "indexpattern": { + "layers": {} + }, + "textBased": { + "layers": {} + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "a81a0430-465f-4346-a7a2-7bdc78053f46", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "zoom.activity" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "zoom.activity" + } + } + } + ], + "internalReferences": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "layers": [ + { + "accessors": [ + "b3448b77-0c90-42fd-85c3-5d1703b03215" + ], + "colorMapping": { + "assignments": [], + "colorMode": { + "type": "categorical" + }, + "paletteId": "default", + "specialAssignments": [ + { + "color": { + "type": "loop" + }, + "rules": [ + { + "type": "other" + } + ], + "touched": false + } + ] + }, + "layerId": "90a95ce9-7d3d-4dac-a55a-a565d116f655", + "layerType": "data", + "position": "top", + "seriesType": "line", + "showGridlines": false, + "xAccessor": "d497ef74-547c-461b-942f-90723ab6daa6" + } + ], + "legend": { + "isVisible": true, + "position": "right", + "shouldTruncate": false, + "showSingleSeries": false + }, + "preferredSeriesType": "line", + "title": "Empty XY chart", + "valueLabels": "hide" + } + }, + "title": "", + "type": "lens", + "visualizationType": "lnsXY" + }, + "enhancements": { + "dynamicActions": { + "events": [] + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "a81a0430-465f-4346-a7a2-7bdc78053f46", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "zoom.activity" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "zoom.activity" + } + } + } + ], + "query": { + "language": "kuery", + "query": "" + }, + "syncColors": false, + "syncCursor": true, + "syncTooltips": false, + "title": "Activity over Time" + }, + "gridData": { + "h": 15, + "i": "56e0b837-8751-4c6c-bf1f-557e702e468a", + "w": 24, + "x": 0, + "y": 15 + }, + "panelIndex": "56e0b837-8751-4c6c-bf1f-557e702e468a", + "type": "lens" + }, + { + "embeddableConfig": { + "attributes": { + "references": [ + { + "id": "logs-*", + "name": "indexpattern-datasource-layer-27c98f33-f3f9-4ed2-8372-9b138d0a6de5", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "98229952-ed78-47b3-a208-76e3c83274d5", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "549affe8-54da-498b-b164-a49c435ca2f8", + "type": "index-pattern" + } + ], + "state": { + "adHocDataViews": {}, + "datasourceStates": { + "formBased": { + "layers": { + "27c98f33-f3f9-4ed2-8372-9b138d0a6de5": { + "columnOrder": [ + "b4690374-71c8-447e-b255-b8f2263616e8" + ], + "columns": { + "b4690374-71c8-447e-b255-b8f2263616e8": { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Total Sign In", + "operationType": "count", + "params": { + "emptyAsNull": true, + "format": { + "id": "number", + "params": { + "decimals": 0 + } + } + }, + "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": "98229952-ed78-47b3-a208-76e3c83274d5", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "zoom.activity" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "zoom.activity" + } + } + }, + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "event.action", + "index": "549affe8-54da-498b-b164-a49c435ca2f8", + "key": "event.action", + "negate": false, + "params": { + "query": "sign-in" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "event.action": "sign-in" + } + } + } + ], + "internalReferences": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "color": "#6092C0", + "layerId": "27c98f33-f3f9-4ed2-8372-9b138d0a6de5", + "layerType": "data", + "metricAccessor": "b4690374-71c8-447e-b255-b8f2263616e8", + "secondaryTrend": { + "type": "none" + } + } + }, + "title": "", + "type": "lens", + "visualizationType": "lnsMetric" + }, + "enhancements": { + "dynamicActions": { + "events": [] + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "98229952-ed78-47b3-a208-76e3c83274d5", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "zoom.activity" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "zoom.activity" + } + } + }, + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "event.action", + "index": "549affe8-54da-498b-b164-a49c435ca2f8", + "key": "event.action", + "negate": false, + "params": { + "query": "sign-in" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "event.action": "sign-in" + } + } + } + ], + "hidePanelTitles": true, + "query": { + "language": "kuery", + "query": "" + }, + "syncColors": false, + "syncCursor": true, + "syncTooltips": false, + "title": "Total Sign In" + }, + "gridData": { + "h": 15, + "i": "5872cf31-6b4b-425f-8393-05a3cdb563af", + "w": 24, + "x": 24, + "y": 0 + }, + "panelIndex": "5872cf31-6b4b-425f-8393-05a3cdb563af", + "type": "lens" + }, + { + "embeddableConfig": { + "attributes": { + "references": [ + { + "id": "logs-*", + "name": "indexpattern-datasource-layer-27c98f33-f3f9-4ed2-8372-9b138d0a6de5", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "486d9810-9ae3-4f64-8848-2f991129b37d", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "b3a6d20f-4aeb-48fb-bf25-1d3a052fd39e", + "type": "index-pattern" + } + ], + "state": { + "adHocDataViews": {}, + "datasourceStates": { + "formBased": { + "layers": { + "27c98f33-f3f9-4ed2-8372-9b138d0a6de5": { + "columnOrder": [ + "b4690374-71c8-447e-b255-b8f2263616e8" + ], + "columns": { + "b4690374-71c8-447e-b255-b8f2263616e8": { + "customLabel": true, + "dataType": "number", + "isBucketed": false, + "label": "Total Sign Out", + "operationType": "count", + "params": { + "emptyAsNull": true, + "format": { + "id": "number", + "params": { + "decimals": 0 + } + } + }, + "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": "486d9810-9ae3-4f64-8848-2f991129b37d", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "zoom.activity" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "zoom.activity" + } + } + }, + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "event.action", + "index": "b3a6d20f-4aeb-48fb-bf25-1d3a052fd39e", + "key": "event.action", + "negate": false, + "params": { + "query": "sign-out" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "event.action": "sign-out" + } + } + } + ], + "internalReferences": [], + "query": { + "language": "kuery", + "query": "" + }, + "visualization": { + "color": "#6092C0", + "layerId": "27c98f33-f3f9-4ed2-8372-9b138d0a6de5", + "layerType": "data", + "metricAccessor": "b4690374-71c8-447e-b255-b8f2263616e8", + "secondaryTrend": { + "type": "none" + } + } + }, + "title": "", + "type": "lens", + "visualizationType": "lnsMetric" + }, + "enhancements": { + "dynamicActions": { + "events": [] + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "data_stream.dataset", + "index": "486d9810-9ae3-4f64-8848-2f991129b37d", + "key": "data_stream.dataset", + "negate": false, + "params": { + "query": "zoom.activity" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "zoom.activity" + } + } + }, + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "field": "event.action", + "index": "b3a6d20f-4aeb-48fb-bf25-1d3a052fd39e", + "key": "event.action", + "negate": false, + "params": { + "query": "sign-out" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "event.action": "sign-out" + } + } + } + ], + "hidePanelTitles": true, + "query": { + "language": "kuery", + "query": "" + }, + "syncColors": false, + "syncCursor": true, + "syncTooltips": false, + "title": "Total Sign Out" + }, + "gridData": { + "h": 15, + "i": "c07a00af-012e-4804-9e19-fb596bf105fb", + "w": 24, + "x": 0, + "y": 0 + }, + "panelIndex": "c07a00af-012e-4804-9e19-fb596bf105fb", + "type": "lens" + }, + { + "embeddableConfig": { + "description": "", + "enhancements": { + "dynamicActions": { + "events": [] + } + }, + "savedObjectId": "e9178ff9-be70-4161-8e4d-9ec7a0d171b5", + "title": "Activity Essential Details" + }, + "gridData": { + "h": 15, + "i": "48d0042f-e909-40d2-92c1-56902457f498", + "w": 24, + "x": 24, + "y": 15 + }, + "panelIndex": "48d0042f-e909-40d2-92c1-56902457f498", + "panelRefName": "panel_48d0042f-e909-40d2-92c1-56902457f498", + "type": "search" + } + ], + "timeRestore": false, + "title": "[Logs Zoom] Activity Overview", + "version": 3 + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2026-06-10T08:10:09.245Z", + "created_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "id": "zoom-e9055606-5d33-4f84-b883-4498c22b6dbc", + "references": [ + { + "id": "logs-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "56e0b837-8751-4c6c-bf1f-557e702e468a:indexpattern-datasource-layer-90a95ce9-7d3d-4dac-a55a-a565d116f655", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "56e0b837-8751-4c6c-bf1f-557e702e468a:a81a0430-465f-4346-a7a2-7bdc78053f46", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "5872cf31-6b4b-425f-8393-05a3cdb563af:indexpattern-datasource-layer-27c98f33-f3f9-4ed2-8372-9b138d0a6de5", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "5872cf31-6b4b-425f-8393-05a3cdb563af:98229952-ed78-47b3-a208-76e3c83274d5", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "5872cf31-6b4b-425f-8393-05a3cdb563af:549affe8-54da-498b-b164-a49c435ca2f8", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "c07a00af-012e-4804-9e19-fb596bf105fb:indexpattern-datasource-layer-27c98f33-f3f9-4ed2-8372-9b138d0a6de5", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "c07a00af-012e-4804-9e19-fb596bf105fb:486d9810-9ae3-4f64-8848-2f991129b37d", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "c07a00af-012e-4804-9e19-fb596bf105fb:b3a6d20f-4aeb-48fb-bf25-1d3a052fd39e", + "type": "index-pattern" + }, + { + "id": "zoom-e9178ff9-be70-4161-8e4d-9ec7a0d171b5", + "name": "48d0042f-e909-40d2-92c1-56902457f498:panel_48d0042f-e909-40d2-92c1-56902457f498", + "type": "search" + }, + { + "id": "logs-*", + "name": "controlGroup_93d82b38-3bad-48d6-9d43-c8359b7bbab3:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "controlGroup_42bd5a1a-4910-464c-a891-0de8b20fb8a0:optionsListDataView", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + } + ], + "type": "dashboard", + "typeMigrationVersion": "10.3.0", + "updated_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0" +} \ No newline at end of file diff --git a/packages/zoom/kibana/search/zoom-e9178ff9-be70-4161-8e4d-9ec7a0d171b5.json b/packages/zoom/kibana/search/zoom-e9178ff9-be70-4161-8e4d-9ec7a0d171b5.json new file mode 100644 index 00000000000..635d095d066 --- /dev/null +++ b/packages/zoom/kibana/search/zoom-e9178ff9-be70-4161-8e4d-9ec7a0d171b5.json @@ -0,0 +1,75 @@ +{ + "attributes": { + "columns": [ + "user.name", + "source.ip", + "zoom.activity.client_type", + "zoom.activity.client_version", + "event.action" + ], + "description": "", + "grid": {}, + "hideChart": false, + "isTextBasedQuery": false, + "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": "zoom.activity" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "data_stream.dataset": "zoom.activity" + } + } + } + ], + "indexRefName": "kibanaSavedObjectMeta.searchSourceJSON.index", + "query": { + "language": "kuery", + "query": "" + } + } + }, + "sort": [ + [ + "@timestamp", + "desc" + ] + ], + "timeRestore": false, + "title": "[Logs Zoom] Activity Essential Details" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2026-06-10T08:09:43.321Z", + "created_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "id": "zoom-e9178ff9-be70-4161-8e4d-9ec7a0d171b5", + "references": [ + { + "id": "logs-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + }, + { + "id": "logs-*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern" + } + ], + "type": "search", + "typeMigrationVersion": "10.5.0", + "updated_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0" +} \ No newline at end of file diff --git a/packages/zoom/manifest.yml b/packages/zoom/manifest.yml index 0fea090d6bd..5a99bbb1f3a 100644 --- a/packages/zoom/manifest.yml +++ b/packages/zoom/manifest.yml @@ -1,17 +1,27 @@ name: zoom title: Zoom -version: "1.23.0" +version: "1.24.0" description: Collect logs from Zoom with Elastic Agent. type: integration -format_version: "3.0.2" -categories: +format_version: "3.3.2" +categories: - security - productivity_security # Added observability category as Zoom provides meeting and user activity data for monitoring - observability conditions: kibana: - version: "^8.13.0 || ^9.0.0" + version: "^8.19.0 || ^9.1.0" +icons: + - src: /img/zoom_blue.svg + title: Zoom + size: 516x240 + type: image/svg+xml +screenshots: + - src: /img/zoom-activity-dashboard.png + title: Zoom Activity Overview Dashboard Screenshot + size: 600x600 + type: image/png policy_templates: - name: zoom title: Zoom logs @@ -20,11 +30,79 @@ policy_templates: - type: http_endpoint title: "Collect Zoom logs via Webhook" description: "Collecting logs from Zoom instances via Webhook" + - type: cel + title: "Collect Zoom logs via REST API" + description: "Collecting logs from Zoom instances via the REST API" + vars: + - name: url + type: text + title: URL + description: Base URL of the Zoom API. + required: true + show_user: false + default: https://api.zoom.us + - name: token_url + type: text + title: OAuth Token URL + description: OAuth Token URL of the Zoom OAuth app. + required: true + show_user: false + default: https://zoom.us + - name: account_id + type: text + title: Account ID + description: Account ID of the Zoom account. + required: true + show_user: true + - name: client_id + type: text + title: Client ID + description: Client ID of the Zoom OAuth app. + required: true + show_user: true + - name: client_secret + type: password + title: Client Secret + description: Client secret of the Zoom OAuth app. + 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 -icons: - - src: /img/zoom_blue.svg - title: Zoom - size: 516x240 - type: image/svg+xml diff --git a/packages/zoom/validation.yml b/packages/zoom/validation.yml index a96151416a6..ac0551a4ce9 100644 --- a/packages/zoom/validation.yml +++ b/packages/zoom/validation.yml @@ -1,3 +1,4 @@ errors: exclude_checks: - SVR00005 # Kibana version for saved tags. + - SVR00004 # References in dashboards.