From b679fd4d13334185febd3ca6a9773700f3c65555 Mon Sep 17 00:00:00 2001 From: Paulo Silva Date: Wed, 1 Apr 2026 14:43:51 -0700 Subject: [PATCH 1/3] Add Elastic Agent metadata to generated integration documents Updates the organization data model and integration logic to include stable Elastic Agent identification in all generated documents. This ensures that synthetic data correctly mimics real-world ECS-compliant logs and supports security features that rely on agent-based correlation. Key changes: - Adds `elasticAgentId` to Device and Host models, and `centralAgent` to the Organization model. - Implements `buildCentralAgent`, `buildLocalAgent`, and `buildServerAgent` helpers in `BaseIntegration`. - Updates all existing integrations to include the `agent` field in their documents. - Expands the Okta system integration with anomalous event generation to support testing for Post-Authentication Detection (PAD). --- .../update-org-data-integrations/SKILL.md | 48 +- .../active_directory_integration.ts | 30 +- .../atlassian_bitbucket_integration.ts | 17 +- .../atlassian_confluence_integration.ts | 17 +- .../atlassian_jira_integration.ts | 17 +- .../integrations/auth0_integration.ts | 17 +- .../integrations/authentik_integration.ts | 40 +- .../integrations/azure_integration.ts | 140 ++- .../org_data/integrations/base_integration.ts | 52 +- .../integrations/beyondinsight_integration.ts | 36 +- .../integrations/bitwarden_integration.ts | 33 +- .../org_data/integrations/box_integration.ts | 18 +- .../integrations/canva_integration.ts | 17 +- .../integrations/cisco_duo_integration.ts | 3 +- .../integrations/cloud_asset_integration.ts | 5 +- .../cloudflare_logpush_integration.ts | 18 +- .../integrations/cloudtrail_integration.ts | 16 +- .../integrations/crowdstrike_integration.ts | 77 +- .../integrations/cyberark_pas_integration.ts | 13 +- .../integrations/endpoint_integration.ts | 41 +- .../integrations/entra_id_integration.ts | 9 +- .../integrations/forgerock_integration.ts | 43 +- .../org_data/integrations/gcp_integration.ts | 21 +- .../integrations/github_integration.ts | 1 + .../integrations/gitlab_integration.ts | 35 +- .../google_workspace_integration.ts | 3 + .../hashicorp_vault_integration.ts | 18 +- .../island_browser_integration.ts | 33 +- .../integrations/jamf_pro_integration.ts | 5 +- .../integrations/jumpcloud_integration.ts | 10 +- .../integrations/keeper_integration.ts | 10 +- .../integrations/keycloak_integration.ts | 10 +- .../integrations/lastpass_integration.ts | 25 +- .../integrations/lyve_cloud_integration.ts | 10 +- .../integrations/mattermost_integration.ts | 10 +- .../integrations/mongodb_atlas_integration.ts | 13 +- .../org_data/integrations/o365_integration.ts | 1 + .../org_data/integrations/okta_integration.ts | 15 +- .../integrations/okta_system_integration.ts | 1000 ++++++++++++++++- .../integrations/onepassword_integration.ts | 4 +- .../ping_directory_integration.ts | 14 +- .../integrations/ping_one_integration.ts | 12 +- .../integrations/sailpoint_integration.ts | 12 +- .../integrations/servicenow_integration.ts | 18 +- .../integrations/slack_integration.ts | 17 +- .../integrations/system_integration.ts | 6 + .../integrations/teleport_integration.ts | 9 +- .../integrations/thycotic_ss_integration.ts | 10 +- .../integrations/ti_abusech_integration.ts | 18 +- .../integrations/workday_integration.ts | 12 +- .../org_data/integrations/zoom_integration.ts | 5 +- .../integrations/zscaler_zia_integration.ts | 9 + src/commands/org_data/org_data_generator.ts | 10 + src/commands/org_data/types.ts | 11 + 54 files changed, 1873 insertions(+), 221 deletions(-) diff --git a/.agents/skills/update-org-data-integrations/SKILL.md b/.agents/skills/update-org-data-integrations/SKILL.md index c2704ba9..9fccc0c8 100644 --- a/.agents/skills/update-org-data-integrations/SKILL.md +++ b/.agents/skills/update-org-data-integrations/SKILL.md @@ -186,14 +186,34 @@ integration that references users: #### Stable fields on Device -| Field | Description | Example | -| --------------------- | ----------------------------------- | ------------------- | -| `id` | Device UUID, used as `host.id` | `276e59a0-...` | -| `macAddress` | Stable MAC address (dash-separated) | `8a-d3-02-ed-99-a2` | -| `ipAddress` | Stable IPv4 address | `234.22.230.186` | -| `crowdstrikeAgentId` | CrowdStrike Falcon agent ID | `e045e02b...` | -| `crowdstrikeDeviceId` | CrowdStrike device ID | `efb573dc...` | -| `serialNumber` | Hardware serial number | `A1B2C3D4E5F6` | +| Field | Description | Example | +| --------------------- | ---------------------------------------------- | ------------------- | +| `id` | Device UUID, used as `host.id` | `276e59a0-...` | +| `macAddress` | Stable MAC address (dash-separated) | `8a-d3-02-ed-99-a2` | +| `ipAddress` | Stable IPv4 address | `234.22.230.186` | +| `crowdstrikeAgentId` | CrowdStrike Falcon agent ID | `e045e02b...` | +| `crowdstrikeDeviceId` | CrowdStrike device ID | `efb573dc...` | +| `serialNumber` | Hardware serial number | `A1B2C3D4E5F6` | +| `elasticAgentId` | Elastic Agent UUID for local workstation agent | `c3f1a9d2-...` | + +#### Stable fields on Host + +| Field | Description | Example | +| ---------------- | -------------------------------------- | ----------------------------- | +| `id` | Host UUID | `a2b3c4d5-...` | +| `name` | Server hostname | `api-server-prod-a1b2c3` | +| `elasticAgentId` | Elastic Agent UUID for the server host | `d4e5f6a7-...` | + +#### CentralAgent on Organization + +The `org.centralAgent` represents a single Elastic Agent deployed on a central fleet +collector server. All cloud/SaaS integrations (Okta, GWS, GitHub, etc.) share this agent +identity in their documents. + +| Field | Description | Example | +| ------ | -------------------------------- | --------------------- | +| `id` | Stable UUID for the agent | `b7c8d9e0-...` | +| `name` | Hostname of the collector server | `fleet-collector-01` | #### Correlation rules for ECS fields @@ -209,6 +229,10 @@ When generating documents, map ECS fields to the stable Employee/Device values: | `host.name` | `${employee.userName}-${device.platform}` for employee devices. | | `host.mac` | Always `device.macAddress`. Convert separator as needed (dash for ECS/endpoint, colon for Jamf). **Never** call `faker.internet.mac()`. | | `host.ip` | Always `device.ipAddress` as the primary IP. **Never** call `faker.internet.ipv4()` for host IPs. Random IPs are OK for `source.ip`, `destination.ip`, or `external_ip`. | +| `agent.id` | Local workstation: `device.elasticAgentId`. Server: `host.elasticAgentId`. Centralized cloud: `org.centralAgent.id`. Use `buildLocalAgent()`, `buildServerAgent()`, or `buildCentralAgent()` helpers from `BaseIntegration`. | +| `agent.name` | Local workstation: `${employee.userName}-${device.platform}` (same as `host.name`). Server: `host.name`. Centralized cloud: `org.centralAgent.name` (`fleet-collector-01`). | +| `agent.type` | `'endpoint'` for Elastic Defend, `'filebeat'` for all other integrations. | +| `agent.version` | Always `ELASTIC_AGENT_VERSION` (`'8.17.4'`), exported from `base_integration.ts`. | #### When to extend the CorrelationMap @@ -272,14 +296,20 @@ When examining a pipeline YAML, look for these processor types: #### Document structure template ```typescript -// CORRECT: Raw pre-pipeline format +// CORRECT: Raw pre-pipeline format with agent metadata return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), // or buildLocalAgent(device, hostname) / buildServerAgent(host) message: JSON.stringify(rawApiPayload), data_stream: { namespace: 'default', type: 'logs', dataset: '.' }, } as IntegrationDocument; ``` +Every document MUST include an `agent` field. Use the appropriate helper from `BaseIntegration`: +- **`buildCentralAgent(org)`** -- for cloud/SaaS integrations (Okta, GWS, GitHub, etc.) +- **`buildLocalAgent(device, hostname, agentType?)`** -- for per-workstation integrations (endpoint, crowdstrike, jamf_pro, island_browser, zscaler_zia) +- **`buildServerAgent(host)`** -- for per-server integrations (system) + #### What NOT to include Do NOT pre-set any fields that the ingest pipeline derives: diff --git a/src/commands/org_data/integrations/active_directory_integration.ts b/src/commands/org_data/integrations/active_directory_integration.ts index 3ea8c4ad..8042b8db 100644 --- a/src/commands/org_data/integrations/active_directory_integration.ts +++ b/src/commands/org_data/integrations/active_directory_integration.ts @@ -4,7 +4,12 @@ * Based on beats x-pack/filebeat/input/entityanalytics/provider/activedirectory/ */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, Device, ActiveDirectoryDocument, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -55,6 +60,7 @@ export class ActiveDirectoryIntegration extends BaseIntegration { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; const timestamp = this.getTimestamp(); + const centralAgent = this.buildCentralAgent(org); const baseDn = `DC=${org.domain.replace('.com', '')},DC=com`; @@ -63,7 +69,14 @@ export class ActiveDirectoryIntegration extends BaseIntegration { const userDn = this.buildUserDn(employee, baseDn); correlationMap.adDnToEmployee.set(userDn, employee); - const userDoc = this.createUserDocument(employee, org, timestamp, baseDn, userDn); + const userDoc = this.createUserDocument( + employee, + org, + timestamp, + baseDn, + userDn, + centralAgent, + ); documents.push(userDoc); } @@ -73,7 +86,14 @@ export class ActiveDirectoryIntegration extends BaseIntegration { (d) => d.type === 'laptop' && d.platform === 'windows', ); for (const device of windowsDevices) { - const computerDoc = this.createDeviceDocument(device, employee, org, timestamp, baseDn); + const computerDoc = this.createDeviceDocument( + device, + employee, + org, + timestamp, + baseDn, + centralAgent, + ); documents.push(computerDoc); } } @@ -100,6 +120,7 @@ export class ActiveDirectoryIntegration extends BaseIntegration { timestamp: string, baseDn: string, userDn: string, + centralAgent: AgentData, ): ActiveDirectoryDocument { const whenCreated = faker.date.past({ years: 2 }).toISOString(); const whenChanged = faker.date.recent({ days: 30 }).toISOString(); @@ -181,6 +202,7 @@ export class ActiveDirectoryIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, activedirectory: { id: userDn, user: entry, @@ -213,6 +235,7 @@ export class ActiveDirectoryIntegration extends BaseIntegration { org: Organization, timestamp: string, baseDn: string, + centralAgent: AgentData, ): ActiveDirectoryDocument { const whenCreated = faker.date.past({ years: 1 }).toISOString(); const whenChanged = faker.date.recent({ days: 14 }).toISOString(); @@ -255,6 +278,7 @@ export class ActiveDirectoryIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, activedirectory: { id: computerDn, device: entry, diff --git a/src/commands/org_data/integrations/atlassian_bitbucket_integration.ts b/src/commands/org_data/integrations/atlassian_bitbucket_integration.ts index f7d55cd7..5563c2aa 100644 --- a/src/commands/org_data/integrations/atlassian_bitbucket_integration.ts +++ b/src/commands/org_data/integrations/atlassian_bitbucket_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic atlassian_bitbucket integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -234,11 +239,12 @@ export class AtlassianBitbucketIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee, org)); + documents.push(this.createAuditDocument(employee, org, centralAgent)); } } @@ -246,7 +252,11 @@ export class AtlassianBitbucketIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee, org: Organization): IntegrationDocument { + private createAuditDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -283,6 +293,7 @@ export class AtlassianBitbucketIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'atlassian_bitbucket.audit' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/atlassian_confluence_integration.ts b/src/commands/org_data/integrations/atlassian_confluence_integration.ts index 88d46323..5a65e0c3 100644 --- a/src/commands/org_data/integrations/atlassian_confluence_integration.ts +++ b/src/commands/org_data/integrations/atlassian_confluence_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic atlassian_confluence integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -206,11 +211,12 @@ export class AtlassianConfluenceIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee, org)); + documents.push(this.createAuditDocument(employee, org, centralAgent)); } } @@ -218,7 +224,11 @@ export class AtlassianConfluenceIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee, org: Organization): IntegrationDocument { + private createAuditDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -264,6 +274,7 @@ export class AtlassianConfluenceIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'atlassian_confluence.audit' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/atlassian_jira_integration.ts b/src/commands/org_data/integrations/atlassian_jira_integration.ts index a977c0bb..21ccb013 100644 --- a/src/commands/org_data/integrations/atlassian_jira_integration.ts +++ b/src/commands/org_data/integrations/atlassian_jira_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic atlassian_jira integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -182,11 +187,12 @@ export class AtlassianJiraIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee, org)); + documents.push(this.createAuditDocument(employee, org, centralAgent)); } } @@ -194,7 +200,11 @@ export class AtlassianJiraIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee, org: Organization): IntegrationDocument { + private createAuditDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -229,6 +239,7 @@ export class AtlassianJiraIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'atlassian_jira.audit' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/auth0_integration.ts b/src/commands/org_data/integrations/auth0_integration.ts index 33b5ff40..20951ecc 100644 --- a/src/commands/org_data/integrations/auth0_integration.ts +++ b/src/commands/org_data/integrations/auth0_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic auth0 integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -147,11 +152,12 @@ export class Auth0Integration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createLogDocument(employee, org)); + documents.push(this.createLogDocument(employee, org, centralAgent)); } } @@ -159,7 +165,11 @@ export class Auth0Integration extends BaseIntegration { return documentsMap; } - private createLogDocument(employee: Employee, org: Organization): IntegrationDocument { + private createLogDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const eventType = faker.helpers.weightedArrayElement( AUTH0_EVENT_TYPES.map((e) => ({ value: e, weight: e.weight })), ); @@ -196,6 +206,7 @@ export class Auth0Integration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, json: { data: rawAuth0Data }, data_stream: { namespace: 'default', type: 'logs', dataset: 'auth0.logs' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/authentik_integration.ts b/src/commands/org_data/integrations/authentik_integration.ts index d3591132..3bb9f910 100644 --- a/src/commands/org_data/integrations/authentik_integration.ts +++ b/src/commands/org_data/integrations/authentik_integration.ts @@ -5,7 +5,12 @@ * authentik.user, or authentik.group respectively. */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -79,6 +84,7 @@ export class AuthentikIntegration extends BaseIntegration { const eventDocs: IntegrationDocument[] = []; const userDocs: IntegrationDocument[] = []; const groupDocs: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); // Build stable group pk map (department -> uuid) const departmentToGroupPk = new Map(); @@ -94,22 +100,24 @@ export class AuthentikIntegration extends BaseIntegration { // One user entity document per employee for (const employee of org.employees) { const groupPks = [getGroupPk(employee.department)]; - userDocs.push(this.createUserDocument(employee, groupPks)); + userDocs.push(this.createUserDocument(employee, groupPks, centralAgent)); } // One group document per department + AllUsers const departmentGroups = [...new Set(org.employees.map((e) => e.department))]; for (const dept of departmentGroups) { const members = org.employees.filter((e) => e.department === dept); - groupDocs.push(this.createGroupDocument(dept, members, getGroupPk(dept))); + groupDocs.push(this.createGroupDocument(dept, members, getGroupPk(dept), centralAgent)); } - groupDocs.push(this.createGroupDocument('AllUsers', org.employees, getGroupPk('AllUsers'))); + groupDocs.push( + this.createGroupDocument('AllUsers', org.employees, getGroupPk('AllUsers'), centralAgent), + ); // 2-4 event documents per employee for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < eventCount; i++) { - eventDocs.push(this.createEventDocument(employee, org)); + eventDocs.push(this.createEventDocument(employee, org, centralAgent)); } } @@ -124,7 +132,11 @@ export class AuthentikIntegration extends BaseIntegration { return String(stableHash(employee.userName) % 100000); } - private createUserDocument(employee: Employee, groupPks: string[]): IntegrationDocument { + private createUserDocument( + employee: Employee, + groupPks: string[], + centralAgent: AgentData, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(24); const pk = this.getStableUserPk(employee); const uid = faker.string.hexadecimal({ length: 64, prefix: '' }); @@ -151,12 +163,18 @@ export class AuthentikIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawUser), data_stream: { namespace: 'default', type: 'logs', dataset: 'authentik.user' }, } as IntegrationDocument; } - private createGroupDocument(name: string, members: Employee[], pk: string): IntegrationDocument { + private createGroupDocument( + name: string, + members: Employee[], + pk: string, + centralAgent: AgentData, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(24); const numPk = faker.number.int({ min: 50000, max: 59999 }); @@ -176,12 +194,17 @@ export class AuthentikIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawGroup), data_stream: { namespace: 'default', type: 'logs', dataset: 'authentik.group' }, } as IntegrationDocument; } - private createEventDocument(employee: Employee, org: Organization): IntegrationDocument { + private createEventDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const eventType = faker.helpers.weightedArrayElement( EVENT_ACTIONS.map((e) => ({ value: e, weight: e.weight })), ); @@ -217,6 +240,7 @@ export class AuthentikIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'authentik.event' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/azure_integration.ts b/src/commands/org_data/integrations/azure_integration.ts index bdeff6ff..30d63889 100644 --- a/src/commands/org_data/integrations/azure_integration.ts +++ b/src/commands/org_data/integrations/azure_integration.ts @@ -311,46 +311,54 @@ export class AzureIntegration extends BaseIntegration { const subscriptionId = faker.string.uuid().toUpperCase(); const resourceGroup = `${org.name.toUpperCase().replace(/\s+/g, '-')}-RG`; const cloudEmployees = org.employees.filter((e) => e.hasAwsAccess); + const centralAgent = this.buildCentralAgent(org); documentsMap.set( 'logs-azure.activitylogs-default', - this.generateActivityLogs(cloudEmployees, org, tenantId, subscriptionId, resourceGroup), + this.generateActivityLogs( + cloudEmployees, + org, + tenantId, + subscriptionId, + resourceGroup, + centralAgent, + ), ); documentsMap.set( 'logs-azure.auditlogs-default', - this.generateAuditLogs(org.employees, org, tenantId), + this.generateAuditLogs(org.employees, org, tenantId, centralAgent), ); documentsMap.set( 'logs-azure.signinlogs-default', - this.generateSignInLogs(org.employees, tenantId), + this.generateSignInLogs(org.employees, tenantId, centralAgent), ); documentsMap.set( 'logs-azure.identity_protection-default', - this.generateIdentityProtectionLogs(org.employees, tenantId), + this.generateIdentityProtectionLogs(org.employees, tenantId, centralAgent), ); documentsMap.set( 'logs-azure.provisioning-default', - this.generateProvisioningLogs(org.employees, org, tenantId), + this.generateProvisioningLogs(org.employees, org, tenantId, centralAgent), ); documentsMap.set( 'logs-azure.graphactivitylogs-default', - this.generateGraphActivityLogs(tenantId), + this.generateGraphActivityLogs(tenantId, centralAgent), ); documentsMap.set( 'logs-azure.firewall_logs-default', - this.generateFirewallLogs(org, subscriptionId, resourceGroup), + this.generateFirewallLogs(org, subscriptionId, resourceGroup, centralAgent), ); documentsMap.set( 'logs-azure.platformlogs-default', - this.generatePlatformLogs(subscriptionId, resourceGroup), + this.generatePlatformLogs(subscriptionId, resourceGroup, centralAgent), ); documentsMap.set( 'logs-azure.application_gateway-default', - this.generateApplicationGatewayLogs(subscriptionId, resourceGroup), + this.generateApplicationGatewayLogs(subscriptionId, resourceGroup, centralAgent), ); documentsMap.set( 'logs-azure.springcloudlogs-default', - this.generateSpringCloudLogs(subscriptionId), + this.generateSpringCloudLogs(subscriptionId, centralAgent), ); return documentsMap; @@ -362,13 +370,22 @@ export class AzureIntegration extends BaseIntegration { tenantId: string, subscriptionId: string, resourceGroup: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; for (const employee of employees) { const count = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < count; i++) { - docs.push(this.createActivityLogDoc(employee, tenantId, subscriptionId, resourceGroup)); + docs.push( + this.createActivityLogDoc( + employee, + tenantId, + subscriptionId, + resourceGroup, + centralAgent, + ), + ); } } @@ -380,6 +397,7 @@ export class AzureIntegration extends BaseIntegration { tenantId: string, subscriptionId: string, resourceGroup: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const resourceDef = faker.helpers.weightedArrayElement( @@ -432,6 +450,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.activitylogs', namespace: 'default', type: 'logs' }, } as IntegrationDocument; @@ -441,19 +460,24 @@ export class AzureIntegration extends BaseIntegration { employees: Employee[], _org: Organization, tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; const auditCount = Math.max(5, Math.ceil(employees.length * 0.3)); for (let i = 0; i < auditCount; i++) { const employee = faker.helpers.arrayElement(employees); - docs.push(this.createAuditLogDoc(employee, tenantId)); + docs.push(this.createAuditLogDoc(employee, tenantId, centralAgent)); } return docs; } - private createAuditLogDoc(employee: Employee, tenantId: string): IntegrationDocument { + private createAuditLogDoc( + employee: Employee, + tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const activity = faker.helpers.weightedArrayElement( AUDIT_LOG_ACTIVITIES.map((a) => ({ value: a, weight: a.weight })), @@ -513,25 +537,34 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.auditlogs', namespace: 'default', type: 'logs' }, } as IntegrationDocument; } - private generateSignInLogs(employees: Employee[], tenantId: string): IntegrationDocument[] { + private generateSignInLogs( + employees: Employee[], + tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; for (const employee of employees) { const count = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < count; i++) { - docs.push(this.createSignInLogDoc(employee, tenantId)); + docs.push(this.createSignInLogDoc(employee, tenantId, centralAgent)); } } return docs; } - private createSignInLogDoc(employee: Employee, tenantId: string): IntegrationDocument { + private createSignInLogDoc( + employee: Employee, + tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const app = faker.helpers.arrayElement(SIGNIN_APPS); const correlationId = faker.string.uuid(); @@ -620,6 +653,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.signinlogs', namespace: 'default', type: 'logs' }, } as IntegrationDocument; @@ -628,19 +662,24 @@ export class AzureIntegration extends BaseIntegration { private generateIdentityProtectionLogs( employees: Employee[], tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; const riskyCount = Math.max(2, Math.ceil(employees.length * 0.1)); const riskyEmployees = faker.helpers.arrayElements(employees, riskyCount); for (const employee of riskyEmployees) { - docs.push(this.createIdentityProtectionDoc(employee, tenantId)); + docs.push(this.createIdentityProtectionDoc(employee, tenantId, centralAgent)); } return docs; } - private createIdentityProtectionDoc(employee: Employee, tenantId: string): IntegrationDocument { + private createIdentityProtectionDoc( + employee: Employee, + tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const riskEventType = faker.helpers.arrayElement(RISK_EVENT_TYPES); const riskLevel = faker.helpers.weightedArrayElement([ @@ -704,6 +743,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.identity_protection', @@ -717,13 +757,14 @@ export class AzureIntegration extends BaseIntegration { employees: Employee[], org: Organization, tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; const provisionCount = Math.max(3, Math.ceil(employees.length * 0.15)); const selectedEmployees = faker.helpers.arrayElements(employees, provisionCount); for (const employee of selectedEmployees) { - docs.push(this.createProvisioningDoc(employee, org, tenantId)); + docs.push(this.createProvisioningDoc(employee, org, tenantId, centralAgent)); } return docs; @@ -733,6 +774,7 @@ export class AzureIntegration extends BaseIntegration { employee: Employee, _org: Organization, tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const targetApp = faker.helpers.arrayElement(PROVISIONING_TARGET_APPS); @@ -827,23 +869,30 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.provisioning', namespace: 'default', type: 'logs' }, } as IntegrationDocument; } - private generateGraphActivityLogs(tenantId: string): IntegrationDocument[] { + private generateGraphActivityLogs( + tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; const count = faker.number.int({ min: 5, max: 15 }); for (let i = 0; i < count; i++) { - docs.push(this.createGraphActivityLogDoc(tenantId)); + docs.push(this.createGraphActivityLogDoc(tenantId, centralAgent)); } return docs; } - private createGraphActivityLogDoc(tenantId: string): IntegrationDocument { + private createGraphActivityLogDoc( + tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const endpoint = faker.helpers.arrayElement(GRAPH_API_ENDPOINTS); const correlationId = faker.string.uuid(); @@ -861,6 +910,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, azure: { correlation_id: correlationId, graphactivitylogs: { @@ -941,6 +991,7 @@ export class AzureIntegration extends BaseIntegration { org: Organization, subscriptionId: string, resourceGroup: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; const count = Math.max(5, Math.ceil(org.employees.length * 0.5)); @@ -948,7 +999,15 @@ export class AzureIntegration extends BaseIntegration { for (let i = 0; i < count; i++) { const device = this.pickRandomDevice(org); - docs.push(this.createFirewallLogDoc(subscriptionId, resourceGroup, firewallName, device)); + docs.push( + this.createFirewallLogDoc( + subscriptionId, + resourceGroup, + firewallName, + device, + centralAgent, + ), + ); } return docs; @@ -959,6 +1018,7 @@ export class AzureIntegration extends BaseIntegration { resourceGroup: string, firewallName: string, device: Device | null, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const ruleCategory = faker.helpers.arrayElement(FIREWALL_RULE_CATEGORIES); @@ -1000,6 +1060,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.firewall_logs', namespace: 'default', type: 'logs' }, } as IntegrationDocument; @@ -1008,18 +1069,23 @@ export class AzureIntegration extends BaseIntegration { private generatePlatformLogs( subscriptionId: string, resourceGroup: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; const count = faker.number.int({ min: 5, max: 12 }); for (let i = 0; i < count; i++) { - docs.push(this.createPlatformLogDoc(subscriptionId, resourceGroup)); + docs.push(this.createPlatformLogDoc(subscriptionId, resourceGroup, centralAgent)); } return docs; } - private createPlatformLogDoc(subscriptionId: string, resourceGroup: string): IntegrationDocument { + private createPlatformLogDoc( + subscriptionId: string, + resourceGroup: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const service = faker.helpers.arrayElement(AZURE_PLATFORM_SERVICES); const operation = faker.helpers.arrayElement(service.operations); @@ -1047,6 +1113,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.platformlogs', namespace: 'default', type: 'logs' }, } as IntegrationDocument; @@ -1055,13 +1122,21 @@ export class AzureIntegration extends BaseIntegration { private generateApplicationGatewayLogs( subscriptionId: string, resourceGroup: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; const count = faker.number.int({ min: 5, max: 15 }); const gatewayName = 'Application-Gateway-01'; for (let i = 0; i < count; i++) { - docs.push(this.createApplicationGatewayLogDoc(subscriptionId, resourceGroup, gatewayName)); + docs.push( + this.createApplicationGatewayLogDoc( + subscriptionId, + resourceGroup, + gatewayName, + centralAgent, + ), + ); } return docs; @@ -1071,6 +1146,7 @@ export class AzureIntegration extends BaseIntegration { subscriptionId: string, resourceGroup: string, gatewayName: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const sourceIp = faker.internet.ipv4(); @@ -1132,6 +1208,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.application_gateway', @@ -1141,14 +1218,19 @@ export class AzureIntegration extends BaseIntegration { } as IntegrationDocument; } - private generateSpringCloudLogs(subscriptionId: string): IntegrationDocument[] { + private generateSpringCloudLogs( + subscriptionId: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; const count = faker.number.int({ min: 5, max: 12 }); const serviceName = 'springcloud01'; const resourceGroup = 'SPRINGAPPS-RG'; for (let i = 0; i < count; i++) { - docs.push(this.createSpringCloudLogDoc(subscriptionId, resourceGroup, serviceName)); + docs.push( + this.createSpringCloudLogDoc(subscriptionId, resourceGroup, serviceName, centralAgent), + ); } return docs; @@ -1158,6 +1240,7 @@ export class AzureIntegration extends BaseIntegration { subscriptionId: string, resourceGroup: string, serviceName: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const appName = faker.helpers.arrayElement(SPRING_CLOUD_APPS); @@ -1198,6 +1281,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.springcloudlogs', diff --git a/src/commands/org_data/integrations/base_integration.ts b/src/commands/org_data/integrations/base_integration.ts index 8112d629..365b727f 100644 --- a/src/commands/org_data/integrations/base_integration.ts +++ b/src/commands/org_data/integrations/base_integration.ts @@ -3,12 +3,21 @@ * Abstract class defining the interface for all security integrations */ -import { Organization, CorrelationMap } from '../types'; +import { Organization, CorrelationMap, Device, Host } from '../types'; import { installPackage } from '../../../utils/kibana_api'; import { ingest } from '../../utils/indices'; import cliProgress from 'cli-progress'; import { chunk } from 'lodash-es'; +export const ELASTIC_AGENT_VERSION = '8.17.4'; + +export interface AgentData { + id: string; + name: string; + type: string; + version: string; +} + /** * Document type for integration documents * Using a generic record type to allow any document structure @@ -186,6 +195,47 @@ export abstract class BaseIntegration { const offsetMs = Math.random() * maxOffsetHours * 60 * 60 * 1000; return new Date(now.getTime() - offsetMs).toISOString(); } + + /** + * Build agent metadata for a centralized cloud/SaaS integration collector. + * All centralized integrations share the same agent identity. + */ + protected buildCentralAgent(org: Organization): AgentData { + return { + id: org.centralAgent.id, + name: org.centralAgent.name, + type: 'filebeat', + version: ELASTIC_AGENT_VERSION, + }; + } + + /** + * Build agent metadata for a local workstation agent (one per device). + */ + protected buildLocalAgent( + device: Device, + hostname: string, + agentType: string = 'filebeat', + ): AgentData { + return { + id: device.elasticAgentId, + name: hostname, + type: agentType, + version: ELASTIC_AGENT_VERSION, + }; + } + + /** + * Build agent metadata for a server host agent (one per server). + */ + protected buildServerAgent(host: Host): AgentData { + return { + id: host.elasticAgentId, + name: host.name, + type: 'filebeat', + version: ELASTIC_AGENT_VERSION, + }; + } } /** diff --git a/src/commands/org_data/integrations/beyondinsight_integration.ts b/src/commands/org_data/integrations/beyondinsight_integration.ts index 2c0b3db5..2e11b69a 100644 --- a/src/commands/org_data/integrations/beyondinsight_integration.ts +++ b/src/commands/org_data/integrations/beyondinsight_integration.ts @@ -5,7 +5,12 @@ * Based on the Elastic beyondinsight_password_safe integration package. */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -97,6 +102,7 @@ export class BeyondInsightIntegration extends BaseIntegration { _correlationMap: CorrelationMap, ): Map { const documentsMap = new Map(); + const centralAgent = this.buildCentralAgent(org); const userAuditDocs: IntegrationDocument[] = []; const sessionDocs: IntegrationDocument[] = []; @@ -105,20 +111,20 @@ export class BeyondInsightIntegration extends BaseIntegration { for (const employee of org.employees) { const auditCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < auditCount; i++) { - userAuditDocs.push(this.createUserAuditDocument(employee)); + userAuditDocs.push(this.createUserAuditDocument(employee, centralAgent)); } if (faker.datatype.boolean(0.3)) { - sessionDocs.push(this.createSessionDocument(employee)); + sessionDocs.push(this.createSessionDocument(employee, centralAgent)); } } const accountCount = faker.number.int({ min: 5, max: 15 }); for (let i = 0; i < accountCount; i++) { - managedAccountDocs.push(this.createManagedAccountDocument(i)); + managedAccountDocs.push(this.createManagedAccountDocument(i, centralAgent)); } - const systemDocs = this.createManagedSystemDocuments(); - const assetDocs = this.createAssetDocuments(); + const systemDocs = this.createManagedSystemDocuments(centralAgent); + const assetDocs = this.createAssetDocuments(centralAgent); documentsMap.set(this.dataStreams[0].index, userAuditDocs); documentsMap.set(this.dataStreams[1].index, sessionDocs); @@ -129,7 +135,10 @@ export class BeyondInsightIntegration extends BaseIntegration { return documentsMap; } - private createUserAuditDocument(employee: Employee): IntegrationDocument { + private createUserAuditDocument( + employee: Employee, + centralAgent: AgentData, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTION_TYPES.map((a) => ({ value: a, weight: a.weight })), ); @@ -151,6 +160,7 @@ export class BeyondInsightIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, event: { original: JSON.stringify(rawEvent) }, data_stream: { dataset: 'beyondinsight_password_safe.useraudit', @@ -160,7 +170,7 @@ export class BeyondInsightIntegration extends BaseIntegration { } as IntegrationDocument; } - private createSessionDocument(employee: Employee): IntegrationDocument { + private createSessionDocument(employee: Employee, centralAgent: AgentData): IntegrationDocument { const startTime = this.getRandomTimestamp(72); const durationSec = faker.number.int({ min: 300, max: 14400 }); const endTime = new Date(new Date(startTime).getTime() + durationSec * 1000).toISOString(); @@ -191,6 +201,7 @@ export class BeyondInsightIntegration extends BaseIntegration { return { '@timestamp': endTime, + agent: centralAgent, event: { original: JSON.stringify(rawEvent) }, data_stream: { dataset: 'beyondinsight_password_safe.session', @@ -200,7 +211,7 @@ export class BeyondInsightIntegration extends BaseIntegration { } as IntegrationDocument; } - private createManagedSystemDocuments(): IntegrationDocument[] { + private createManagedSystemDocuments(centralAgent: AgentData): IntegrationDocument[] { return MANAGED_SYSTEM_NAMES.map((name, idx) => { const systemId = idx + 1; const ip = faker.internet.ipv4(); @@ -223,6 +234,7 @@ export class BeyondInsightIntegration extends BaseIntegration { return { '@timestamp': this.getRandomTimestamp(168), + agent: centralAgent, event: { original: JSON.stringify(rawEvent) }, data_stream: { dataset: 'beyondinsight_password_safe.managedsystem', @@ -233,7 +245,7 @@ export class BeyondInsightIntegration extends BaseIntegration { }); } - private createManagedAccountDocument(idx: number): IntegrationDocument { + private createManagedAccountDocument(idx: number, centralAgent: AgentData): IntegrationDocument { const accountName = faker.helpers.arrayElement(MANAGED_ACCOUNT_NAMES); const systemName = faker.helpers.arrayElement(MANAGED_SYSTEM_NAMES); const accountId = idx + 1; @@ -270,6 +282,7 @@ export class BeyondInsightIntegration extends BaseIntegration { return { '@timestamp': lastChange, + agent: centralAgent, event: { original: JSON.stringify(rawEvent) }, data_stream: { dataset: 'beyondinsight_password_safe.managedaccount', @@ -279,7 +292,7 @@ export class BeyondInsightIntegration extends BaseIntegration { } as IntegrationDocument; } - private createAssetDocuments(): IntegrationDocument[] { + private createAssetDocuments(centralAgent: AgentData): IntegrationDocument[] { return MANAGED_SYSTEM_NAMES.map((name, idx) => { const assetId = idx + 1; const ip = faker.internet.ipv4(); @@ -305,6 +318,7 @@ export class BeyondInsightIntegration extends BaseIntegration { return { '@timestamp': lastUpdateDate, + agent: centralAgent, event: { original: JSON.stringify(rawEvent) }, data_stream: { dataset: 'beyondinsight_password_safe.asset', diff --git a/src/commands/org_data/integrations/bitwarden_integration.ts b/src/commands/org_data/integrations/bitwarden_integration.ts index c0d2de02..1c1f68d8 100644 --- a/src/commands/org_data/integrations/bitwarden_integration.ts +++ b/src/commands/org_data/integrations/bitwarden_integration.ts @@ -5,7 +5,12 @@ * Produces raw Bitwarden API JSON in message for ingest pipeline processing */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -102,6 +107,7 @@ export class BitwardenIntegration extends BaseIntegration { _correlationMap: CorrelationMap, ): Map { const documentsMap = new Map(); + const centralAgent = this.buildCentralAgent(org); const eventDocs: IntegrationDocument[] = []; const memberDocs: IntegrationDocument[] = []; @@ -109,14 +115,14 @@ export class BitwardenIntegration extends BaseIntegration { for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - eventDocs.push(this.createEventDocument(employee)); + eventDocs.push(this.createEventDocument(employee, centralAgent)); } - memberDocs.push(this.createMemberDocument(employee)); + memberDocs.push(this.createMemberDocument(employee, centralAgent)); } - const groupDocs = this.createGroupDocuments(); - const policyDocs = this.createPolicyDocuments(); - const collectionDocs = this.createCollectionDocuments(); + const groupDocs = this.createGroupDocuments(centralAgent); + const policyDocs = this.createPolicyDocuments(centralAgent); + const collectionDocs = this.createCollectionDocuments(centralAgent); documentsMap.set(this.dataStreams[0].index, eventDocs); documentsMap.set(this.dataStreams[1].index, memberDocs); @@ -127,7 +133,7 @@ export class BitwardenIntegration extends BaseIntegration { return documentsMap; } - private createEventDocument(employee: Employee): IntegrationDocument { + private createEventDocument(employee: Employee, centralAgent: AgentData): IntegrationDocument { const eventType = faker.helpers.weightedArrayElement( EVENT_TYPES.map((e) => ({ value: e, weight: e.weight })), ); @@ -155,6 +161,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { dataset: 'bitwarden.event', @@ -164,7 +171,7 @@ export class BitwardenIntegration extends BaseIntegration { } as IntegrationDocument; } - private createMemberDocument(employee: Employee): IntegrationDocument { + private createMemberDocument(employee: Employee, centralAgent: AgentData): IntegrationDocument { const memberId = getStableMemberId(employee); const userId = getStableUserId(employee); const status = faker.helpers.arrayElement(MEMBER_STATUSES); @@ -188,6 +195,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawMember), data_stream: { dataset: 'bitwarden.member', @@ -197,7 +205,7 @@ export class BitwardenIntegration extends BaseIntegration { } as IntegrationDocument; } - private createGroupDocuments(): IntegrationDocument[] { + private createGroupDocuments(centralAgent: AgentData): IntegrationDocument[] { return GROUP_NAMES.map((name) => { const groupId = faker.string.uuid(); const collectionCount = faker.number.int({ min: 1, max: 3 }); @@ -218,6 +226,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawGroup), data_stream: { dataset: 'bitwarden.group', @@ -228,7 +237,7 @@ export class BitwardenIntegration extends BaseIntegration { }); } - private createPolicyDocuments(): IntegrationDocument[] { + private createPolicyDocuments(centralAgent: AgentData): IntegrationDocument[] { return POLICY_TYPES.map((policy) => { const policyId = faker.string.uuid(); const timestamp = this.getRandomTimestamp(168); @@ -255,6 +264,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawPolicy), data_stream: { dataset: 'bitwarden.policy', @@ -265,7 +275,7 @@ export class BitwardenIntegration extends BaseIntegration { }); } - private createCollectionDocuments(): IntegrationDocument[] { + private createCollectionDocuments(centralAgent: AgentData): IntegrationDocument[] { const collectionNames = [ 'Engineering Secrets', 'Production Credentials', @@ -287,6 +297,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawCollection), data_stream: { dataset: 'bitwarden.collection', diff --git a/src/commands/org_data/integrations/box_integration.ts b/src/commands/org_data/integrations/box_integration.ts index ce518ef6..1b351087 100644 --- a/src/commands/org_data/integrations/box_integration.ts +++ b/src/commands/org_data/integrations/box_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic box_events integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -151,11 +156,12 @@ export class BoxIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createEventDocument(org, employee)); + documents.push(this.createEventDocument(org, employee, centralAgent)); } } @@ -163,7 +169,11 @@ export class BoxIntegration extends BaseIntegration { return documentsMap; } - private createEventDocument(org: Organization, employee: Employee): IntegrationDocument { + private createEventDocument( + org: Organization, + employee: Employee, + centralAgent: AgentData, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( EVENT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -313,9 +323,9 @@ export class BoxIntegration extends BaseIntegration { }; } - // Output: raw format with message = JSON.stringify(rawBoxEvent) const doc: IntegrationDocument = { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawBoxEvent), data_stream: { dataset: 'box_events.events', diff --git a/src/commands/org_data/integrations/canva_integration.ts b/src/commands/org_data/integrations/canva_integration.ts index 6f31a43f..b37aa81b 100644 --- a/src/commands/org_data/integrations/canva_integration.ts +++ b/src/commands/org_data/integrations/canva_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic canva integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -135,11 +140,12 @@ export class CanvaIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee, org)); + documents.push(this.createAuditDocument(employee, org, centralAgent)); } } @@ -147,7 +153,11 @@ export class CanvaIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee, org: Organization): IntegrationDocument { + private createAuditDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -222,6 +232,7 @@ export class CanvaIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAudit), data_stream: { dataset: 'canva.audit', diff --git a/src/commands/org_data/integrations/cisco_duo_integration.ts b/src/commands/org_data/integrations/cisco_duo_integration.ts index 54662143..d01610dc 100644 --- a/src/commands/org_data/integrations/cisco_duo_integration.ts +++ b/src/commands/org_data/integrations/cisco_duo_integration.ts @@ -84,7 +84,7 @@ export class CiscoDuoIntegration extends BaseIntegration { return documentsMap; } - private generateAuthDocument(employee: Employee, _org: Organization): IntegrationDocument { + private generateAuthDocument(employee: Employee, org: Organization): IntegrationDocument { const factor = faker.helpers.weightedArrayElement( DUO_FACTORS.map((f) => ({ value: f.name, weight: f.weight })), ); @@ -150,6 +150,7 @@ export class CiscoDuoIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'cisco_duo.auth' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/cloud_asset_integration.ts b/src/commands/org_data/integrations/cloud_asset_integration.ts index 41c27701..70eae6bf 100644 --- a/src/commands/org_data/integrations/cloud_asset_integration.ts +++ b/src/commands/org_data/integrations/cloud_asset_integration.ts @@ -72,12 +72,13 @@ export class CloudAssetIntegration extends BaseIntegration { */ private createCloudResourceDocument( resource: CloudResource, - _org: Organization, + org: Organization, ): CloudAssetDocument { const timestamp = this.getRandomTimestamp(48); const baseDoc: CloudAssetDocument = { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), event: { kind: 'asset', }, @@ -139,6 +140,7 @@ export class CloudAssetIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), event: { kind: 'asset', }, @@ -206,6 +208,7 @@ export class CloudAssetIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), event: { kind: 'asset', }, diff --git a/src/commands/org_data/integrations/cloudflare_logpush_integration.ts b/src/commands/org_data/integrations/cloudflare_logpush_integration.ts index 677c198a..bd9b1bba 100644 --- a/src/commands/org_data/integrations/cloudflare_logpush_integration.ts +++ b/src/commands/org_data/integrations/cloudflare_logpush_integration.ts @@ -36,6 +36,7 @@ export class CloudflareLogpushIntegration extends BaseIntegration { const documentsMap = new Map(); const httpDocs: IntegrationDocument[] = []; const fwDocs: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); const httpCount = this.getHttpRequestCount(org.size); const fwCount = Math.floor(httpCount * 0.1); // ~10% of traffic triggers firewall @@ -44,13 +45,13 @@ export class CloudflareLogpushIntegration extends BaseIntegration { for (let i = 0; i < httpCount; i++) { const isAttack = faker.datatype.boolean(0.05); // 5% attack traffic const zone = faker.helpers.arrayElement(org.cloudflareZones); - httpDocs.push(this.generateHttpDocument(zone, isAttack)); + httpDocs.push(this.generateHttpDocument(zone, isAttack, centralAgent)); } // Generate firewall event logs for (let i = 0; i < fwCount; i++) { const zone = faker.helpers.arrayElement(org.cloudflareZones); - fwDocs.push(this.generateFirewallDocument(zone)); + fwDocs.push(this.generateFirewallDocument(zone, centralAgent)); } documentsMap.set('logs-cloudflare_logpush.http_request-default', httpDocs); @@ -58,7 +59,11 @@ export class CloudflareLogpushIntegration extends BaseIntegration { return documentsMap; } - private generateHttpDocument(zone: CloudflareZone, isAttack: boolean): IntegrationDocument { + private generateHttpDocument( + zone: CloudflareZone, + isAttack: boolean, + centralAgent: ReturnType, + ): IntegrationDocument { const subdomain = faker.helpers.arrayElement(zone.subdomains); const host = `${subdomain}.${zone.name}`; const method = faker.helpers.weightedArrayElement( @@ -122,6 +127,7 @@ export class CloudflareLogpushIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', @@ -131,7 +137,10 @@ export class CloudflareLogpushIntegration extends BaseIntegration { } as IntegrationDocument; } - private generateFirewallDocument(zone: CloudflareZone): IntegrationDocument { + private generateFirewallDocument( + zone: CloudflareZone, + centralAgent: ReturnType, + ): IntegrationDocument { const rule = faker.helpers.arrayElement(CLOUDFLARE_WAF_RULES); const action = faker.helpers.weightedArrayElement([ { value: 'block', weight: 50 }, @@ -179,6 +188,7 @@ export class CloudflareLogpushIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', diff --git a/src/commands/org_data/integrations/cloudtrail_integration.ts b/src/commands/org_data/integrations/cloudtrail_integration.ts index 8d9aeb8c..d4abbd18 100644 --- a/src/commands/org_data/integrations/cloudtrail_integration.ts +++ b/src/commands/org_data/integrations/cloudtrail_integration.ts @@ -189,6 +189,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser, employee, account!, + org, assumedRoleArn, callerIdentityTime, accessKeyId, @@ -212,6 +213,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser, employee, account!, + org, service, assumedRoleArn, apiTime, @@ -250,6 +252,7 @@ export class CloudTrailIntegration extends BaseIntegration { this.createServiceAccountApiEvent( iamUser, account!, + org, service, timestamp, accessKeyId, @@ -278,7 +281,9 @@ export class CloudTrailIntegration extends BaseIntegration { const timestamp = this.getRandomTimestamp(48); const isFailure = faker.number.float() < 0.05; // 5% failure rate - events.push(this.createConsoleLoginEvent(iamUser, employee, account!, timestamp, isFailure)); + events.push( + this.createConsoleLoginEvent(iamUser, employee, account!, org, timestamp, isFailure), + ); } return events; @@ -340,6 +345,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, } as IntegrationDocument; @@ -352,6 +358,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser: CloudIamUser, employee: Employee, account: CloudAccount, + org: Organization, assumedRoleArn: string, timestamp: string, accessKeyId: string, @@ -399,6 +406,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, } as IntegrationDocument; @@ -411,6 +419,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser: CloudIamUser, employee: Employee, account: CloudAccount, + org: Organization, service: string, assumedRoleArn: string, timestamp: string, @@ -479,6 +488,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, } as IntegrationDocument; @@ -490,6 +500,7 @@ export class CloudTrailIntegration extends BaseIntegration { private createServiceAccountApiEvent( iamUser: CloudIamUser, account: CloudAccount, + org: Organization, service: string, timestamp: string, accessKeyId: string, @@ -532,6 +543,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, } as IntegrationDocument; @@ -544,6 +556,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser: CloudIamUser, employee: Employee, account: CloudAccount, + org: Organization, timestamp: string, isFailure: boolean, ): IntegrationDocument { @@ -599,6 +612,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/crowdstrike_integration.ts b/src/commands/org_data/integrations/crowdstrike_integration.ts index 4f2b0b44..0fcad0a1 100644 --- a/src/commands/org_data/integrations/crowdstrike_integration.ts +++ b/src/commands/org_data/integrations/crowdstrike_integration.ts @@ -202,7 +202,7 @@ export class CrowdStrikeIntegration extends BaseIntegration { alertDocs.push(this.generateAlertDocument(employee, device)); } - const falconDocs = this.generateFalconDocuments(correlationMap); + const falconDocs = this.generateFalconDocuments(org, correlationMap); documentsMap.set('logs-crowdstrike.host-default', hostDocs); documentsMap.set('logs-crowdstrike.alert-default', alertDocs); @@ -259,9 +259,10 @@ export class CrowdStrikeIntegration extends BaseIntegration { product_type_desc: 'Workstation', }; + const hostname = `${employee.userName}-${device.platform}`; return { - // @timestamp is required by IntegrationDocument type; pipeline overwrites from modified_timestamp '@timestamp': modifiedTimestamp, + agent: this.buildLocalAgent(device, hostname), message: JSON.stringify(rawHost), data_stream: { namespace: 'default', type: 'logs', dataset: 'crowdstrike.host' }, } as IntegrationDocument; @@ -371,9 +372,10 @@ export class CrowdStrikeIntegration extends BaseIntegration { source_vendors: ['CrowdStrike'], }; + const hostname = `${employee.userName}-${device.platform}`; return { - // @timestamp is required by IntegrationDocument type; pipeline overwrites from timestamp '@timestamp': alertTimestamp, + agent: this.buildLocalAgent(device, hostname), message: JSON.stringify(rawAlert), data_stream: { namespace: 'default', type: 'logs', dataset: 'crowdstrike.alert' }, } as IntegrationDocument; @@ -404,7 +406,10 @@ export class CrowdStrikeIntegration extends BaseIntegration { return 'Unknown'; } - private generateFalconDocuments(correlationMap: CorrelationMap): IntegrationDocument[] { + private generateFalconDocuments( + org: Organization, + correlationMap: CorrelationMap, + ): IntegrationDocument[] { const falconDocs: IntegrationDocument[] = []; let offset = 0; @@ -415,7 +420,7 @@ export class CrowdStrikeIntegration extends BaseIntegration { for (let i = 0; i < eventCount; i++) { const eventType = faker.helpers.weightedArrayElement(FALCON_EVENT_WEIGHTS); offset++; - falconDocs.push(this.generateFalconEvent(eventType, employee, device, offset)); + falconDocs.push(this.generateFalconEvent(eventType, employee, device, org, offset)); } } @@ -426,23 +431,26 @@ export class CrowdStrikeIntegration extends BaseIntegration { eventType: FalconEventType, employee: Employee, device: Device, + org: Organization, offset: number, ): IntegrationDocument { + const localAgent = this.buildLocalAgent(device, `${employee.userName}-${device.platform}`); + const centralAgent = this.buildCentralAgent(org); switch (eventType) { case 'DetectionSummaryEvent': - return this.generateDetectionSummaryEvent(employee, device, offset); + return this.generateDetectionSummaryEvent(employee, device, offset, localAgent); case 'RemoteResponseSessionStartEvent': - return this.generateRemoteResponseStartEvent(employee, device, offset); + return this.generateRemoteResponseStartEvent(employee, device, offset, localAgent); case 'RemoteResponseSessionEndEvent': - return this.generateRemoteResponseEndEvent(employee, device, offset); + return this.generateRemoteResponseEndEvent(employee, device, offset, localAgent); case 'AuthActivityAuditEvent': - return this.generateAuthAuditEvent(employee, offset); + return this.generateAuthAuditEvent(employee, offset, centralAgent); case 'UserActivityAuditEvent': - return this.generateUserActivityAuditEvent(employee, offset); + return this.generateUserActivityAuditEvent(employee, offset, centralAgent); case 'FirewallMatchEvent': - return this.generateFirewallMatchEvent(employee, device, offset); + return this.generateFirewallMatchEvent(employee, device, offset, localAgent); case 'IncidentSummaryEvent': - return this.generateIncidentSummaryEvent(employee, device, offset); + return this.generateIncidentSummaryEvent(employee, device, offset, localAgent); } } @@ -456,6 +464,7 @@ export class CrowdStrikeIntegration extends BaseIntegration { rawEvent: Record, offset: number, timestamp: string, + agentData?: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const eventCreationTimeMs = new Date(timestamp).getTime(); const rawEnvelope = { @@ -471,6 +480,7 @@ export class CrowdStrikeIntegration extends BaseIntegration { return { '@timestamp': timestamp, + ...(agentData && { agent: agentData }), message: JSON.stringify(rawEnvelope), data_stream: { dataset: 'crowdstrike.falcon', namespace: 'default', type: 'logs' }, } as IntegrationDocument; @@ -480,6 +490,7 @@ export class CrowdStrikeIntegration extends BaseIntegration { employee: Employee, device: Device, offset: number, + agentData: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const _mitre = faker.helpers.arrayElement(MITRE_ATTACKS); const proc = faker.helpers.arrayElement(SUSPICIOUS_PROCESSES); @@ -529,13 +540,14 @@ export class CrowdStrikeIntegration extends BaseIntegration { ProcessStartTime: timestampMs, }; - return this.buildFalconDoc('DetectionSummaryEvent', rawEvent, offset, timestamp); + return this.buildFalconDoc('DetectionSummaryEvent', rawEvent, offset, timestamp, agentData); } private generateRemoteResponseStartEvent( employee: Employee, device: Device, offset: number, + agentData: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const hostname = `${employee.userName}-${device.platform}`; const timestamp = this.getRandomTimestamp(24); @@ -549,13 +561,20 @@ export class CrowdStrikeIntegration extends BaseIntegration { StartTimestamp: Math.floor(new Date(timestamp).getTime() / 1000), }; - return this.buildFalconDoc('RemoteResponseSessionStartEvent', rawEvent, offset, timestamp); + return this.buildFalconDoc( + 'RemoteResponseSessionStartEvent', + rawEvent, + offset, + timestamp, + agentData, + ); } private generateRemoteResponseEndEvent( employee: Employee, device: Device, offset: number, + agentData: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const hostname = `${employee.userName}-${device.platform}`; const timestamp = this.getRandomTimestamp(24); @@ -576,10 +595,20 @@ export class CrowdStrikeIntegration extends BaseIntegration { ), }; - return this.buildFalconDoc('RemoteResponseSessionEndEvent', rawEvent, offset, endTimestamp); + return this.buildFalconDoc( + 'RemoteResponseSessionEndEvent', + rawEvent, + offset, + endTimestamp, + agentData, + ); } - private generateAuthAuditEvent(employee: Employee, offset: number): IntegrationDocument { + private generateAuthAuditEvent( + employee: Employee, + offset: number, + agentData: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(24); const operation = faker.helpers.arrayElement(AUTH_OPERATIONS); const success = faker.datatype.boolean(0.85); @@ -596,10 +625,14 @@ export class CrowdStrikeIntegration extends BaseIntegration { ], }; - return this.buildFalconDoc('AuthActivityAuditEvent', rawEvent, offset, timestamp); + return this.buildFalconDoc('AuthActivityAuditEvent', rawEvent, offset, timestamp, agentData); } - private generateUserActivityAuditEvent(employee: Employee, offset: number): IntegrationDocument { + private generateUserActivityAuditEvent( + employee: Employee, + offset: number, + agentData: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(24); const operation = faker.helpers.arrayElement(USER_ACTIVITY_OPERATIONS); const success = faker.datatype.boolean(0.95); @@ -616,13 +649,14 @@ export class CrowdStrikeIntegration extends BaseIntegration { ], }; - return this.buildFalconDoc('UserActivityAuditEvent', rawEvent, offset, timestamp); + return this.buildFalconDoc('UserActivityAuditEvent', rawEvent, offset, timestamp, agentData); } private generateFirewallMatchEvent( employee: Employee, device: Device, offset: number, + agentData: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const hostname = `${employee.userName}-${device.platform}`; const timestamp = this.getRandomTimestamp(24); @@ -656,13 +690,14 @@ export class CrowdStrikeIntegration extends BaseIntegration { Status: ruleAction === '2' ? 'blocked' : 'allowed', }; - return this.buildFalconDoc('FirewallMatchEvent', rawEvent, offset, timestamp); + return this.buildFalconDoc('FirewallMatchEvent', rawEvent, offset, timestamp, agentData); } private generateIncidentSummaryEvent( employee: Employee, device: Device, offset: number, + agentData: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const hostname = `${employee.userName}-${device.platform}`; const timestamp = this.getRandomTimestamp(48); @@ -683,6 +718,6 @@ export class CrowdStrikeIntegration extends BaseIntegration { UTCTimestamp: timestampMs, }; - return this.buildFalconDoc('IncidentSummaryEvent', rawEvent, offset, timestamp); + return this.buildFalconDoc('IncidentSummaryEvent', rawEvent, offset, timestamp, agentData); } } diff --git a/src/commands/org_data/integrations/cyberark_pas_integration.ts b/src/commands/org_data/integrations/cyberark_pas_integration.ts index f73a069e..2eff717b 100644 --- a/src/commands/org_data/integrations/cyberark_pas_integration.ts +++ b/src/commands/org_data/integrations/cyberark_pas_integration.ts @@ -5,7 +5,12 @@ * Audit record fields use CamelCase; pipeline converts to snake_case. */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -187,11 +192,12 @@ export class CyberArkPasIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee)); + documents.push(this.createAuditDocument(employee, centralAgent)); } } @@ -199,7 +205,7 @@ export class CyberArkPasIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee): IntegrationDocument { + private createAuditDocument(employee: Employee, centralAgent: AgentData): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -290,6 +296,7 @@ export class CyberArkPasIntegration extends BaseIntegration { const doc: IntegrationDocument = { '@timestamp': timestamp, + agent: centralAgent, message, data_stream: { dataset: 'cyberarkpas.audit', diff --git a/src/commands/org_data/integrations/endpoint_integration.ts b/src/commands/org_data/integrations/endpoint_integration.ts index d5af935d..496d5084 100644 --- a/src/commands/org_data/integrations/endpoint_integration.ts +++ b/src/commands/org_data/integrations/endpoint_integration.ts @@ -3,13 +3,16 @@ * Generates endpoint process, file, network, security, alert, registry, and library events */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + ELASTIC_AGENT_VERSION, +} from './base_integration'; import { Organization, CorrelationMap, Employee, Device } from '../types'; import { faker } from '@faker-js/faker'; import { MALWARE_HASHES } from '../data/threat_intel_data'; -const ENDPOINT_AGENT_VERSION = '8.17.4'; - const PROCESS_ACTIONS: Array<{ action: string; type: string[] }> = [ { action: 'start', type: ['start'] }, { action: 'exec', type: ['start'] }, @@ -281,7 +284,7 @@ export class EndpointIntegration extends BaseIntegration { const laptops = employee.devices.filter((d) => d.type === 'laptop'); for (const device of laptops) { allLaptops.push({ employee, device }); - const agentId = device.crowdstrikeAgentId; + const agentId = device.elasticAgentId; const hostId = device.id; const platform = device.platform as string; @@ -340,7 +343,7 @@ export class EndpointIntegration extends BaseIntegration { this.generateAlertDocument( employee, device, - device.crowdstrikeAgentId, + device.elasticAgentId, device.id, device.platform as string, ), @@ -381,11 +384,12 @@ export class EndpointIntegration extends BaseIntegration { }; } - private buildAgentObject(agentId: string) { + private buildAgentObject(agentId: string, hostname: string) { return { id: agentId, + name: hostname, type: 'endpoint', - version: ENDPOINT_AGENT_VERSION, + version: ELASTIC_AGENT_VERSION, }; } @@ -408,9 +412,10 @@ export class EndpointIntegration extends BaseIntegration { employee.userName, ); + const hostname = `${employee.userName}-${device.platform}`; return { '@timestamp': timestamp, - agent: this.buildAgentObject(agentId), + agent: this.buildAgentObject(agentId, hostname), process: { Ext: { ancestry: [parentEntityId], @@ -474,9 +479,10 @@ export class EndpointIntegration extends BaseIntegration { const extension = fileName.includes('.') ? fileName.split('.').pop() : undefined; const entityId = faker.string.alphanumeric(40); + const hostname = `${employee.userName}-${device.platform}`; return { '@timestamp': timestamp, - agent: this.buildAgentObject(agentId), + agent: this.buildAgentObject(agentId, hostname), process: { Ext: { ancestry: [faker.string.alphanumeric(40)] }, name: platform === 'windows' ? 'explorer.exe' : 'bash', @@ -531,9 +537,10 @@ export class EndpointIntegration extends BaseIntegration { const entityId = faker.string.alphanumeric(40); const proc = faker.helpers.arrayElement(COMMON_PROCESSES); + const hostname = `${employee.userName}-${device.platform}`; return { '@timestamp': timestamp, - agent: this.buildAgentObject(agentId), + agent: this.buildAgentObject(agentId, hostname), process: { Ext: { ancestry: [faker.string.alphanumeric(40)] }, name: proc.name, @@ -602,9 +609,10 @@ export class EndpointIntegration extends BaseIntegration { ]); const entityId = faker.string.alphanumeric(40); + const hostname = `${employee.userName}-${device.platform}`; return { '@timestamp': timestamp, - agent: this.buildAgentObject(agentId), + agent: this.buildAgentObject(agentId, hostname), process: { Ext: { ancestry: [faker.string.alphanumeric(40)], @@ -672,12 +680,13 @@ export class EndpointIntegration extends BaseIntegration { const sha256 = faker.helpers.arrayElement(MALWARE_HASHES); const fileName = `${faker.string.alphanumeric(8)}.${faker.helpers.arrayElement(['dll', 'exe', 'ps1', 'vbs'])}`; + const hostname = `${employee.userName}-${device.platform}`; return { '@timestamp': timestamp, agent: { - ...this.buildAgentObject(agentId), + ...this.buildAgentObject(agentId, hostname), build: { - original: `version: ${ENDPOINT_AGENT_VERSION}, compiled: Mon Jan 01 00:00:00 2024, branch: main, commit: ${faker.string.hexadecimal({ length: 40, casing: 'lower', prefix: '' })}`, + original: `version: ${ELASTIC_AGENT_VERSION}, compiled: Mon Jan 01 00:00:00 2024, branch: main, commit: ${faker.string.hexadecimal({ length: 40, casing: 'lower', prefix: '' })}`, }, }, process: { @@ -769,9 +778,10 @@ export class EndpointIntegration extends BaseIntegration { const value = regPath.split('\\').pop() ?? 'Value'; const entityId = faker.string.alphanumeric(40); + const hostname = `${employee.userName}-windows`; return { '@timestamp': timestamp, - agent: this.buildAgentObject(agentId), + agent: this.buildAgentObject(agentId, hostname), registry: { hive, path: fullPath, @@ -833,9 +843,10 @@ export class EndpointIntegration extends BaseIntegration { lib = faker.helpers.arrayElement(SHARED_LIBS); } + const hostname = `${employee.userName}-${device.platform}`; return { '@timestamp': timestamp, - agent: this.buildAgentObject(agentId), + agent: this.buildAgentObject(agentId, hostname), process: { Ext: { ancestry: [faker.string.alphanumeric(40)], diff --git a/src/commands/org_data/integrations/entra_id_integration.ts b/src/commands/org_data/integrations/entra_id_integration.ts index 4db8081a..b17bbb01 100644 --- a/src/commands/org_data/integrations/entra_id_integration.ts +++ b/src/commands/org_data/integrations/entra_id_integration.ts @@ -53,10 +53,11 @@ export class EntraIdIntegration extends BaseIntegration { const entityIndex = this.dataStreams[0].index; const entityDataset = 'entityanalytics_entra_id.entity'; const timestamp = this.getTimestamp(); + const centralAgent = this.buildCentralAgent(org); const documents: IntegrationDocument[] = []; // Sync start marker - documents.push(this.createSyncMarker('started', timestamp, entityDataset)); + documents.push(this.createSyncMarker('started', timestamp, entityDataset, centralAgent)); // User documents for (const employee of org.employees) { @@ -73,7 +74,7 @@ export class EntraIdIntegration extends BaseIntegration { } // Sync end marker - documents.push(this.createSyncMarker('completed', timestamp, entityDataset)); + documents.push(this.createSyncMarker('completed', timestamp, entityDataset, centralAgent)); documentsMap.set(entityIndex, documents); return documentsMap; @@ -102,6 +103,7 @@ export class EntraIdIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), azure_ad: { userPrincipalName: employee.email, mail: employee.email, @@ -179,6 +181,7 @@ export class EntraIdIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), azure_ad: { accountEnabled: true, displayName: entraDisplayName, @@ -231,9 +234,11 @@ export class EntraIdIntegration extends BaseIntegration { action: 'started' | 'completed', timestamp: string, dataset: string, + centralAgent: ReturnType, ): EntraIdSyncMarkerDocument { return { '@timestamp': timestamp, + agent: centralAgent, event: { action, kind: 'asset', diff --git a/src/commands/org_data/integrations/forgerock_integration.ts b/src/commands/org_data/integrations/forgerock_integration.ts index a305e9f7..a11d09db 100644 --- a/src/commands/org_data/integrations/forgerock_integration.ts +++ b/src/commands/org_data/integrations/forgerock_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic forgerock integration package (AM and IDM data streams) */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -71,6 +76,7 @@ export class ForgeRockIntegration extends BaseIntegration { _correlationMap: CorrelationMap, ): Map { const documentsMap = new Map(); + const centralAgent = this.buildCentralAgent(org); const amAuthDocs: IntegrationDocument[] = []; const amAccessDocs: IntegrationDocument[] = []; const idmAuthDocs: IntegrationDocument[] = []; @@ -79,22 +85,22 @@ export class ForgeRockIntegration extends BaseIntegration { for (const employee of org.employees) { const amAuthCount = faker.number.int({ min: 1, max: 4 }); for (let i = 0; i < amAuthCount; i++) { - amAuthDocs.push(this.createAmAuthDocument(employee, org)); + amAuthDocs.push(this.createAmAuthDocument(employee, org, centralAgent)); } const amAccessCount = faker.number.int({ min: 1, max: 3 }); for (let i = 0; i < amAccessCount; i++) { - amAccessDocs.push(this.createAmAccessDocument(employee, org)); + amAccessDocs.push(this.createAmAccessDocument(employee, org, centralAgent)); } const idmAuthCount = faker.number.int({ min: 1, max: 2 }); for (let i = 0; i < idmAuthCount; i++) { - idmAuthDocs.push(this.createIdmAuthDocument(employee, org)); + idmAuthDocs.push(this.createIdmAuthDocument(employee, org, centralAgent)); } const idmAccessCount = faker.number.int({ min: 1, max: 2 }); for (let i = 0; i < idmAccessCount; i++) { - idmAccessDocs.push(this.createIdmAccessDocument(employee)); + idmAccessDocs.push(this.createIdmAccessDocument(employee, centralAgent)); } } @@ -105,7 +111,11 @@ export class ForgeRockIntegration extends BaseIntegration { return documentsMap; } - private createAmAuthDocument(employee: Employee, _org: Organization): IntegrationDocument { + private createAmAuthDocument( + employee: Employee, + _org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const evt = faker.helpers.weightedArrayElement( AM_AUTH_EVENTS.map((e) => ({ value: e, weight: e.weight })), ); @@ -141,12 +151,17 @@ export class ForgeRockIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify({ payload }), data_stream: { namespace: 'default', type: 'logs', dataset: 'forgerock.am_authentication' }, } as IntegrationDocument; } - private createAmAccessDocument(employee: Employee, org: Organization): IntegrationDocument { + private createAmAccessDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const sourceIp = faker.internet.ipv4(); const realm = faker.helpers.arrayElement(FORGEROCK_REALMS); @@ -197,12 +212,17 @@ export class ForgeRockIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify({ payload }), data_stream: { namespace: 'default', type: 'logs', dataset: 'forgerock.am_access' }, } as IntegrationDocument; } - private createIdmAuthDocument(employee: Employee, _org: Organization): IntegrationDocument { + private createIdmAuthDocument( + employee: Employee, + _org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const method = faker.helpers.arrayElement(IDM_AUTH_METHODS); const result = faker.helpers.weightedArrayElement([ @@ -228,6 +248,7 @@ export class ForgeRockIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify({ payload }), data_stream: { namespace: 'default', @@ -237,7 +258,10 @@ export class ForgeRockIntegration extends BaseIntegration { } as IntegrationDocument; } - private createIdmAccessDocument(employee: Employee): IntegrationDocument { + private createIdmAccessDocument( + employee: Employee, + centralAgent: AgentData, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const status = faker.helpers.weightedArrayElement([ { value: 'SUCCESSFUL', weight: 92 }, @@ -277,6 +301,7 @@ export class ForgeRockIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify({ payload }), data_stream: { namespace: 'default', type: 'logs', dataset: 'forgerock.idm_access' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/gcp_integration.ts b/src/commands/org_data/integrations/gcp_integration.ts index eca2ec4a..c0bd7455 100644 --- a/src/commands/org_data/integrations/gcp_integration.ts +++ b/src/commands/org_data/integrations/gcp_integration.ts @@ -8,7 +8,12 @@ * Audit pipeline drops unless json.protoPayload.@type == "type.googleapis.com/google.cloud.audit.AuditLog" */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -169,6 +174,7 @@ export class GcpIntegration extends BaseIntegration { _correlationMap: CorrelationMap, ): Map { const documentsMap = new Map(); + const centralAgent = this.buildCentralAgent(org); const auditDocs: IntegrationDocument[] = []; const firewallDocs: IntegrationDocument[] = []; @@ -178,13 +184,13 @@ export class GcpIntegration extends BaseIntegration { for (const employee of cloudEmployees) { const auditCount = faker.number.int({ min: 2, max: 6 }); for (let i = 0; i < auditCount; i++) { - auditDocs.push(this.createAuditDocument(employee, org, gcpProjectId)); + auditDocs.push(this.createAuditDocument(employee, org, gcpProjectId, centralAgent)); } } const firewallCount = Math.max(5, Math.ceil(org.employees.length * 0.5)); for (let i = 0; i < firewallCount; i++) { - firewallDocs.push(this.createFirewallDocument(org, gcpProjectId)); + firewallDocs.push(this.createFirewallDocument(org, gcpProjectId, centralAgent)); } documentsMap.set(this.dataStreams[0].index, auditDocs); @@ -196,6 +202,7 @@ export class GcpIntegration extends BaseIntegration { employee: Employee, org: Organization, projectId: string, + centralAgent: AgentData, ): IntegrationDocument { const service = faker.helpers.weightedArrayElement( GCP_SERVICES.map((s) => ({ value: s, weight: s.weight })), @@ -258,12 +265,17 @@ export class GcpIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawGcpLogEntry), data_stream: { namespace: 'default', type: 'logs', dataset: 'gcp.audit' }, } as IntegrationDocument; } - private createFirewallDocument(org: Organization, projectId: string): IntegrationDocument { + private createFirewallDocument( + org: Organization, + projectId: string, + centralAgent: AgentData, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const rule = faker.helpers.arrayElement(FIREWALL_RULES); const sourceIp = faker.internet.ipv4(); @@ -334,6 +346,7 @@ export class GcpIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawGcpFirewallEntry), data_stream: { namespace: 'default', type: 'logs', dataset: 'gcp.firewall' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/github_integration.ts b/src/commands/org_data/integrations/github_integration.ts index 1762c21f..49e9cecb 100644 --- a/src/commands/org_data/integrations/github_integration.ts +++ b/src/commands/org_data/integrations/github_integration.ts @@ -85,6 +85,7 @@ export class GitHubIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'github.audit' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/gitlab_integration.ts b/src/commands/org_data/integrations/gitlab_integration.ts index 1e7736f1..3b263f5e 100644 --- a/src/commands/org_data/integrations/gitlab_integration.ts +++ b/src/commands/org_data/integrations/gitlab_integration.ts @@ -6,7 +6,12 @@ * Pipelines: integrations/packages/gitlab/data_stream/{auth,audit,api}/elasticsearch/ingest_pipeline/default.yml */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -155,6 +160,7 @@ export class GitLabIntegration extends BaseIntegration { _correlationMap: CorrelationMap, ): Map { const documentsMap = new Map(); + const centralAgent = this.buildCentralAgent(org); const auditDocs: IntegrationDocument[] = []; const apiDocs: IntegrationDocument[] = []; const authDocs: IntegrationDocument[] = []; @@ -164,7 +170,7 @@ export class GitLabIntegration extends BaseIntegration { for (const employee of gitlabEmployees) { const auditCount = faker.number.int({ min: 1, max: 3 }); for (let i = 0; i < auditCount; i++) { - auditDocs.push(this.createAuditDocument(employee, org)); + auditDocs.push(this.createAuditDocument(employee, org, centralAgent)); } const apiCount = faker.number.int({ @@ -172,12 +178,12 @@ export class GitLabIntegration extends BaseIntegration { max: employee.department === 'Product & Engineering' ? 8 : 3, }); for (let i = 0; i < apiCount; i++) { - apiDocs.push(this.createApiDocument(employee, org)); + apiDocs.push(this.createApiDocument(employee, org, centralAgent)); } const authCount = faker.number.int({ min: 1, max: 2 }); for (let i = 0; i < authCount; i++) { - authDocs.push(this.createAuthDocument(employee, org)); + authDocs.push(this.createAuthDocument(employee, org, centralAgent)); } } @@ -187,7 +193,11 @@ export class GitLabIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee, _org: Organization): IntegrationDocument { + private createAuditDocument( + employee: Employee, + _org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const evt = faker.helpers.weightedArrayElement( AUDIT_EVENTS.map((e) => ({ value: e, weight: e.weight })), ); @@ -220,12 +230,17 @@ export class GitLabIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: 'gitlab.audit' }, } as IntegrationDocument; } - private createApiDocument(employee: Employee, org: Organization): IntegrationDocument { + private createApiDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const route = faker.helpers.arrayElement(API_ROUTES); const method = faker.helpers.weightedArrayElement([ @@ -295,12 +310,17 @@ export class GitLabIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: 'gitlab.api' }, } as IntegrationDocument; } - private createAuthDocument(employee: Employee, _org: Organization): IntegrationDocument { + private createAuthDocument( + employee: Employee, + _org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const evt = faker.helpers.weightedArrayElement( AUTH_MESSAGES.map((e) => ({ value: e, weight: e.weight })), ); @@ -340,6 +360,7 @@ export class GitLabIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: 'gitlab.auth' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/google_workspace_integration.ts b/src/commands/org_data/integrations/google_workspace_integration.ts index 829671b0..76cb3148 100644 --- a/src/commands/org_data/integrations/google_workspace_integration.ts +++ b/src/commands/org_data/integrations/google_workspace_integration.ts @@ -194,6 +194,7 @@ export class GoogleWorkspaceIntegration extends BaseIntegration { }; return { '@timestamp': ts, + agent: this.buildCentralAgent(org), message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: `google_workspace.${dataset}` }, } as IntegrationDocument; @@ -404,6 +405,7 @@ export class GoogleWorkspaceIntegration extends BaseIntegration { }; return { '@timestamp': ts, + agent: this.buildCentralAgent(org), message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: 'google_workspace.alert' }, } as IntegrationDocument; @@ -700,6 +702,7 @@ export class GoogleWorkspaceIntegration extends BaseIntegration { const raw = { row, schema }; return { '@timestamp': new Date(timestampUsec / 1000).toISOString(), + agent: this.buildCentralAgent(org), message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: 'google_workspace.gmail' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/hashicorp_vault_integration.ts b/src/commands/org_data/integrations/hashicorp_vault_integration.ts index e2f72d2b..29014095 100644 --- a/src/commands/org_data/integrations/hashicorp_vault_integration.ts +++ b/src/commands/org_data/integrations/hashicorp_vault_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic hashicorp_vault integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -100,6 +105,7 @@ export class HashiCorpVaultIntegration extends BaseIntegration { _correlationMap: CorrelationMap, ): Map { const documentsMap = new Map(); + const centralAgent = this.buildCentralAgent(org); const auditDocs: IntegrationDocument[] = []; const logDocs: IntegrationDocument[] = []; @@ -108,13 +114,13 @@ export class HashiCorpVaultIntegration extends BaseIntegration { for (const employee of vaultEmployees) { const auditCount = faker.number.int({ min: 2, max: 6 }); for (let i = 0; i < auditCount; i++) { - auditDocs.push(this.createAuditDocument(employee)); + auditDocs.push(this.createAuditDocument(employee, centralAgent)); } } const logCount = Math.max(5, Math.ceil(org.employees.length * 0.3)); for (let i = 0; i < logCount; i++) { - logDocs.push(this.createLogDocument()); + logDocs.push(this.createLogDocument(centralAgent)); } documentsMap.set(this.dataStreams[0].index, auditDocs); @@ -122,7 +128,7 @@ export class HashiCorpVaultIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee): IntegrationDocument { + private createAuditDocument(employee: Employee, centralAgent: AgentData): IntegrationDocument { const op = faker.helpers.weightedArrayElement( VAULT_OPERATIONS.map((o) => ({ value: o, weight: o.weight })), ); @@ -177,12 +183,13 @@ export class HashiCorpVaultIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawVaultAuditEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'hashicorp_vault.audit' }, } as IntegrationDocument; } - private createLogDocument(): IntegrationDocument { + private createLogDocument(centralAgent: AgentData): IntegrationDocument { const logEvt = faker.helpers.weightedArrayElement( LOG_MESSAGES.map((l) => ({ value: l, weight: l.weight })), ); @@ -190,6 +197,7 @@ export class HashiCorpVaultIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: logEvt.message, data_stream: { namespace: 'default', type: 'logs', dataset: 'hashicorp_vault.log' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/island_browser_integration.ts b/src/commands/org_data/integrations/island_browser_integration.ts index f36dbc3e..a4db32d2 100644 --- a/src/commands/org_data/integrations/island_browser_integration.ts +++ b/src/commands/org_data/integrations/island_browser_integration.ts @@ -5,7 +5,12 @@ * Based on the Elastic island_browser integration package. */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap, Device } from '../types'; import { faker } from '@faker-js/faker'; @@ -123,11 +128,14 @@ export class IslandBrowserIntegration extends BaseIntegration { const auditDocs: IntegrationDocument[] = []; const adminDocs: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { - userDocs.push(this.createUserDocument(employee, org, tenantId)); + userDocs.push(this.createUserDocument(employee, org, tenantId, centralAgent)); for (const device of employee.devices.filter((d) => d.type === 'laptop')) { - deviceDocs.push(this.createDeviceDocument(employee, device, org, tenantId)); + const hostname = `${employee.userName}-${device.platform}`; + const localAgent = this.buildLocalAgent(device, hostname); + deviceDocs.push(this.createDeviceDocument(employee, device, org, tenantId, localAgent)); } const auditCount = faker.number.int({ min: 2, max: 5 }); @@ -136,7 +144,9 @@ export class IslandBrowserIntegration extends BaseIntegration { employee.devices.filter((d) => d.type === 'laptop'), ); if (device) { - auditDocs.push(this.createAuditDocument(employee, device, org, tenantId)); + const hostname = `${employee.userName}-${device.platform}`; + const localAgent = this.buildLocalAgent(device, hostname); + auditDocs.push(this.createAuditDocument(employee, device, org, tenantId, localAgent)); } } } @@ -147,7 +157,7 @@ export class IslandBrowserIntegration extends BaseIntegration { for (const admin of admins.slice(0, 3)) { const actionCount = faker.number.int({ min: 1, max: 3 }); for (let i = 0; i < actionCount; i++) { - adminDocs.push(this.createAdminActionDocument(admin, tenantId)); + adminDocs.push(this.createAdminActionDocument(admin, tenantId, centralAgent)); } } @@ -163,6 +173,7 @@ export class IslandBrowserIntegration extends BaseIntegration { employee: Employee, org: Organization, tenantId: string, + agentData: AgentData, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(24); const groups: string[] = [employee.department]; @@ -197,6 +208,7 @@ export class IslandBrowserIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: agentData, message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: 'island_browser.user' }, } as IntegrationDocument; @@ -207,6 +219,7 @@ export class IslandBrowserIntegration extends BaseIntegration { device: Device, org: Organization, tenantId: string, + agentData: AgentData, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(24); const browserVersion = faker.helpers.arrayElement(BROWSER_VERSIONS); @@ -254,6 +267,7 @@ export class IslandBrowserIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: agentData, message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: 'island_browser.device' }, } as IntegrationDocument; @@ -264,6 +278,7 @@ export class IslandBrowserIntegration extends BaseIntegration { device: Device, org: Organization, tenantId: string, + agentData: AgentData, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const auditType = faker.helpers.weightedArrayElement( @@ -322,12 +337,17 @@ export class IslandBrowserIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: agentData, message: JSON.stringify(raw), data_stream: { namespace: 'default', type: 'logs', dataset: 'island_browser.audit' }, } as IntegrationDocument; } - private createAdminActionDocument(admin: Employee, tenantId: string): IntegrationDocument { + private createAdminActionDocument( + admin: Employee, + tenantId: string, + agentData: AgentData, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const actionDomain = faker.helpers.arrayElement(ADMIN_ACTION_DOMAINS); const entityType = faker.helpers.arrayElement(ADMIN_ENTITY_TYPES); @@ -352,6 +372,7 @@ export class IslandBrowserIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: agentData, message: JSON.stringify(raw), data_stream: { namespace: 'default', diff --git a/src/commands/org_data/integrations/jamf_pro_integration.ts b/src/commands/org_data/integrations/jamf_pro_integration.ts index f684aaad..f4bf4307 100644 --- a/src/commands/org_data/integrations/jamf_pro_integration.ts +++ b/src/commands/org_data/integrations/jamf_pro_integration.ts @@ -180,8 +180,10 @@ export class JamfProIntegration extends BaseIntegration { }, }; + const hostname = `${employee.userName}-${device.platform}`; return { '@timestamp': this.getRandomTimestamp(24), + agent: this.buildLocalAgent(device, hostname), message: inventoryPayload, data_stream: { namespace: 'default', @@ -208,9 +210,10 @@ export class JamfProIntegration extends BaseIntegration { const macColonFormat = device.macAddress.replace(/-/g, ':').toLowerCase(); const fullName = `${employee.firstName} ${employee.lastName}`; - // Pipeline expects json -> jamf_pro.events; device fields under event.computer + const hostname = `${employee.userName}-${device.platform}`; return { '@timestamp': this.getRandomTimestamp(48), + agent: this.buildLocalAgent(device, hostname), json: { event: { username: fullName, diff --git a/src/commands/org_data/integrations/jumpcloud_integration.ts b/src/commands/org_data/integrations/jumpcloud_integration.ts index 0bcbc4ea..4bc33866 100644 --- a/src/commands/org_data/integrations/jumpcloud_integration.ts +++ b/src/commands/org_data/integrations/jumpcloud_integration.ts @@ -154,11 +154,12 @@ export class JumpCloudIntegration extends BaseIntegration { const documentsMap = new Map(); const eventDocs: IntegrationDocument[] = []; const orgId = faker.string.hexadecimal({ length: 24, prefix: '' }); + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - eventDocs.push(this.createEventDocument(employee, orgId)); + eventDocs.push(this.createEventDocument(employee, orgId, centralAgent)); } } @@ -166,7 +167,11 @@ export class JumpCloudIntegration extends BaseIntegration { return documentsMap; } - private createEventDocument(employee: Employee, orgId: string): IntegrationDocument { + private createEventDocument( + employee: Employee, + orgId: string, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const eventDef = faker.helpers.weightedArrayElement( EVENT_TYPES.map((e) => ({ value: e, weight: e.weight })), ); @@ -234,6 +239,7 @@ export class JumpCloudIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawJumpCloudEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'jumpcloud.events' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/keeper_integration.ts b/src/commands/org_data/integrations/keeper_integration.ts index 7e020a26..876c3677 100644 --- a/src/commands/org_data/integrations/keeper_integration.ts +++ b/src/commands/org_data/integrations/keeper_integration.ts @@ -141,11 +141,12 @@ export class KeeperIntegration extends BaseIntegration { const documentsMap = new Map(); const auditDocs: IntegrationDocument[] = []; const enterpriseId = faker.number.int({ min: 1000, max: 9999 }); + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - auditDocs.push(this.createAuditDocument(employee, enterpriseId)); + auditDocs.push(this.createAuditDocument(employee, enterpriseId, centralAgent)); } } @@ -153,7 +154,11 @@ export class KeeperIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee, enterpriseId: number): IntegrationDocument { + private createAuditDocument( + employee: Employee, + enterpriseId: number, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const eventDef = faker.helpers.weightedArrayElement( AUDIT_EVENTS.map((e) => ({ value: e, weight: e.weight })), ); @@ -185,6 +190,7 @@ export class KeeperIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'keeper.audit' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/keycloak_integration.ts b/src/commands/org_data/integrations/keycloak_integration.ts index 237c4284..5db4f95e 100644 --- a/src/commands/org_data/integrations/keycloak_integration.ts +++ b/src/commands/org_data/integrations/keycloak_integration.ts @@ -182,11 +182,12 @@ export class KeycloakIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const logDocs: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - logDocs.push(this.createLogDocument(employee, org)); + logDocs.push(this.createLogDocument(employee, org, centralAgent)); } } @@ -194,7 +195,11 @@ export class KeycloakIntegration extends BaseIntegration { return documentsMap; } - private createLogDocument(employee: Employee, org: Organization): IntegrationDocument { + private createLogDocument( + employee: Employee, + org: Organization, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const eventDef = faker.helpers.weightedArrayElement( EVENT_TYPES.map((e) => ({ value: e, weight: e.weight })), ); @@ -220,6 +225,7 @@ export class KeycloakIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawKeycloakJson), data_stream: { namespace: 'default', type: 'logs', dataset: 'keycloak.log' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/lastpass_integration.ts b/src/commands/org_data/integrations/lastpass_integration.ts index 63b50f95..4081b810 100644 --- a/src/commands/org_data/integrations/lastpass_integration.ts +++ b/src/commands/org_data/integrations/lastpass_integration.ts @@ -162,17 +162,18 @@ export class LastPassIntegration extends BaseIntegration { const userDocs: IntegrationDocument[] = []; const eventDocs: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { - userDocs.push(this.createUserDocument(employee)); + userDocs.push(this.createUserDocument(employee, centralAgent)); const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - eventDocs.push(this.createEventReportDocument(employee)); + eventDocs.push(this.createEventReportDocument(employee, centralAgent)); } } - const sharedFolderDocs = this.createSharedFolderDocuments(org); + const sharedFolderDocs = this.createSharedFolderDocuments(org, centralAgent); documentsMap.set(this.dataStreams[0].index, userDocs); documentsMap.set(this.dataStreams[1].index, eventDocs); @@ -181,7 +182,10 @@ export class LastPassIntegration extends BaseIntegration { return documentsMap; } - private createUserDocument(employee: Employee): IntegrationDocument { + private createUserDocument( + employee: Employee, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const timestamp = this.getRandomTimestamp(24); const userId = getStableLastPassUserId(employee); const groups = faker.helpers.arrayElements(USER_GROUPS, { min: 1, max: 3 }); @@ -213,6 +217,7 @@ export class LastPassIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawUser), data_stream: { namespace: 'default', type: 'logs', dataset: 'lastpass.user' }, } as IntegrationDocument; @@ -225,7 +230,10 @@ export class LastPassIntegration extends BaseIntegration { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; } - private createEventReportDocument(employee: Employee): IntegrationDocument { + private createEventReportDocument( + employee: Employee, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const eventDef = faker.helpers.weightedArrayElement( EVENT_ACTIONS.map((e) => ({ value: e, weight: e.weight })), ); @@ -244,6 +252,7 @@ export class LastPassIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', @@ -253,7 +262,10 @@ export class LastPassIntegration extends BaseIntegration { } as IntegrationDocument; } - private createSharedFolderDocuments(org: Organization): IntegrationDocument[] { + private createSharedFolderDocuments( + org: Organization, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument[] { const docs: IntegrationDocument[] = []; for (const name of SHARED_FOLDER_NAMES) { const timestamp = this.getRandomTimestamp(168); @@ -281,6 +293,7 @@ export class LastPassIntegration extends BaseIntegration { docs.push({ '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawFolder), data_stream: { namespace: 'default', diff --git a/src/commands/org_data/integrations/lyve_cloud_integration.ts b/src/commands/org_data/integrations/lyve_cloud_integration.ts index 16a6e48b..146ce21c 100644 --- a/src/commands/org_data/integrations/lyve_cloud_integration.ts +++ b/src/commands/org_data/integrations/lyve_cloud_integration.ts @@ -54,11 +54,12 @@ export class LyveCloudIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee, org)); + documents.push(this.createAuditDocument(employee, org, centralAgent)); } } @@ -66,7 +67,11 @@ export class LyveCloudIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee, _org: Organization): IntegrationDocument { + private createAuditDocument( + employee: Employee, + _org: Organization, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const api = faker.helpers.weightedArrayElement( S3_API_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -128,6 +133,7 @@ export class LyveCloudIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAudit), log: { file: { path: '/var/log/lyve/S3/audit.json' } }, data_stream: { namespace: 'default', type: 'logs', dataset: 'lyve_cloud.audit' }, diff --git a/src/commands/org_data/integrations/mattermost_integration.ts b/src/commands/org_data/integrations/mattermost_integration.ts index af9c45c4..d29b5362 100644 --- a/src/commands/org_data/integrations/mattermost_integration.ts +++ b/src/commands/org_data/integrations/mattermost_integration.ts @@ -61,11 +61,12 @@ export class MattermostIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee, org)); + documents.push(this.createAuditDocument(employee, org, centralAgent)); } } @@ -73,7 +74,11 @@ export class MattermostIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee, org: Organization): IntegrationDocument { + private createAuditDocument( + employee: Employee, + org: Organization, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -117,6 +122,7 @@ export class MattermostIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'mattermost.audit' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/mongodb_atlas_integration.ts b/src/commands/org_data/integrations/mongodb_atlas_integration.ts index 3be23029..ab8e06ed 100644 --- a/src/commands/org_data/integrations/mongodb_atlas_integration.ts +++ b/src/commands/org_data/integrations/mongodb_atlas_integration.ts @@ -69,15 +69,16 @@ export class MongoDbAtlasIntegration extends BaseIntegration { const auditDocs: IntegrationDocument[] = []; const orgDocs: IntegrationDocument[] = []; const orgId = faker.string.hexadecimal({ length: 24, prefix: '' }); + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const auditCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < auditCount; i++) { - auditDocs.push(this.createAuditDocument(employee)); + auditDocs.push(this.createAuditDocument(employee, centralAgent)); } if (faker.datatype.boolean(0.4)) { - orgDocs.push(this.createOrgDocument(employee, org, orgId)); + orgDocs.push(this.createOrgDocument(employee, org, orgId, centralAgent)); } } @@ -86,7 +87,10 @@ export class MongoDbAtlasIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee): IntegrationDocument { + private createAuditDocument( + employee: Employee, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -118,6 +122,7 @@ export class MongoDbAtlasIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAudit), host_name: hostname, data_stream: { namespace: 'default', type: 'logs', dataset: 'mongodb_atlas.mongod_audit' }, @@ -128,6 +133,7 @@ export class MongoDbAtlasIntegration extends BaseIntegration { employee: Employee, org: Organization, orgId: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const eventType = faker.helpers.weightedArrayElement( ORG_EVENT_TYPES.map((e) => ({ value: e, weight: e.weight })), @@ -175,6 +181,7 @@ export class MongoDbAtlasIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, response, data_stream: { namespace: 'default', diff --git a/src/commands/org_data/integrations/o365_integration.ts b/src/commands/org_data/integrations/o365_integration.ts index 52637d76..5787138d 100644 --- a/src/commands/org_data/integrations/o365_integration.ts +++ b/src/commands/org_data/integrations/o365_integration.ts @@ -72,6 +72,7 @@ export class O365Integration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), o365audit, data_stream: { namespace: 'default', type: 'logs', dataset: 'o365.audit' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/okta_integration.ts b/src/commands/org_data/integrations/okta_integration.ts index af00a935..5aa56b5f 100644 --- a/src/commands/org_data/integrations/okta_integration.ts +++ b/src/commands/org_data/integrations/okta_integration.ts @@ -46,8 +46,9 @@ export class OktaIntegration extends BaseIntegration { const documents: IntegrationDocument[] = []; const entityIndex = this.dataStreams[0].index; const timestamp = this.getTimestamp(); + const centralAgent = this.buildCentralAgent(org); - documents.push(this.createSyncMarker('started', timestamp)); + documents.push(this.createSyncMarker('started', timestamp, centralAgent)); for (const employee of org.employees) { correlationMap.oktaUserIdToEmployee.set(employee.oktaUserId, employee); @@ -55,8 +56,8 @@ export class OktaIntegration extends BaseIntegration { documents.push(this.createUserDocument(employee, org, timestamp)); } - documents.push(this.createSyncMarker('completed', timestamp)); - documents.push(this.createDeviceSyncMarker('started', timestamp)); + documents.push(this.createSyncMarker('completed', timestamp, centralAgent)); + documents.push(this.createDeviceSyncMarker('started', timestamp, centralAgent)); for (const employee of org.employees) { for (const device of employee.devices) { @@ -64,7 +65,7 @@ export class OktaIntegration extends BaseIntegration { } } - documents.push(this.createDeviceSyncMarker('completed', timestamp)); + documents.push(this.createDeviceSyncMarker('completed', timestamp, centralAgent)); documentsMap.set(entityIndex, documents); return documentsMap; @@ -98,6 +99,7 @@ export class OktaIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), event: { action: 'user-discovered' }, okta: { id: employee.oktaUserId, @@ -200,6 +202,7 @@ export class OktaIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), event: { action: 'device-discovered' }, okta: { id: device.id, @@ -260,9 +263,11 @@ export class OktaIntegration extends BaseIntegration { private createSyncMarker( action: 'started' | 'completed', timestamp: string, + centralAgent: ReturnType, ): OktaSyncMarkerDocument { return { '@timestamp': timestamp, + agent: centralAgent, event: { action, kind: 'asset', @@ -285,9 +290,11 @@ export class OktaIntegration extends BaseIntegration { private createDeviceSyncMarker( action: 'started' | 'completed', timestamp: string, + centralAgent: ReturnType, ): OktaSyncMarkerDocument { return { '@timestamp': timestamp, + agent: centralAgent, event: { action, kind: 'asset', diff --git a/src/commands/org_data/integrations/okta_system_integration.ts b/src/commands/org_data/integrations/okta_system_integration.ts index 78e01a4e..fe696cc7 100644 --- a/src/commands/org_data/integrations/okta_system_integration.ts +++ b/src/commands/org_data/integrations/okta_system_integration.ts @@ -28,6 +28,35 @@ const BROWSERS = ['CHROME', 'FIREFOX', 'SAFARI', 'EDGE']; const OS_TYPES = ['Mac OS X', 'Windows 10', 'Windows 11', 'Linux']; const DEVICE_TYPES = ['Computer', 'Mobile']; +/** + * Okta admin privilege types for group.privilege events + */ +const OKTA_PRIVILEGE_TYPES = [ + 'Super admin', + 'Group admin', + 'App admin', + 'Read-only admin', + 'Help desk admin', + 'Org admin', + 'API access management admin', +]; + +/** + * Anomalous geo locations for rare region/IP detection scenarios + */ +const ANOMALOUS_LOCATIONS = [ + { country: 'North Korea', state: 'Pyongyang', city: 'Pyongyang', ip: '175.45.176.' }, + { + country: 'Russia', + state: 'Kamchatka Krai', + city: 'Petropavlovsk-Kamchatsky', + ip: '185.220.101.', + }, + { country: 'China', state: 'Xinjiang', city: 'Urumqi', ip: '103.25.61.' }, + { country: 'Nigeria', state: 'Lagos', city: 'Lagos', ip: '41.58.100.' }, + { country: 'Brazil', state: 'Roraima', city: 'Boa Vista', ip: '177.54.32.' }, +]; + /** * Okta System Logs Integration */ @@ -71,6 +100,15 @@ export class OktaSystemIntegration extends BaseIntegration { documents.push(...this.generateGroupMembershipEvents(employee, employeeGroups, org)); } + // Org-level PAD event types (not per-employee) + documents.push(...this.generateUserLifecycleEvents(org)); + documents.push(...this.generateGroupPrivilegeEvents(org)); + documents.push(...this.generateGroupApplicationAssignmentEvents(org)); + documents.push(...this.generateGroupLifecycleEvents(org)); + + // Anomalous behavior patterns for PAD detection + documents.push(...this.generateAnomalousEvents(org)); + // Sort documents by timestamp documents.sort((a, b) => { const timestampA = (a as OktaSystemLogDocument)['@timestamp']; @@ -204,7 +242,269 @@ export class OktaSystemIntegration extends BaseIntegration { // Generate one event per group, backdated to when employee was "added" for (const group of groups) { const timestamp = faker.date.past({ years: 1 }).toISOString(); - events.push(this.createGroupMembershipEvent(employee, group, org, timestamp)); + events.push( + this.createGroupMembershipEvent( + employee, + group, + org, + timestamp, + 'group.user_membership.add', + 'Add user to group membership', + ), + ); + } + + // ~10% of employees get a removal event from a random group + if (groups.length > 1 && faker.number.float() < 0.1) { + const removedGroup = faker.helpers.arrayElement(groups); + const timestamp = this.getRandomTimestamp(48); + events.push( + this.createGroupMembershipEvent( + employee, + removedGroup, + org, + timestamp, + 'group.user_membership.remove', + 'Remove user from group membership', + ), + ); + } + + return events; + } + + /** + * Generate user lifecycle events across the org + */ + private generateUserLifecycleEvents(org: Organization): OktaSystemLogDocument[] { + const events: OktaSystemLogDocument[] = []; + const adminEmployee = faker.helpers.arrayElement(org.employees); + + for (const employee of org.employees) { + // Every employee had a create + activate (backdated) + const createTime = faker.date.past({ years: 2 }).toISOString(); + events.push( + this.createAdminActionEvent( + adminEmployee, + employee, + null, + org, + createTime, + 'user.lifecycle.create', + 'Create Okta user', + ), + ); + const activateTime = new Date( + new Date(createTime).getTime() + faker.number.int({ min: 1, max: 60 }) * 60000, + ).toISOString(); + events.push( + this.createAdminActionEvent( + adminEmployee, + employee, + null, + org, + activateTime, + 'user.lifecycle.activate', + 'Activate Okta user', + ), + ); + + // ~30% get a profile update + if (faker.number.float() < 0.3) { + const timestamp = this.getRandomTimestamp(48); + events.push( + this.createAdminActionEvent( + adminEmployee, + employee, + null, + org, + timestamp, + 'user.lifecycle.update', + 'Update Okta user profile', + ), + ); + } + + // ~3% get deactivated (offboarding) + if (faker.number.float() < 0.03) { + const timestamp = this.getRandomTimestamp(48); + events.push( + this.createAdminActionEvent( + adminEmployee, + employee, + null, + org, + timestamp, + 'user.lifecycle.deactivate', + 'Deactivate Okta user', + ), + ); + } + + // ~2% get suspended then unsuspended + if (faker.number.float() < 0.02) { + const suspendTime = this.getRandomTimestamp(48); + events.push( + this.createAdminActionEvent( + adminEmployee, + employee, + null, + org, + suspendTime, + 'user.lifecycle.suspend', + 'Suspend Okta user', + ), + ); + const unsuspendTime = new Date( + new Date(suspendTime).getTime() + faker.number.int({ min: 1, max: 24 }) * 3600000, + ).toISOString(); + events.push( + this.createAdminActionEvent( + adminEmployee, + employee, + null, + org, + unsuspendTime, + 'user.lifecycle.unsuspend', + 'Unsuspend Okta user', + ), + ); + } + } + + return events; + } + + /** + * Generate group privilege grant/revoke events + */ + private generateGroupPrivilegeEvents(org: Organization): OktaSystemLogDocument[] { + const events: OktaSystemLogDocument[] = []; + const adminEmployee = faker.helpers.arrayElement(org.employees); + + // ~5% of employees get privilege grants + for (const employee of org.employees) { + if (faker.number.float() < 0.05) { + const privilege = faker.helpers.arrayElement(OKTA_PRIVILEGE_TYPES); + const timestamp = this.getRandomTimestamp(48); + events.push( + this.createAdminActionEvent( + adminEmployee, + employee, + null, + org, + timestamp, + 'group.privilege.grant', + 'Grant group admin privilege', + { privilegeGranted: privilege }, + ), + ); + + // Occasional revoke (~30% of grants) + if (faker.number.float() < 0.3) { + const revokeTime = new Date( + new Date(timestamp).getTime() + faker.number.int({ min: 1, max: 48 }) * 3600000, + ).toISOString(); + events.push( + this.createAdminActionEvent( + adminEmployee, + employee, + null, + org, + revokeTime, + 'group.privilege.revoke', + 'Revoke group admin privilege', + { privilegeRevoked: privilege }, + ), + ); + } + } + } + + return events; + } + + /** + * Generate group application assignment events + */ + private generateGroupApplicationAssignmentEvents(org: Organization): OktaSystemLogDocument[] { + const events: OktaSystemLogDocument[] = []; + const adminEmployee = faker.helpers.arrayElement(org.employees); + + // For a few groups, assign them to applications + const groups = org.oktaGroups.slice(0, Math.min(5, org.oktaGroups.length)); + for (const group of groups) { + const app = faker.helpers.arrayElement(OKTA_APPLICATIONS); + const timestamp = faker.date.past({ years: 1 }).toISOString(); + events.push( + this.createGroupAppAssignmentEvent( + adminEmployee, + group, + app, + org, + timestamp, + 'group.application_assignment.add', + 'Add application assignment to group', + ), + ); + + // Occasional removal + if (faker.number.float() < 0.2) { + const removeTime = this.getRandomTimestamp(48); + events.push( + this.createGroupAppAssignmentEvent( + adminEmployee, + group, + app, + org, + removeTime, + 'group.application_assignment.remove', + 'Remove application assignment from group', + ), + ); + } + } + + return events; + } + + /** + * Generate group lifecycle create/delete events + */ + private generateGroupLifecycleEvents(org: Organization): OktaSystemLogDocument[] { + const events: OktaSystemLogDocument[] = []; + const adminEmployee = faker.helpers.arrayElement(org.employees); + + // Each existing group had a create event (backdated) + for (const group of org.oktaGroups) { + const timestamp = faker.date.past({ years: 2 }).toISOString(); + events.push( + this.createGroupLifecycleEvent( + adminEmployee, + group, + org, + timestamp, + 'group.lifecycle.create', + 'Create Okta group', + ), + ); + } + + // Occasional group deletion (~10% of groups) + for (const group of org.oktaGroups) { + if (faker.number.float() < 0.1) { + const timestamp = this.getRandomTimestamp(48); + events.push( + this.createGroupLifecycleEvent( + adminEmployee, + group, + org, + timestamp, + 'group.lifecycle.delete', + 'Delete Okta group', + ), + ); + } } return events; @@ -297,6 +597,7 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', @@ -377,6 +678,7 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', @@ -451,6 +753,7 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', @@ -534,6 +837,7 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', @@ -550,6 +854,8 @@ export class OktaSystemIntegration extends BaseIntegration { group: OktaGroup, org: Organization, timestamp: string, + eventType: string = 'group.user_membership.add', + displayMessage: string = 'Add user to group membership', ): OktaSystemLogDocument { const transactionId = this.generateTransactionId(); const uuid = faker.string.uuid(); @@ -563,8 +869,8 @@ export class OktaSystemIntegration extends BaseIntegration { displayName: 'Okta System', detailEntry: null, }, - eventType: 'group.user_membership.add', - displayMessage: 'Add user to group membership', + eventType, + displayMessage, outcome: { result: 'SUCCESS', }, @@ -609,6 +915,694 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'okta.system', + }, + } as unknown as OktaSystemLogDocument; + } + + /** + * Create an admin action event (user lifecycle, group privilege, etc.) + */ + private createAdminActionEvent( + actor: Employee, + targetEmployee: Employee, + targetGroup: OktaGroup | null, + org: Organization, + timestamp: string, + eventType: string, + displayMessage: string, + debugExtras?: Record, + clientInfoOverride?: ReturnType, + ): OktaSystemLogDocument { + const clientInfo = clientInfoOverride || this.generateClientInfo(actor); + const transactionId = this.generateTransactionId(); + const uuid = faker.string.uuid(); + + const target: Array<{ id: string; type: string; alternateId: string; displayName: string }> = [ + { + id: targetEmployee.oktaUserId, + type: 'User', + alternateId: targetEmployee.email, + displayName: `${targetEmployee.firstName} ${targetEmployee.lastName}`, + }, + ]; + + if (targetGroup) { + target.push({ + id: targetGroup.id, + type: 'UserGroup', + alternateId: targetGroup.name, + displayName: targetGroup.name, + }); + } + + const rawEvent = { + actor: { + id: actor.oktaUserId, + type: 'User', + alternateId: actor.email, + displayName: `${actor.firstName} ${actor.lastName}`, + detailEntry: null, + }, + eventType, + displayMessage, + outcome: { + result: 'SUCCESS', + }, + severity: 'INFO', + published: timestamp, + client: { + ipAddress: clientInfo.ip, + device: clientInfo.device, + userAgent: { + browser: clientInfo.browser, + os: clientInfo.os, + rawUserAgent: clientInfo.rawUserAgent, + }, + zone: 'null', + geographicalContext: { + city: clientInfo.geo.city_name, + country: clientInfo.geo.country_name, + state: clientInfo.geo.region_name, + geolocation: clientInfo.geo.location, + }, + }, + authenticationContext: { + authenticationStep: 0, + externalSessionId: this.generateSessionId(), + }, + debugContext: { + debugData: { + requestId: transactionId, + requestUri: '/api/v1/users', + url: '/api/v1/users', + threatSuspected: 'false', + ...debugExtras, + }, + }, + transaction: { + id: transactionId, + type: 'WEB', + }, + uuid, + target, + version: '0', + }; + + return { + '@timestamp': timestamp, + message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'okta.system', + }, + } as unknown as OktaSystemLogDocument; + } + + /** + * Create a group application assignment event + */ + private createGroupAppAssignmentEvent( + actor: Employee, + group: OktaGroup, + app: { name: string; id: string; type: string }, + org: Organization, + timestamp: string, + eventType: string, + displayMessage: string, + ): OktaSystemLogDocument { + const clientInfo = this.generateClientInfo(actor); + const transactionId = this.generateTransactionId(); + const uuid = faker.string.uuid(); + + const rawEvent = { + actor: { + id: actor.oktaUserId, + type: 'User', + alternateId: actor.email, + displayName: `${actor.firstName} ${actor.lastName}`, + detailEntry: null, + }, + eventType, + displayMessage, + outcome: { + result: 'SUCCESS', + }, + severity: 'INFO', + published: timestamp, + client: { + ipAddress: clientInfo.ip, + device: clientInfo.device, + userAgent: { + browser: clientInfo.browser, + os: clientInfo.os, + rawUserAgent: clientInfo.rawUserAgent, + }, + zone: 'null', + geographicalContext: { + city: clientInfo.geo.city_name, + country: clientInfo.geo.country_name, + state: clientInfo.geo.region_name, + geolocation: clientInfo.geo.location, + }, + }, + authenticationContext: { + authenticationStep: 0, + externalSessionId: this.generateSessionId(), + }, + debugContext: { + debugData: { + requestId: transactionId, + requestUri: '/api/v1/apps', + url: '/api/v1/apps', + threatSuspected: 'false', + }, + }, + transaction: { + id: transactionId, + type: 'WEB', + }, + uuid, + target: [ + { + id: group.id, + type: 'UserGroup', + alternateId: group.name, + displayName: group.name, + }, + { + id: app.id, + type: 'AppInstance', + alternateId: app.name, + displayName: app.name, + }, + ], + version: '0', + }; + + return { + '@timestamp': timestamp, + message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'okta.system', + }, + } as unknown as OktaSystemLogDocument; + } + + /** + * Create a group lifecycle event (create/delete) + */ + private createGroupLifecycleEvent( + actor: Employee, + group: OktaGroup, + org: Organization, + timestamp: string, + eventType: string, + displayMessage: string, + ): OktaSystemLogDocument { + const clientInfo = this.generateClientInfo(actor); + const transactionId = this.generateTransactionId(); + const uuid = faker.string.uuid(); + + const rawEvent = { + actor: { + id: actor.oktaUserId, + type: 'User', + alternateId: actor.email, + displayName: `${actor.firstName} ${actor.lastName}`, + detailEntry: null, + }, + eventType, + displayMessage, + outcome: { + result: 'SUCCESS', + }, + severity: 'INFO', + published: timestamp, + client: { + ipAddress: clientInfo.ip, + device: clientInfo.device, + userAgent: { + browser: clientInfo.browser, + os: clientInfo.os, + rawUserAgent: clientInfo.rawUserAgent, + }, + zone: 'null', + geographicalContext: { + city: clientInfo.geo.city_name, + country: clientInfo.geo.country_name, + state: clientInfo.geo.region_name, + geolocation: clientInfo.geo.location, + }, + }, + authenticationContext: { + authenticationStep: 0, + externalSessionId: this.generateSessionId(), + }, + debugContext: { + debugData: { + requestId: transactionId, + requestUri: '/api/v1/groups', + url: '/api/v1/groups', + threatSuspected: 'false', + }, + }, + transaction: { + id: transactionId, + type: 'WEB', + }, + uuid, + target: [ + { + id: group.id, + type: 'UserGroup', + alternateId: group.name, + displayName: group.name, + }, + ], + version: '0', + }; + + return { + '@timestamp': timestamp, + message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'okta.system', + }, + } as unknown as OktaSystemLogDocument; + } + + // ========================================== + // Anomalous behavior generation for PAD + // ========================================== + + /** + * Generate anomalous events designed to trigger PAD ML detections. + * Selects ~3% of employees as "rogue actors" and generates concentrated + * bursts of suspicious activity. + */ + private generateAnomalousEvents(org: Organization): OktaSystemLogDocument[] { + const events: OktaSystemLogDocument[] = []; + const rogueCount = Math.max(1, Math.floor(org.employees.length * 0.03)); + const rogueEmployees = faker.helpers.arrayElements(org.employees, rogueCount); + + console.log( + ` Generating PAD anomalous Okta patterns for ${rogueEmployees.length} rogue actor(s)...`, + ); + + for (const rogue of rogueEmployees) { + const burstEvents = this.generateRogueAdminBurst(rogue, org); + const sessionEvents = this.generateMultiCountrySessions(rogue, org); + const rareEvents = this.generateRareAccessEvents(rogue, org); + + events.push(...burstEvents, ...sessionEvents, ...rareEvents); + + console.log( + ` - ${rogue.firstName} ${rogue.lastName} (${rogue.email}): ` + + `${burstEvents.length} admin burst events, ` + + `${sessionEvents.length} multi-country sessions, ` + + `${rareEvents.length} rare IP/region events`, + ); + } + + console.log(` Total PAD anomalous events: ${events.length}`); + + return events; + } + + /** + * Scenario A: Rogue admin burst — triggers all 5 PAD spike ML jobs. + * Generates a concentrated burst of admin actions within a 1-2 hour window. + */ + private generateRogueAdminBurst(rogue: Employee, org: Organization): OktaSystemLogDocument[] { + const events: OktaSystemLogDocument[] = []; + const burstStart = new Date(Date.now() - faker.number.int({ min: 1, max: 24 }) * 3600000); + const burstDurationMs = faker.number.int({ min: 30, max: 120 }) * 60000; + + const randomBurstTime = (): string => { + return new Date( + burstStart.getTime() + faker.number.int({ min: 0, max: burstDurationMs }), + ).toISOString(); + }; + + const targetEmployees = faker.helpers.arrayElements( + org.employees.filter((e) => e.oktaUserId !== rogue.oktaUserId), + Math.min(20, org.employees.length - 1), + ); + + // 15-30 group membership add/remove events + const membershipCount = faker.number.int({ min: 15, max: 30 }); + for (let i = 0; i < membershipCount; i++) { + const target = faker.helpers.arrayElement(targetEmployees); + const group = faker.helpers.arrayElement(org.oktaGroups); + const isAdd = faker.number.float() < 0.6; + events.push( + this.createGroupMembershipEvent( + target, + group, + org, + randomBurstTime(), + isAdd ? 'group.user_membership.add' : 'group.user_membership.remove', + isAdd ? 'Add user to group membership' : 'Remove user from group membership', + ), + ); + // Override actor to be the rogue admin (the default uses SystemPrincipal) + // Re-create with createAdminActionEvent for proper actor attribution + } + // Replace the membership events with properly attributed ones + events.length = events.length - membershipCount; + for (let i = 0; i < membershipCount; i++) { + const target = faker.helpers.arrayElement(targetEmployees); + const group = faker.helpers.arrayElement(org.oktaGroups); + const isAdd = faker.number.float() < 0.6; + events.push( + this.createAdminActionEvent( + rogue, + target, + group, + org, + randomBurstTime(), + isAdd ? 'group.user_membership.add' : 'group.user_membership.remove', + isAdd ? 'Add user to group membership' : 'Remove user from group membership', + ), + ); + } + + // 10-20 user lifecycle events + const lifecycleCount = faker.number.int({ min: 10, max: 20 }); + const lifecycleTypes = [ + { type: 'user.lifecycle.activate', msg: 'Activate Okta user' }, + { type: 'user.lifecycle.deactivate', msg: 'Deactivate Okta user' }, + { type: 'user.lifecycle.suspend', msg: 'Suspend Okta user' }, + { type: 'user.lifecycle.update', msg: 'Update Okta user profile' }, + { type: 'user.lifecycle.unsuspend', msg: 'Unsuspend Okta user' }, + ]; + for (let i = 0; i < lifecycleCount; i++) { + const target = faker.helpers.arrayElement(targetEmployees); + const action = faker.helpers.arrayElement(lifecycleTypes); + events.push( + this.createAdminActionEvent( + rogue, + target, + null, + org, + randomBurstTime(), + action.type, + action.msg, + ), + ); + } + + // 5-10 group privilege grant/revoke events + const privilegeCount = faker.number.int({ min: 5, max: 10 }); + for (let i = 0; i < privilegeCount; i++) { + const target = faker.helpers.arrayElement(targetEmployees); + const privilege = faker.helpers.arrayElement(OKTA_PRIVILEGE_TYPES); + const isGrant = faker.number.float() < 0.7; + events.push( + this.createAdminActionEvent( + rogue, + target, + null, + org, + randomBurstTime(), + isGrant ? 'group.privilege.grant' : 'group.privilege.revoke', + isGrant ? 'Grant group admin privilege' : 'Revoke group admin privilege', + isGrant ? { privilegeGranted: privilege } : { privilegeRevoked: privilege }, + ), + ); + } + + // 5-10 group application assignment events + const appAssignCount = faker.number.int({ min: 5, max: 10 }); + for (let i = 0; i < appAssignCount; i++) { + const group = faker.helpers.arrayElement(org.oktaGroups); + const app = faker.helpers.arrayElement(OKTA_APPLICATIONS); + const isAdd = faker.number.float() < 0.6; + events.push( + this.createGroupAppAssignmentEvent( + rogue, + group, + app, + org, + randomBurstTime(), + isAdd ? 'group.application_assignment.add' : 'group.application_assignment.remove', + isAdd + ? 'Add application assignment to group' + : 'Remove application assignment from group', + ), + ); + } + + // 3-5 group lifecycle create/delete events + const groupLifecycleCount = faker.number.int({ min: 3, max: 5 }); + for (let i = 0; i < groupLifecycleCount; i++) { + const fakeGroup: OktaGroup = { + id: `00g${faker.string.alphanumeric(17)}`, + name: `${faker.word.adjective()}-${faker.word.noun()}-group`, + description: faker.company.catchPhrase(), + type: 'access', + }; + const isCreate = faker.number.float() < 0.6; + events.push( + this.createGroupLifecycleEvent( + rogue, + fakeGroup, + org, + randomBurstTime(), + isCreate ? 'group.lifecycle.create' : 'group.lifecycle.delete', + isCreate ? 'Create Okta group' : 'Delete Okta group', + ), + ); + } + + return events; + } + + /** + * Scenario B: Concurrent multi-country sessions — triggers the Okta session + * transform and pad_okta_high_sum_concurrent_sessions_by_user ML job. + * Generates session.start events from 3-4 different countries within ~30 minutes + * WITHOUT matching session.end events. + */ + private generateMultiCountrySessions( + rogue: Employee, + org: Organization, + ): OktaSystemLogDocument[] { + const events: OktaSystemLogDocument[] = []; + const baseTime = new Date(Date.now() - faker.number.int({ min: 1, max: 12 }) * 3600000); + + const foreignLocations = faker.helpers.arrayElements( + ANOMALOUS_LOCATIONS, + faker.number.int({ min: 3, max: 4 }), + ); + + for (const location of foreignLocations) { + const sessionId = this.generateSessionId(); + const timestamp = new Date( + baseTime.getTime() + faker.number.int({ min: 0, max: 30 }) * 60000, + ).toISOString(); + const ip = location.ip + faker.number.int({ min: 1, max: 254 }); + + const clientInfo = { + ip, + device: 'Computer', + browser: 'CHROME', + os: 'Linux', + rawUserAgent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + geo: { + city_name: location.city, + country_name: location.country, + region_name: location.state, + location: { lat: faker.location.latitude(), lon: faker.location.longitude() }, + }, + }; + + // Session start only — no end event so transform flags it as active + events.push( + this.createSessionEventWithClientInfo( + rogue, + org, + 'user.session.start', + 'User login to Okta', + timestamp, + sessionId, + 'SUCCESS', + undefined, + clientInfo, + ), + ); + } + + return events; + } + + /** + * Scenario C: Rare source IP and region — triggers pad_okta_rare_source_ip_by_user + * and pad_okta_rare_region_name_by_user ML jobs. + * Generates admin action events from unusual geographic locations. + */ + private generateRareAccessEvents(rogue: Employee, org: Organization): OktaSystemLogDocument[] { + const events: OktaSystemLogDocument[] = []; + const location = faker.helpers.arrayElement(ANOMALOUS_LOCATIONS); + const ip = location.ip + faker.number.int({ min: 1, max: 254 }); + + const rareClientInfo = { + ip, + device: 'Computer', + browser: 'FIREFOX', + os: 'Linux', + rawUserAgent: 'Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0', + geo: { + city_name: location.city, + country_name: location.country, + region_name: location.state, + location: { lat: faker.location.latitude(), lon: faker.location.longitude() }, + }, + }; + + // Generate admin actions from the rare location + const targetEmployees = faker.helpers.arrayElements( + org.employees.filter((e) => e.oktaUserId !== rogue.oktaUserId), + Math.min(5, org.employees.length - 1), + ); + + const eventTypes = [ + { type: 'group.user_membership.add', msg: 'Add user to group membership' }, + { type: 'user.lifecycle.update', msg: 'Update Okta user profile' }, + { type: 'group.privilege.grant', msg: 'Grant group admin privilege' }, + { type: 'user.lifecycle.deactivate', msg: 'Deactivate Okta user' }, + ]; + + for (const target of targetEmployees) { + const action = faker.helpers.arrayElement(eventTypes); + const timestamp = this.getRandomTimestamp(24); + const debugExtras = + action.type === 'group.privilege.grant' + ? { privilegeGranted: faker.helpers.arrayElement(OKTA_PRIVILEGE_TYPES) } + : undefined; + events.push( + this.createAdminActionEvent( + rogue, + target, + action.type.startsWith('group.user_membership') + ? faker.helpers.arrayElement(org.oktaGroups) + : null, + org, + timestamp, + action.type, + action.msg, + debugExtras, + rareClientInfo, + ), + ); + } + + return events; + } + + /** + * Create a session event with custom client info (used for anomalous multi-country sessions) + */ + private createSessionEventWithClientInfo( + employee: Employee, + org: Organization, + eventType: 'user.session.start' | 'user.session.end', + displayMessage: string, + timestamp: string, + sessionId: string, + result: 'SUCCESS' | 'FAILURE', + reason?: string, + clientInfo?: ReturnType, + ): OktaSystemLogDocument { + const client = clientInfo || this.generateClientInfo(employee); + const transactionId = this.generateTransactionId(); + const uuid = faker.string.uuid(); + + const rawEvent = { + actor: { + id: employee.oktaUserId, + type: 'User', + alternateId: employee.email, + displayName: `${employee.firstName} ${employee.lastName}`, + detailEntry: null, + }, + eventType, + displayMessage, + outcome: { + result, + reason: reason || null, + }, + severity: result === 'SUCCESS' ? 'INFO' : 'WARN', + published: timestamp, + client: { + ipAddress: client.ip, + device: client.device, + userAgent: { + browser: client.browser, + os: client.os, + rawUserAgent: client.rawUserAgent, + }, + zone: 'null', + geographicalContext: { + city: client.geo.city_name, + country: client.geo.country_name, + state: client.geo.region_name, + geolocation: client.geo.location, + }, + }, + authenticationContext: { + authenticationStep: 0, + externalSessionId: sessionId, + }, + debugContext: { + debugData: { + requestId: transactionId, + requestUri: eventType === 'user.session.start' ? '/api/v1/authn' : '/login/signout', + url: eventType === 'user.session.start' ? '/api/v1/authn?' : '/login/signout', + deviceFingerprint: faker.string.alphanumeric(32), + threatSuspected: 'false', + }, + }, + request: { + ipChain: [ + { + ip: client.ip, + version: 'V4', + geographicalContext: { + city: client.geo.city_name, + country: client.geo.country_name, + state: client.geo.region_name, + geolocation: client.geo.location, + }, + }, + ], + }, + transaction: { + id: transactionId, + type: 'WEB', + }, + uuid, + version: '0', + }; + + return { + '@timestamp': timestamp, + message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', diff --git a/src/commands/org_data/integrations/onepassword_integration.ts b/src/commands/org_data/integrations/onepassword_integration.ts index 0c03eac5..0b53fc92 100644 --- a/src/commands/org_data/integrations/onepassword_integration.ts +++ b/src/commands/org_data/integrations/onepassword_integration.ts @@ -122,7 +122,7 @@ export class OnePasswordIntegration extends BaseIntegration { return documentsMap; } - private generateSigninDocument(employee: Employee, _org: Organization): IntegrationDocument { + private generateSigninDocument(employee: Employee, org: Organization): IntegrationDocument { const sourceIp = faker.internet.ipv4(); const success = faker.datatype.boolean(0.95); const timestamp = this.getRandomTimestamp(72); @@ -153,6 +153,7 @@ export class OnePasswordIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: '1password.signin_attempts' }, } as IntegrationDocument; @@ -204,6 +205,7 @@ export class OnePasswordIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: '1password.item_usages' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/ping_directory_integration.ts b/src/commands/org_data/integrations/ping_directory_integration.ts index eabcd5b2..5bc2b8bd 100644 --- a/src/commands/org_data/integrations/ping_directory_integration.ts +++ b/src/commands/org_data/integrations/ping_directory_integration.ts @@ -10,7 +10,12 @@ * API reference: https://developer.pingidentity.com/pingdirectory/directory-proxy-scim/user-profile-endpoints/get-read-search-users.html */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -99,6 +104,7 @@ export class PingDirectoryIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); // Build a lookup for managers const employeeById = new Map(); @@ -113,7 +119,9 @@ export class PingDirectoryIntegration extends BaseIntegration { for (const employee of org.employees) { const manager = employee.managerId ? employeeById.get(employee.managerId) : undefined; - documents.push(this.createScimUserDocument(employee, manager, org, scimBaseUrl)); + documents.push( + this.createScimUserDocument(employee, manager, org, scimBaseUrl, centralAgent), + ); } documentsMap.set(this.dataStreams[0].index, documents); @@ -129,6 +137,7 @@ export class PingDirectoryIntegration extends BaseIntegration { manager: Employee | undefined, org: Organization, scimBaseUrl: string, + centralAgent: AgentData, ): IntegrationDocument { const scimUserId = faker.string.uuid(); const createdDate = faker.date @@ -159,6 +168,7 @@ export class PingDirectoryIntegration extends BaseIntegration { return { '@timestamp': this.getRandomTimestamp(24), + agent: centralAgent, event: { dataset: 'ping_directory.users', kind: 'asset', diff --git a/src/commands/org_data/integrations/ping_one_integration.ts b/src/commands/org_data/integrations/ping_one_integration.ts index 9249200d..929bd1db 100644 --- a/src/commands/org_data/integrations/ping_one_integration.ts +++ b/src/commands/org_data/integrations/ping_one_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic ping_one integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -105,6 +110,7 @@ export class PingOneIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); const environmentId = faker.string.uuid(); @@ -112,7 +118,7 @@ export class PingOneIntegration extends BaseIntegration { for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 3 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee, org, environmentId)); + documents.push(this.createAuditDocument(employee, org, environmentId, centralAgent)); } } @@ -127,6 +133,7 @@ export class PingOneIntegration extends BaseIntegration { employee: Employee, org: Organization, environmentId: string, + centralAgent: AgentData, ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), @@ -221,6 +228,7 @@ export class PingOneIntegration extends BaseIntegration { return { '@timestamp': recordedAt, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', diff --git a/src/commands/org_data/integrations/sailpoint_integration.ts b/src/commands/org_data/integrations/sailpoint_integration.ts index 9077072e..81c0059d 100644 --- a/src/commands/org_data/integrations/sailpoint_integration.ts +++ b/src/commands/org_data/integrations/sailpoint_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic sailpoint_identity_sc integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -142,6 +147,7 @@ export class SailPointIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); const orgSlug = org.name.toLowerCase().replace(/[^a-z0-9]/g, '-'); @@ -150,7 +156,7 @@ export class SailPointIntegration extends BaseIntegration { const eventCount = faker.number.int({ min: 2, max: 3 }); for (let i = 0; i < eventCount; i++) { const targetEmployee = faker.helpers.arrayElement(org.employees); - documents.push(this.createEventDocument(employee, targetEmployee, orgSlug)); + documents.push(this.createEventDocument(employee, targetEmployee, orgSlug, centralAgent)); } } @@ -165,6 +171,7 @@ export class SailPointIntegration extends BaseIntegration { actor: Employee, target: Employee, orgSlug: string, + centralAgent: AgentData, ): IntegrationDocument { const eventType = faker.helpers.weightedArrayElement( EVENT_TYPES.map((et) => ({ value: et, weight: et.weight })), @@ -227,6 +234,7 @@ export class SailPointIntegration extends BaseIntegration { return { '@timestamp': created, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', diff --git a/src/commands/org_data/integrations/servicenow_integration.ts b/src/commands/org_data/integrations/servicenow_integration.ts index a9c02bb5..90c48acc 100644 --- a/src/commands/org_data/integrations/servicenow_integration.ts +++ b/src/commands/org_data/integrations/servicenow_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic servicenow integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -86,6 +91,7 @@ export class ServiceNowIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); // Generate incidents -- roughly 1 per 5 employees const incidentCount = Math.max( @@ -103,7 +109,7 @@ export class ServiceNowIntegration extends BaseIntegration { ? faker.helpers.arrayElement(opsEmployees) : faker.helpers.arrayElement(org.employees); - documents.push(this.createIncidentDocument(opener, assignee, org, i + 1)); + documents.push(this.createIncidentDocument(opener, assignee, org, i + 1, centralAgent)); } // Generate a few change requests from Operations staff @@ -113,7 +119,9 @@ export class ServiceNowIntegration extends BaseIntegration { opsEmployees.length > 0 ? faker.helpers.arrayElement(opsEmployees) : faker.helpers.arrayElement(org.employees); - documents.push(this.createChangeRequestDocument(requester, org, incidentCount + i + 1)); + documents.push( + this.createChangeRequestDocument(requester, org, incidentCount + i + 1, centralAgent), + ); } documentsMap.set(this.dataStreams[0].index, documents); @@ -129,6 +137,7 @@ export class ServiceNowIntegration extends BaseIntegration { assignee: Employee, org: Organization, seq: number, + centralAgent: AgentData, ): IntegrationDocument { const category = faker.helpers.arrayElement(INCIDENT_CATEGORIES); const subcategory = faker.helpers.arrayElement(INCIDENT_SUBCATEGORIES[category]); @@ -186,6 +195,7 @@ export class ServiceNowIntegration extends BaseIntegration { return { '@timestamp': openedAt, + agent: centralAgent, message: JSON.stringify(rawServiceNowEvent), _conf: { timestamp_field: 'sys_updated_on', @@ -208,6 +218,7 @@ export class ServiceNowIntegration extends BaseIntegration { requester: Employee, org: Organization, seq: number, + centralAgent: AgentData, ): IntegrationDocument { const sysId = faker.string.uuid().replace(/-/g, ''); const number = `CHG${String(seq).padStart(7, '0')}`; @@ -256,6 +267,7 @@ export class ServiceNowIntegration extends BaseIntegration { return { '@timestamp': openedAt, + agent: centralAgent, message: JSON.stringify(rawServiceNowEvent), _conf: { timestamp_field: 'sys_updated_on', diff --git a/src/commands/org_data/integrations/slack_integration.ts b/src/commands/org_data/integrations/slack_integration.ts index 00c5d3d7..a10f25fd 100644 --- a/src/commands/org_data/integrations/slack_integration.ts +++ b/src/commands/org_data/integrations/slack_integration.ts @@ -4,7 +4,12 @@ * Based on the Elastic slack integration package */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -72,12 +77,13 @@ export class SlackIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); // Generate 2-4 audit events per employee for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee, org)); + documents.push(this.createAuditDocument(employee, org, centralAgent)); } } @@ -88,7 +94,11 @@ export class SlackIntegration extends BaseIntegration { /** * Create a Slack audit log document */ - private createAuditDocument(employee: Employee, org: Organization): IntegrationDocument { + private createAuditDocument( + employee: Employee, + org: Organization, + centralAgent: AgentData, + ): IntegrationDocument { const action = faker.helpers.weightedArrayElement( AUDIT_ACTIONS.map((a) => ({ value: a, weight: a.weight })), ); @@ -146,6 +156,7 @@ export class SlackIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', diff --git a/src/commands/org_data/integrations/system_integration.ts b/src/commands/org_data/integrations/system_integration.ts index ea08c649..9dc41755 100644 --- a/src/commands/org_data/integrations/system_integration.ts +++ b/src/commands/org_data/integrations/system_integration.ts @@ -274,6 +274,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -306,6 +307,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -337,6 +339,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -359,6 +362,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -382,6 +386,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -437,6 +442,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { diff --git a/src/commands/org_data/integrations/teleport_integration.ts b/src/commands/org_data/integrations/teleport_integration.ts index d1b345e4..cb27137a 100644 --- a/src/commands/org_data/integrations/teleport_integration.ts +++ b/src/commands/org_data/integrations/teleport_integration.ts @@ -122,11 +122,12 @@ export class TeleportIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 5 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createAuditDocument(employee)); + documents.push(this.createAuditDocument(employee, centralAgent)); } } @@ -134,7 +135,10 @@ export class TeleportIntegration extends BaseIntegration { return documentsMap; } - private createAuditDocument(employee: Employee): IntegrationDocument { + private createAuditDocument( + employee: Employee, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const eventDef = faker.helpers.weightedArrayElement( AUDIT_EVENTS.map((e) => ({ value: e, weight: e.weight })), ); @@ -177,6 +181,7 @@ export class TeleportIntegration extends BaseIntegration { // user must be string (pipeline expects it in raw JSON) return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', type: 'logs', dataset: 'teleport.audit' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/thycotic_ss_integration.ts b/src/commands/org_data/integrations/thycotic_ss_integration.ts index ba622196..c204b63d 100644 --- a/src/commands/org_data/integrations/thycotic_ss_integration.ts +++ b/src/commands/org_data/integrations/thycotic_ss_integration.ts @@ -127,11 +127,12 @@ export class ThycoticSsIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 1, max: 4 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createLogDocument(employee, org)); + documents.push(this.createLogDocument(employee, org, centralAgent)); } } @@ -139,7 +140,11 @@ export class ThycoticSsIntegration extends BaseIntegration { return documentsMap; } - private createLogDocument(employee: Employee, org: Organization): IntegrationDocument { + private createLogDocument( + employee: Employee, + org: Organization, + centralAgent: { id: string; name: string; type: string; version: string }, + ): IntegrationDocument { const eventDef = faker.helpers.weightedArrayElement( SECRET_EVENTS.map((e) => ({ value: e, weight: e.weight })), ); @@ -171,6 +176,7 @@ export class ThycoticSsIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message, data_stream: { namespace: 'default', type: 'logs', dataset: 'thycotic_ss.logs' }, } as IntegrationDocument; diff --git a/src/commands/org_data/integrations/ti_abusech_integration.ts b/src/commands/org_data/integrations/ti_abusech_integration.ts index 8ca78b5a..5657f237 100644 --- a/src/commands/org_data/integrations/ti_abusech_integration.ts +++ b/src/commands/org_data/integrations/ti_abusech_integration.ts @@ -3,7 +3,12 @@ * Generates malware hash and malicious URL indicator documents */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; import { @@ -34,17 +39,18 @@ export class TiAbusechIntegration extends BaseIntegration { const documentsMap = new Map(); const malwareDocs: IntegrationDocument[] = []; const urlDocs: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); const indicatorCount = this.getIndicatorCount(org.size); // Generate malware hash indicators for (let i = 0; i < indicatorCount.malware; i++) { - malwareDocs.push(this.generateMalwareDocument()); + malwareDocs.push(this.generateMalwareDocument(centralAgent)); } // Generate URL indicators for (let i = 0; i < indicatorCount.url; i++) { - urlDocs.push(this.generateUrlDocument()); + urlDocs.push(this.generateUrlDocument(centralAgent)); } documentsMap.set('logs-ti_abusech.malware-default', malwareDocs); @@ -52,7 +58,7 @@ export class TiAbusechIntegration extends BaseIntegration { return documentsMap; } - private generateMalwareDocument(): IntegrationDocument { + private generateMalwareDocument(centralAgent: AgentData): IntegrationDocument { const family = faker.helpers.arrayElement(MALWARE_FAMILIES); // Use deterministic hashes from our shared set so CrowdStrike alerts can reference them const sha256 = faker.helpers.arrayElement(MALWARE_HASHES); @@ -88,13 +94,14 @@ export class TiAbusechIntegration extends BaseIntegration { return { '@timestamp': lastSeen, + agent: centralAgent, message: JSON.stringify(rawEvent), _conf: { ioc_expiration_duration: '90d' }, data_stream: { namespace: 'default', type: 'logs', dataset: 'ti_abusech.malware' }, } as IntegrationDocument; } - private generateUrlDocument(): IntegrationDocument { + private generateUrlDocument(centralAgent: AgentData): IntegrationDocument { const url = faker.helpers.arrayElement(MALICIOUS_URLS); const threatType = faker.helpers.arrayElement(ABUSECH_THREAT_TYPES); const status = faker.helpers.weightedArrayElement([ @@ -135,6 +142,7 @@ export class TiAbusechIntegration extends BaseIntegration { return { '@timestamp': lastSeen, + agent: centralAgent, message: JSON.stringify(rawEvent), _conf: { interval: '24h' }, data_stream: { namespace: 'default', type: 'logs', dataset: 'ti_abusech.url' }, diff --git a/src/commands/org_data/integrations/workday_integration.ts b/src/commands/org_data/integrations/workday_integration.ts index 634e6ced..fbe6839e 100644 --- a/src/commands/org_data/integrations/workday_integration.ts +++ b/src/commands/org_data/integrations/workday_integration.ts @@ -9,7 +9,12 @@ * API reference: https://community.workday.com/sites/default/files/file-hosting/restapi/index.html#person/v4/people */ -import { BaseIntegration, IntegrationDocument, DataStreamConfig } from './base_integration'; +import { + BaseIntegration, + IntegrationDocument, + DataStreamConfig, + AgentData, +} from './base_integration'; import { Organization, Employee, CorrelationMap } from '../types'; import { faker } from '@faker-js/faker'; @@ -98,6 +103,7 @@ export class WorkdayIntegration extends BaseIntegration { ): Map { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; + const centralAgent = this.buildCentralAgent(org); // Build a lookup for managers const employeeById = new Map(); @@ -109,7 +115,7 @@ export class WorkdayIntegration extends BaseIntegration { for (const employee of org.employees) { const manager = employee.managerId ? employeeById.get(employee.managerId) : undefined; - documents.push(this.createPersonDocument(employee, manager, org)); + documents.push(this.createPersonDocument(employee, manager, org, centralAgent)); } documentsMap.set(this.dataStreams[0].index, documents); @@ -123,6 +129,7 @@ export class WorkdayIntegration extends BaseIntegration { employee: Employee, manager: Employee | undefined, org: Organization, + centralAgent: AgentData, ): IntegrationDocument { const workdayId = faker.string.hexadecimal({ length: 32, prefix: '' }).toLowerCase(); const hireDate = faker.date @@ -238,6 +245,7 @@ export class WorkdayIntegration extends BaseIntegration { return { '@timestamp': this.getRandomTimestamp(24), + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', diff --git a/src/commands/org_data/integrations/zoom_integration.ts b/src/commands/org_data/integrations/zoom_integration.ts index 74786ecd..762abec7 100644 --- a/src/commands/org_data/integrations/zoom_integration.ts +++ b/src/commands/org_data/integrations/zoom_integration.ts @@ -118,11 +118,12 @@ export class ZoomIntegration extends BaseIntegration { const documentsMap = new Map(); const documents: IntegrationDocument[] = []; const masterAccountId = faker.string.alphanumeric(22); + const centralAgent = this.buildCentralAgent(org); for (const employee of org.employees) { const eventCount = faker.number.int({ min: 2, max: 4 }); for (let i = 0; i < eventCount; i++) { - documents.push(this.createWebhookDocument(employee, org, masterAccountId)); + documents.push(this.createWebhookDocument(employee, org, masterAccountId, centralAgent)); } } @@ -134,6 +135,7 @@ export class ZoomIntegration extends BaseIntegration { employee: Employee, org: Organization, masterAccountId: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const eventDef = faker.helpers.weightedArrayElement( WEBHOOK_EVENTS.map((e) => ({ value: e, weight: e.weight })), @@ -240,6 +242,7 @@ export class ZoomIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, event: { action: eventDef.action, category: eventDef.category, diff --git a/src/commands/org_data/integrations/zscaler_zia_integration.ts b/src/commands/org_data/integrations/zscaler_zia_integration.ts index 9abde522..7359ba55 100644 --- a/src/commands/org_data/integrations/zscaler_zia_integration.ts +++ b/src/commands/org_data/integrations/zscaler_zia_integration.ts @@ -103,8 +103,10 @@ export class ZscalerZiaIntegration extends BaseIntegration { proto: 'SSL', }; + const hostname = laptop ? `${employee.userName}-${laptop.platform}` : 'unknown'; return { '@timestamp': timestamp, + agent: laptop ? this.buildLocalAgent(laptop, hostname) : undefined, message: JSON.stringify({ event: rawEvent }), data_stream: { namespace: 'default', type: 'logs', dataset: 'zscaler_zia.web' }, } as IntegrationDocument; @@ -165,8 +167,10 @@ export class ZscalerZiaIntegration extends BaseIntegration { proto: 'SSL', }; + const hostname = laptop ? `${employee.userName}-${laptop.platform}` : 'unknown'; return { '@timestamp': timestamp, + agent: laptop ? this.buildLocalAgent(laptop, hostname) : undefined, message: JSON.stringify({ event: rawEvent }), data_stream: { namespace: 'default', type: 'logs', dataset: 'zscaler_zia.web' }, } as IntegrationDocument; @@ -213,8 +217,13 @@ export class ZscalerZiaIntegration extends BaseIntegration { devicename: employee.devices[0]?.displayName || 'Unknown', }; + const device = employee.devices.find((d) => d.type === 'laptop'); + const hostname = device + ? `${employee.userName}-${device.platform}` + : `${employee.userName}-unknown`; return { '@timestamp': this.getRandomTimestamp(72), + agent: device ? this.buildLocalAgent(device, hostname) : undefined, message: JSON.stringify({ event: rawEvent }), data_stream: { namespace: 'default', type: 'logs', dataset: 'zscaler_zia.firewall' }, } as IntegrationDocument; diff --git a/src/commands/org_data/org_data_generator.ts b/src/commands/org_data/org_data_generator.ts index 30c813c3..981fa396 100644 --- a/src/commands/org_data/org_data_generator.ts +++ b/src/commands/org_data/org_data_generator.ts @@ -10,6 +10,7 @@ import { Employee, Device, Host, + CentralAgent, CloudAccount, CloudResource, CloudIamUser, @@ -101,6 +102,12 @@ export const generateOrgData = (config: OrganizationConfig): Organization => { // Determine productivity suite const productivitySuite: ProductivitySuite = config.productivitySuite || 'microsoft'; + // Generate central agent for cloud/SaaS integrations + const centralAgent: CentralAgent = { + id: faker.string.uuid(), + name: 'fleet-collector-01', + }; + return { name: config.name, domain, @@ -117,6 +124,7 @@ export const generateOrgData = (config: OrganizationConfig): Organization => { cloudflareZones, onePasswordVaults, productivitySuite, + centralAgent, }; }; @@ -450,6 +458,7 @@ const generateDevice = (type: DeviceType, platform: LaptopPlatform | MobilePlatf crowdstrikeDeviceId: faker.string.hexadecimal({ length: 32, prefix: '' }).toLowerCase(), macAddress: faker.internet.mac({ separator: '-' }), ipAddress: faker.internet.ipv4(), + elasticAgentId: faker.string.uuid(), }; }; @@ -492,6 +501,7 @@ const generateHosts = (count: number, cloudProviders: CloudProvider[]): Host[] = region, purpose, os, + elasticAgentId: faker.string.uuid(), }); } diff --git a/src/commands/org_data/types.ts b/src/commands/org_data/types.ts index 342503be..7aef1c8c 100644 --- a/src/commands/org_data/types.ts +++ b/src/commands/org_data/types.ts @@ -40,6 +40,7 @@ export interface Device { crowdstrikeDeviceId: string; macAddress: string; ipAddress: string; + elasticAgentId: string; } /** @@ -96,6 +97,15 @@ export interface Host { version: string; family: string; }; + elasticAgentId: string; +} + +/** + * Central Elastic Agent that collects data from cloud/SaaS integrations + */ +export interface CentralAgent { + id: string; + name: string; } /** @@ -208,6 +218,7 @@ export interface Organization { cloudflareZones: CloudflareZone[]; onePasswordVaults: OnePasswordVault[]; productivitySuite: ProductivitySuite; + centralAgent: CentralAgent; } /** From 730442ca0212fe46e438a8bedfd680c78c07e85b Mon Sep 17 00:00:00 2001 From: Paulo Silva Date: Fri, 10 Apr 2026 15:56:09 -0700 Subject: [PATCH 2/3] fix lint errors --- src/commands/org_data/integrations/okta_system_integration.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/commands/org_data/integrations/okta_system_integration.ts b/src/commands/org_data/integrations/okta_system_integration.ts index dcd15fa8..87af59fb 100644 --- a/src/commands/org_data/integrations/okta_system_integration.ts +++ b/src/commands/org_data/integrations/okta_system_integration.ts @@ -1228,6 +1228,7 @@ export class OktaSystemIntegration extends BaseIntegration { const rogueCount = Math.max(1, Math.floor(org.employees.length * 0.03)); const rogueEmployees = faker.helpers.arrayElements(org.employees, rogueCount); + // eslint-disable-next-line no-console console.log( ` Generating PAD anomalous Okta patterns for ${rogueEmployees.length} rogue actor(s)...`, ); @@ -1239,6 +1240,7 @@ export class OktaSystemIntegration extends BaseIntegration { events.push(...burstEvents, ...sessionEvents, ...rareEvents); + // eslint-disable-next-line no-console console.log( ` - ${rogue.firstName} ${rogue.lastName} (${rogue.email}): ` + `${burstEvents.length} admin burst events, ` + @@ -1247,6 +1249,7 @@ export class OktaSystemIntegration extends BaseIntegration { ); } + // eslint-disable-next-line no-console console.log(` Total PAD anomalous events: ${events.length}`); return events; From 61b6de1523b9b8d39e481c3344aebe26253ec1b3 Mon Sep 17 00:00:00 2001 From: Paulo Silva Date: Fri, 10 Apr 2026 16:17:58 -0700 Subject: [PATCH 3/3] replace console.log with the logger utility --- .../org_data/integrations/okta_system_integration.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/commands/org_data/integrations/okta_system_integration.ts b/src/commands/org_data/integrations/okta_system_integration.ts index 87af59fb..50d45a2d 100644 --- a/src/commands/org_data/integrations/okta_system_integration.ts +++ b/src/commands/org_data/integrations/okta_system_integration.ts @@ -16,6 +16,7 @@ import { type OktaGroup, } from '../types.ts'; import { faker } from '@faker-js/faker'; +import { log } from '../../../utils/logger.ts'; /** * Okta applications for SSO events @@ -1228,8 +1229,7 @@ export class OktaSystemIntegration extends BaseIntegration { const rogueCount = Math.max(1, Math.floor(org.employees.length * 0.03)); const rogueEmployees = faker.helpers.arrayElements(org.employees, rogueCount); - // eslint-disable-next-line no-console - console.log( + log.info( ` Generating PAD anomalous Okta patterns for ${rogueEmployees.length} rogue actor(s)...`, ); @@ -1240,8 +1240,7 @@ export class OktaSystemIntegration extends BaseIntegration { events.push(...burstEvents, ...sessionEvents, ...rareEvents); - // eslint-disable-next-line no-console - console.log( + log.info( ` - ${rogue.firstName} ${rogue.lastName} (${rogue.email}): ` + `${burstEvents.length} admin burst events, ` + `${sessionEvents.length} multi-country sessions, ` + @@ -1249,8 +1248,7 @@ export class OktaSystemIntegration extends BaseIntegration { ); } - // eslint-disable-next-line no-console - console.log(` Total PAD anomalous events: ${events.length}`); + log.info(` Total PAD anomalous events: ${events.length}`); return events; }