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 3e6d2cae..f11b4ead 100644 --- a/src/commands/org_data/integrations/active_directory_integration.ts +++ b/src/commands/org_data/integrations/active_directory_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, @@ -65,6 +66,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`; @@ -73,7 +75,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); } @@ -83,7 +92,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); } } @@ -110,6 +126,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(); @@ -191,6 +208,7 @@ export class ActiveDirectoryIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, activedirectory: { id: userDn, user: entry, @@ -223,6 +241,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(); @@ -265,6 +284,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 148048e5..07a690d4 100644 --- a/src/commands/org_data/integrations/atlassian_bitbucket_integration.ts +++ b/src/commands/org_data/integrations/atlassian_bitbucket_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -238,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)); } } @@ -250,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 })), ); @@ -287,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 b348bedb..e8f93878 100644 --- a/src/commands/org_data/integrations/atlassian_confluence_integration.ts +++ b/src/commands/org_data/integrations/atlassian_confluence_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -210,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)); } } @@ -222,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 })), ); @@ -268,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 fb2841f9..b5f4a4ee 100644 --- a/src/commands/org_data/integrations/atlassian_jira_integration.ts +++ b/src/commands/org_data/integrations/atlassian_jira_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -186,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)); } } @@ -198,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 })), ); @@ -233,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 919a5981..937da084 100644 --- a/src/commands/org_data/integrations/auth0_integration.ts +++ b/src/commands/org_data/integrations/auth0_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -151,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)); } } @@ -163,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 })), ); @@ -200,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 00822d13..d31b5257 100644 --- a/src/commands/org_data/integrations/authentik_integration.ts +++ b/src/commands/org_data/integrations/authentik_integration.ts @@ -9,6 +9,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -83,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(); @@ -98,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)); } } @@ -128,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: '' }); @@ -155,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 }); @@ -180,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 })), ); @@ -221,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 3d64b25b..7a1be75f 100644 --- a/src/commands/org_data/integrations/azure_integration.ts +++ b/src/commands/org_data/integrations/azure_integration.ts @@ -315,46 +315,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; @@ -366,13 +374,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, + ), + ); } } @@ -384,6 +401,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( @@ -436,6 +454,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; @@ -445,13 +464,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 auditCount = Math.max(5, Math.ceil(employees.length * 0.3)); for (let i = 0; i < auditCount; i++) { const actor = faker.helpers.arrayElement(employees); - docs.push(this.createAuditLogDoc(actor, employees, tenantId)); + docs.push(this.createAuditLogDoc(actor, employees, tenantId, centralAgent)); } return docs; @@ -461,6 +481,7 @@ export class AzureIntegration extends BaseIntegration { actor: Employee, employees: Employee[], tenantId: string, + centralAgent: { id: string; name: string; type: string; version: string }, ): IntegrationDocument { const timestamp = this.getRandomTimestamp(72); const activity = faker.helpers.weightedArrayElement( @@ -523,25 +544,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(); @@ -630,6 +660,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; @@ -638,19 +669,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([ @@ -714,6 +750,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.identity_protection', @@ -727,13 +764,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; @@ -743,6 +781,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); @@ -837,23 +876,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(); @@ -871,6 +917,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, azure: { correlation_id: correlationId, graphactivitylogs: { @@ -951,6 +998,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)); @@ -958,7 +1006,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; @@ -969,6 +1025,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); @@ -1010,6 +1067,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; @@ -1018,18 +1076,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); @@ -1057,6 +1120,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; @@ -1065,13 +1129,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; @@ -1081,6 +1153,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(); @@ -1142,6 +1215,7 @@ export class AzureIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawAzureJson), data_stream: { dataset: 'azure.application_gateway', @@ -1151,14 +1225,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; @@ -1168,6 +1247,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); @@ -1208,6 +1288,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 71029f4f..81874913 100644 --- a/src/commands/org_data/integrations/base_integration.ts +++ b/src/commands/org_data/integrations/base_integration.ts @@ -4,12 +4,21 @@ */ import { log } from '../../../utils/logger.ts'; -import { type Organization, type CorrelationMap } from '../types.ts'; +import { type Organization, type CorrelationMap, type Device, type Host } from '../types.ts'; import { installPackage } from '../../../utils/kibana_api.ts'; import { ingest } from '../../utils/indices.ts'; 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 @@ -187,6 +196,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 7ce8942d..24fed8d0 100644 --- a/src/commands/org_data/integrations/beyondinsight_integration.ts +++ b/src/commands/org_data/integrations/beyondinsight_integration.ts @@ -9,6 +9,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -101,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[] = []; @@ -109,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); @@ -133,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 })), ); @@ -155,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', @@ -164,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(); @@ -195,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', @@ -204,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(); @@ -227,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', @@ -237,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; @@ -274,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', @@ -283,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(); @@ -309,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 b4545971..f0dc4b76 100644 --- a/src/commands/org_data/integrations/bitwarden_integration.ts +++ b/src/commands/org_data/integrations/bitwarden_integration.ts @@ -9,6 +9,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -106,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[] = []; @@ -113,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); @@ -131,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 })), ); @@ -159,6 +161,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { dataset: 'bitwarden.event', @@ -168,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); @@ -192,6 +195,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawMember), data_stream: { dataset: 'bitwarden.member', @@ -201,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 }); @@ -222,6 +226,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawGroup), data_stream: { dataset: 'bitwarden.group', @@ -232,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); @@ -259,6 +264,7 @@ export class BitwardenIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawPolicy), data_stream: { dataset: 'bitwarden.policy', @@ -269,7 +275,7 @@ export class BitwardenIntegration extends BaseIntegration { }); } - private createCollectionDocuments(): IntegrationDocument[] { + private createCollectionDocuments(centralAgent: AgentData): IntegrationDocument[] { const collectionNames = [ 'Engineering Secrets', 'Production Credentials', @@ -291,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 81f708a9..a29d08fe 100644 --- a/src/commands/org_data/integrations/box_integration.ts +++ b/src/commands/org_data/integrations/box_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -155,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)); } } @@ -167,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 })), ); @@ -317,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 7cfa1be3..16872a2c 100644 --- a/src/commands/org_data/integrations/canva_integration.ts +++ b/src/commands/org_data/integrations/canva_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -139,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)); } } @@ -151,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 })), ); @@ -226,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 a74ae743..2392d515 100644 --- a/src/commands/org_data/integrations/cisco_duo_integration.ts +++ b/src/commands/org_data/integrations/cisco_duo_integration.ts @@ -88,7 +88,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 })), ); @@ -154,6 +154,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 6b81f501..dd3f766d 100644 --- a/src/commands/org_data/integrations/cloud_asset_integration.ts +++ b/src/commands/org_data/integrations/cloud_asset_integration.ts @@ -76,12 +76,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', }, @@ -143,6 +144,7 @@ export class CloudAssetIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), event: { kind: 'asset', }, @@ -210,6 +212,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 2af57f8c..531c0d3d 100644 --- a/src/commands/org_data/integrations/cloudflare_logpush_integration.ts +++ b/src/commands/org_data/integrations/cloudflare_logpush_integration.ts @@ -40,6 +40,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 @@ -48,13 +49,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); @@ -62,7 +63,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( @@ -126,6 +131,7 @@ export class CloudflareLogpushIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', @@ -135,7 +141,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 }, @@ -183,6 +192,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 b78fc219..d79427ad 100644 --- a/src/commands/org_data/integrations/cloudtrail_integration.ts +++ b/src/commands/org_data/integrations/cloudtrail_integration.ts @@ -287,6 +287,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser, employee, account!, + org, assumedRoleArn, callerIdentityTime, accessKeyId, @@ -310,6 +311,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser, employee, account!, + org, service, assumedRoleArn, apiTime, @@ -388,6 +390,7 @@ export class CloudTrailIntegration extends BaseIntegration { this.createServiceAccountApiEvent( iamUser, account!, + org, service, timestamp, accessKeyId, @@ -442,7 +445,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; @@ -614,6 +619,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), tags: CLOUDTRAIL_TAGS, data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, @@ -627,6 +633,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser: CloudIamUser, employee: Employee, account: CloudAccount, + org: Organization, assumedRoleArn: string, timestamp: string, accessKeyId: string, @@ -674,6 +681,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), tags: CLOUDTRAIL_TAGS, data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, @@ -687,6 +695,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser: CloudIamUser, employee: Employee, account: CloudAccount, + org: Organization, service: string, assumedRoleArn: string, timestamp: string, @@ -755,6 +764,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), tags: CLOUDTRAIL_TAGS, data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, @@ -767,6 +777,7 @@ export class CloudTrailIntegration extends BaseIntegration { private createServiceAccountApiEvent( iamUser: CloudIamUser, account: CloudAccount, + org: Organization, service: string, timestamp: string, accessKeyId: string, @@ -809,6 +820,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), tags: CLOUDTRAIL_TAGS, data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, @@ -822,6 +834,7 @@ export class CloudTrailIntegration extends BaseIntegration { iamUser: CloudIamUser, employee: Employee, account: CloudAccount, + org: Organization, timestamp: string, isFailure: boolean, ): IntegrationDocument { @@ -877,6 +890,7 @@ export class CloudTrailIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), message: JSON.stringify(rawEvent), tags: CLOUDTRAIL_TAGS, data_stream: { namespace: 'default', type: 'logs', dataset: 'aws.cloudtrail' }, diff --git a/src/commands/org_data/integrations/crowdstrike_integration.ts b/src/commands/org_data/integrations/crowdstrike_integration.ts index 8bf7e4ef..fdc6bdae 100644 --- a/src/commands/org_data/integrations/crowdstrike_integration.ts +++ b/src/commands/org_data/integrations/crowdstrike_integration.ts @@ -206,7 +206,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); @@ -263,9 +263,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; @@ -375,9 +376,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; @@ -408,7 +410,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; @@ -419,7 +424,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)); } } @@ -430,23 +435,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); } } @@ -460,6 +468,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 = { @@ -475,6 +484,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; @@ -484,6 +494,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); @@ -533,13 +544,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); @@ -553,13 +565,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); @@ -580,10 +599,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); @@ -600,10 +629,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); @@ -620,13 +653,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); @@ -660,13 +694,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); @@ -687,6 +722,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 88827cc8..9f6c3df8 100644 --- a/src/commands/org_data/integrations/cyberark_pas_integration.ts +++ b/src/commands/org_data/integrations/cyberark_pas_integration.ts @@ -9,6 +9,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -191,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)); } } @@ -203,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 })), ); @@ -294,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 39166d67..956b64b9 100644 --- a/src/commands/org_data/integrations/endpoint_integration.ts +++ b/src/commands/org_data/integrations/endpoint_integration.ts @@ -7,13 +7,12 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + ELASTIC_AGENT_VERSION, } from './base_integration.ts'; import { type Organization, type CorrelationMap, type Employee, type Device } from '../types.ts'; import { faker } from '@faker-js/faker'; import { MALWARE_HASHES } from '../data/threat_intel_data.ts'; -const ENDPOINT_AGENT_VERSION = '8.17.4'; - const PROCESS_ACTIONS: Array<{ action: string; type: string[] }> = [ { action: 'start', type: ['start'] }, { action: 'exec', type: ['start'] }, @@ -285,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; @@ -344,7 +343,7 @@ export class EndpointIntegration extends BaseIntegration { this.generateAlertDocument( employee, device, - device.crowdstrikeAgentId, + device.elasticAgentId, device.id, device.platform as string, ), @@ -385,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, }; } @@ -412,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], @@ -478,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', @@ -535,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, @@ -606,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)], @@ -676,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: { @@ -773,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, @@ -837,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 89cc1f48..a91e10ea 100644 --- a/src/commands/org_data/integrations/entra_id_integration.ts +++ b/src/commands/org_data/integrations/entra_id_integration.ts @@ -57,10 +57,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) { @@ -77,7 +78,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; @@ -106,6 +107,7 @@ export class EntraIdIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), azure_ad: { userPrincipalName: employee.email, mail: employee.email, @@ -183,6 +185,7 @@ export class EntraIdIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), azure_ad: { accountEnabled: true, displayName: entraDisplayName, @@ -235,9 +238,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 657375a4..c7567c1a 100644 --- a/src/commands/org_data/integrations/forgerock_integration.ts +++ b/src/commands/org_data/integrations/forgerock_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -75,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[] = []; @@ -83,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)); } } @@ -109,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 })), ); @@ -145,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); @@ -201,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([ @@ -232,6 +248,7 @@ export class ForgeRockIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify({ payload }), data_stream: { namespace: 'default', @@ -241,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 }, @@ -281,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 8c212c16..1a378bd2 100644 --- a/src/commands/org_data/integrations/gcp_integration.ts +++ b/src/commands/org_data/integrations/gcp_integration.ts @@ -12,6 +12,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -173,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[] = []; @@ -182,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); @@ -200,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 })), @@ -262,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(); @@ -338,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 0618ce63..be0a7380 100644 --- a/src/commands/org_data/integrations/github_integration.ts +++ b/src/commands/org_data/integrations/github_integration.ts @@ -94,6 +94,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 d041c667..777b791a 100644 --- a/src/commands/org_data/integrations/gitlab_integration.ts +++ b/src/commands/org_data/integrations/gitlab_integration.ts @@ -10,6 +10,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -159,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[] = []; @@ -168,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({ @@ -176,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)); } } @@ -191,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 })), ); @@ -224,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([ @@ -299,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 })), ); @@ -344,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 3cb2e388..6398d3a6 100644 --- a/src/commands/org_data/integrations/google_workspace_integration.ts +++ b/src/commands/org_data/integrations/google_workspace_integration.ts @@ -203,6 +203,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; @@ -413,6 +414,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; @@ -709,6 +711,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 fd3b7455..717206b9 100644 --- a/src/commands/org_data/integrations/hashicorp_vault_integration.ts +++ b/src/commands/org_data/integrations/hashicorp_vault_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -104,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[] = []; @@ -112,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); @@ -126,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 })), ); @@ -181,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 })), ); @@ -194,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 42374bc6..1166c761 100644 --- a/src/commands/org_data/integrations/island_browser_integration.ts +++ b/src/commands/org_data/integrations/island_browser_integration.ts @@ -9,6 +9,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap, type Device } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -127,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 }); @@ -140,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)); } } } @@ -151,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)); } } @@ -167,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]; @@ -201,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; @@ -211,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); @@ -258,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; @@ -268,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( @@ -326,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); @@ -356,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 f57d7840..ccac0485 100644 --- a/src/commands/org_data/integrations/jamf_pro_integration.ts +++ b/src/commands/org_data/integrations/jamf_pro_integration.ts @@ -189,6 +189,7 @@ export class JamfProIntegration extends BaseIntegration { return { '@timestamp': this.getRandomTimestamp(24), + agent: this.buildLocalAgent(device, hostname), message: inventoryPayload, data_stream: { namespace: 'default', @@ -215,9 +216,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 c410a9e3..a7e9a924 100644 --- a/src/commands/org_data/integrations/jumpcloud_integration.ts +++ b/src/commands/org_data/integrations/jumpcloud_integration.ts @@ -158,11 +158,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)); } } @@ -170,7 +171,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 })), ); @@ -238,6 +243,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 963ca185..1376e66d 100644 --- a/src/commands/org_data/integrations/keeper_integration.ts +++ b/src/commands/org_data/integrations/keeper_integration.ts @@ -145,11 +145,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)); } } @@ -157,7 +158,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 })), ); @@ -189,6 +194,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 132bdda9..4e24895e 100644 --- a/src/commands/org_data/integrations/keycloak_integration.ts +++ b/src/commands/org_data/integrations/keycloak_integration.ts @@ -186,11 +186,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)); } } @@ -198,7 +199,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 })), ); @@ -224,6 +229,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 42b84fcb..9e073768 100644 --- a/src/commands/org_data/integrations/lastpass_integration.ts +++ b/src/commands/org_data/integrations/lastpass_integration.ts @@ -166,17 +166,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); @@ -185,7 +186,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 }); @@ -217,6 +221,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; @@ -229,7 +234,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 })), ); @@ -248,6 +256,7 @@ export class LastPassIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: centralAgent, message: JSON.stringify(rawEvent), data_stream: { namespace: 'default', @@ -257,7 +266,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); @@ -285,6 +297,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 8cf429c2..804e16de 100644 --- a/src/commands/org_data/integrations/lyve_cloud_integration.ts +++ b/src/commands/org_data/integrations/lyve_cloud_integration.ts @@ -58,11 +58,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)); } } @@ -70,7 +71,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 })), ); @@ -132,6 +137,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 794eeaf5..fe0a27be 100644 --- a/src/commands/org_data/integrations/mattermost_integration.ts +++ b/src/commands/org_data/integrations/mattermost_integration.ts @@ -65,11 +65,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)); } } @@ -77,7 +78,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 })), ); @@ -121,6 +126,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 d00e7873..c2a729cb 100644 --- a/src/commands/org_data/integrations/mongodb_atlas_integration.ts +++ b/src/commands/org_data/integrations/mongodb_atlas_integration.ts @@ -73,15 +73,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)); } } @@ -90,7 +91,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 })), ); @@ -122,6 +126,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' }, @@ -132,6 +137,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 })), @@ -179,6 +185,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 13d6147f..02e3aa25 100644 --- a/src/commands/org_data/integrations/o365_integration.ts +++ b/src/commands/org_data/integrations/o365_integration.ts @@ -81,6 +81,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 b76fb90c..624ae1b8 100644 --- a/src/commands/org_data/integrations/okta_integration.ts +++ b/src/commands/org_data/integrations/okta_integration.ts @@ -50,8 +50,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); @@ -59,8 +60,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) { @@ -68,7 +69,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; @@ -102,6 +103,7 @@ export class OktaIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), event: { action: 'user-discovered' }, okta: { id: employee.oktaUserId, @@ -204,6 +206,7 @@ export class OktaIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildCentralAgent(org), event: { action: 'device-discovered' }, okta: { id: device.id, @@ -264,9 +267,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', @@ -289,9 +294,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 ec31c8a7..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 @@ -38,6 +39,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 */ @@ -81,6 +111,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']; @@ -214,7 +253,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; @@ -307,6 +608,7 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', @@ -387,6 +689,7 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', @@ -461,6 +764,7 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', @@ -544,6 +848,7 @@ export class OktaSystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, message: JSON.stringify(rawEvent), + agent: this.buildCentralAgent(org), data_stream: { namespace: 'default', type: 'logs', @@ -560,6 +865,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(); @@ -573,8 +880,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', }, @@ -619,6 +926,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); + + log.info( + ` 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); + + log.info( + ` - ${rogue.firstName} ${rogue.lastName} (${rogue.email}): ` + + `${burstEvents.length} admin burst events, ` + + `${sessionEvents.length} multi-country sessions, ` + + `${rareEvents.length} rare IP/region events`, + ); + } + + log.info(` 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 95243b5b..459307c9 100644 --- a/src/commands/org_data/integrations/onepassword_integration.ts +++ b/src/commands/org_data/integrations/onepassword_integration.ts @@ -132,7 +132,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); @@ -163,6 +163,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; @@ -214,6 +215,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 97b5f0d2..b05cc99a 100644 --- a/src/commands/org_data/integrations/ping_directory_integration.ts +++ b/src/commands/org_data/integrations/ping_directory_integration.ts @@ -14,6 +14,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { log } from '../../../utils/logger.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; @@ -104,6 +105,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(); @@ -118,7 +120,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); @@ -134,6 +138,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 @@ -164,6 +169,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 d991421f..9d1ba84e 100644 --- a/src/commands/org_data/integrations/ping_one_integration.ts +++ b/src/commands/org_data/integrations/ping_one_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -109,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(); @@ -116,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)); } } @@ -131,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 })), @@ -225,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 46d6c29a..2e598d39 100644 --- a/src/commands/org_data/integrations/sailpoint_integration.ts +++ b/src/commands/org_data/integrations/sailpoint_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -146,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, '-'); @@ -154,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)); } } @@ -169,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 })), @@ -231,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 3f5d6363..4e2592a0 100644 --- a/src/commands/org_data/integrations/servicenow_integration.ts +++ b/src/commands/org_data/integrations/servicenow_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -90,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( @@ -107,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 @@ -117,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); @@ -133,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]); @@ -190,6 +195,7 @@ export class ServiceNowIntegration extends BaseIntegration { return { '@timestamp': openedAt, + agent: centralAgent, message: JSON.stringify(rawServiceNowEvent), _conf: { timestamp_field: 'sys_updated_on', @@ -212,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')}`; @@ -260,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 7df41a51..7c7ccb42 100644 --- a/src/commands/org_data/integrations/slack_integration.ts +++ b/src/commands/org_data/integrations/slack_integration.ts @@ -8,6 +8,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -76,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)); } } @@ -92,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 })), ); @@ -150,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 bcf6419e..a4faf097 100644 --- a/src/commands/org_data/integrations/system_integration.ts +++ b/src/commands/org_data/integrations/system_integration.ts @@ -383,6 +383,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -415,6 +416,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -446,6 +448,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -468,6 +471,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -491,6 +495,7 @@ export class SystemIntegration extends BaseIntegration { return { '@timestamp': timestamp, + agent: this.buildServerAgent(host), message, input: { type: 'log' }, data_stream: { @@ -1170,6 +1175,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 43ec98ef..b33bb60e 100644 --- a/src/commands/org_data/integrations/teleport_integration.ts +++ b/src/commands/org_data/integrations/teleport_integration.ts @@ -126,11 +126,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)); } } @@ -138,7 +139,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 })), ); @@ -181,6 +185,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 204d57e8..6f6c2316 100644 --- a/src/commands/org_data/integrations/thycotic_ss_integration.ts +++ b/src/commands/org_data/integrations/thycotic_ss_integration.ts @@ -131,11 +131,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)); } } @@ -143,7 +144,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 })), ); @@ -175,6 +180,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 c9762fbf..ae22e053 100644 --- a/src/commands/org_data/integrations/ti_abusech_integration.ts +++ b/src/commands/org_data/integrations/ti_abusech_integration.ts @@ -7,6 +7,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { type Organization, type CorrelationMap } from '../types.ts'; import { faker } from '@faker-js/faker'; @@ -38,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); @@ -56,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); @@ -92,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([ @@ -139,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 e056b78b..daf0801c 100644 --- a/src/commands/org_data/integrations/workday_integration.ts +++ b/src/commands/org_data/integrations/workday_integration.ts @@ -13,6 +13,7 @@ import { BaseIntegration, type IntegrationDocument, type DataStreamConfig, + type AgentData, } from './base_integration.ts'; import { log } from '../../../utils/logger.ts'; import { type Organization, type Employee, type CorrelationMap } from '../types.ts'; @@ -103,6 +104,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(); @@ -114,7 +116,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); @@ -128,6 +130,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 @@ -243,6 +246,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 592c4942..f7f97be3 100644 --- a/src/commands/org_data/integrations/zoom_integration.ts +++ b/src/commands/org_data/integrations/zoom_integration.ts @@ -122,11 +122,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)); } } @@ -138,6 +139,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 })), @@ -244,6 +246,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 4c1876cf..79dfb8f9 100644 --- a/src/commands/org_data/integrations/zscaler_zia_integration.ts +++ b/src/commands/org_data/integrations/zscaler_zia_integration.ts @@ -112,8 +112,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; @@ -174,8 +176,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; @@ -222,8 +226,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 d71404de..bce69ce6 100644 --- a/src/commands/org_data/org_data_generator.ts +++ b/src/commands/org_data/org_data_generator.ts @@ -10,6 +10,7 @@ import { type Employee, type Device, type Host, + type CentralAgent, type CloudAccount, type CloudResource, type 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; } /**