Skip to content
This repository has been archived by the owner on Dec 4, 2023. It is now read-only.

INT-7921: ingest applications #157

Merged
merged 4 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/jupiterone.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ The following entities are created:
| Account | `crowdstrike_account` | `Account` |
| Application | `crowdstrike_detected_application` | `Application` |
| Device Sensor Agent | `crowdstrike_sensor` | `HostAgent` |
| Discover Application | `crowdstrike_discover_application` | `Application` |
| Prevention Policy | `crowdstrike_prevention_policy` | `ControlPolicy` |
| Service | `crowdstrike_endpoint_protection` | `Service` |
| Vulnerability | `crowdstrike_vulnerability` | `Finding` |
Expand All @@ -118,6 +119,7 @@ The following relationships are created:
| `crowdstrike_detected_application` | **HAS** | `crowdstrike_vulnerability` |
| `crowdstrike_prevention_policy` | **ENFORCES** | `crowdstrike_endpoint_protection` |
| `crowdstrike_sensor` | **ASSIGNED** | `crowdstrike_prevention_policy` |
| `crowdstrike_sensor` | **HAS** | `crowdstrike_discover_application` |
| `crowdstrike_sensor` | **HAS** | `crowdstrike_zero_trust_assessment` |
| `crowdstrike_vulnerability` | **EXPLOITS** | `crowdstrike_sensor` |

Expand Down
13 changes: 8 additions & 5 deletions src/crowdstrike/CrowdStrikeApiGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
RateLimitState,
ResourcesResponse,
} from './types';
import { RequestInit } from 'node-fetch';
import fetch, { RequestInit } from 'node-fetch';
import { FalconAPIResourceIterationCallback } from './FalconAPIClient';
import { ICrowdStrikeApiClientQueryBuilder } from './CrowdStrikeApiClientQueryBuilder';
import { Total } from './Total';
Expand All @@ -24,7 +24,7 @@ function getUnixTimeNow() {
return Date.now() / 1000;
}

async function sleep(ms) {
async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

Expand Down Expand Up @@ -56,13 +56,13 @@ export class CrowdStrikeApiGateway {
private readonly rateLimitConfig: RateLimitConfig = DEFAULT_RATE_LIMIT_CONFIG;
private total: Total;
private queryBuilder: ICrowdStrikeApiClientQueryBuilder;
private fetcher;
private fetcher: typeof fetch;

constructor(
credentials: OAuth2ClientCredentials,
logger: IntegrationLogger,
queryBuilder: ICrowdStrikeApiClientQueryBuilder,
fetcher,
fetcher: typeof fetch,
attemptOptions?: AttemptOptions,
) {
this.queryBuilder = queryBuilder;
Expand Down Expand Up @@ -361,8 +361,11 @@ export class CrowdStrikeApiGateway {
'pagination response details',
);

paginationParams = response.meta.pagination as PaginationMeta;
seen += response.resources.length;
paginationParams = {
...response.meta.pagination,
offset: seen,
} as PaginationMeta;

const baseUrl = this.queryBuilder.buildResourcePathUrl(
availabilityZone,
Expand Down
57 changes: 55 additions & 2 deletions src/crowdstrike/FalconAPIClient.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { URLSearchParams } from 'url';

import {
DiscoverApplication,
DiscoverApplicationIdentifier,
Device,
DeviceIdentifier,
OAuth2Token,
Expand Down Expand Up @@ -139,6 +141,31 @@ export class FalconAPIClient {
resourcePath: '/zero-trust-assessment/queries/assessments/v1',
});
}

/**
* Iterates the known device applications, providing pages
* of the collection based on the provided query to the provided callback.
*
* @param input
* @returns Promise
*/
public async iterateApplications(input: {
callback: FalconAPIResourceIterationCallback<DiscoverApplication>;
query?: QueryParams;
}): Promise<void> {
return this.crowdStrikeApiGateway.paginateResources<DiscoverApplicationIdentifier>(
{
callback: async (appsIds) => {
if (appsIds.length) {
return input.callback(await this.fetchApplications(appsIds));
}
},
query: input.query,
resourcePath: '/discover/queries/applications/v1',
},
);
}

/**
* Iterates prevention policies using the "combined" API, providing pages of
* the collection to the provided callback.
Expand Down Expand Up @@ -195,6 +222,7 @@ export class FalconAPIClient {

return response.resources;
}

/***
* ZTA details
* https://falconpy.io/Service-Collections/Zero-Trust-Assessment.html#getassessmentv1
Expand All @@ -204,11 +232,11 @@ export class FalconAPIClient {
for (const id of ids) {
searchParams.append('ids', id);
}

const availabilityZone = this.crowdStrikeApiGateway.getAvailabilityZone();

const response =
await this.crowdStrikeApiGateway.executeAPIRequestWithRetries<
ResourcesResponse<any>
ResourcesResponse<ZeroTrustAssessment>
>(
`https://api.${availabilityZone}crowdstrike.com/zero-trust-assessment/entities/assessments/v1?` +
searchParams,
Expand All @@ -222,4 +250,29 @@ export class FalconAPIClient {

return response.resources;
}

/**
* Discover Service - applications.
* Swagger: https://assets.falcon.us-2.crowdstrike.com/support/api/swagger-us2.html#/discover/get-applications
*/
private async fetchApplications(
ids: string[],
): Promise<DiscoverApplication[]> {
const availabilityZone = this.crowdStrikeApiGateway.getAvailabilityZone();
const queryParams = ids.map((id) => `ids=${id}`).join('&');
const response =
await this.crowdStrikeApiGateway.executeAPIRequestWithRetries<
ResourcesResponse<DiscoverApplication>
>(
`https://api.${availabilityZone}crowdstrike.com/discover/entities/applications/v1?${queryParams}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
accept: 'application/json',
},
},
);
return response.resources;
}
}
48 changes: 46 additions & 2 deletions src/crowdstrike/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export type ResourcesResponse<T> = {
* The identifier of a discovered device within the CrowdStrike Falcon platform.
*/
export type DeviceIdentifier = string;
export type DiscoverApplicationIdentifier = string;

export type Device = {
device_id: DeviceIdentifier;
Expand Down Expand Up @@ -187,10 +188,10 @@ export type Vulnerability = {
site_name: string;
system_manufacturer: string;
};
apps?: Application[];
apps?: DetectedApplication[];
};

export type Application = {
export type DetectedApplication = {
product_name_version: string;
sub_status: string;
remediation?: {
Expand All @@ -201,6 +202,49 @@ export type Application = {
};
};

export interface DiscoverApplication {
id: DiscoverApplicationIdentifier;
category?: string;
cid: string;
name?: string;
vendor?: string;
version?: string;
name_vendor?: string;
name_vendor_version?: string;
versioning_scheme?: string;
architectures?: string[];
groups?: string[];
installation_paths?: string[];
installation_timestamp?: string;
first_seen_timestamp?: string;
last_updated_timestamp?: string;
last_used_timestamp?: string;
is_suspicious?: boolean;
is_normalized?: boolean;
host?: Host;
last_used_file_hash?: string;
last_used_file_name?: string;
last_used_user_name?: string;
last_used_user_sid?: string;
}

export interface Host {
id: string;
aid?: string;
country?: string;
platform_name?: string;
os_version?: string;
kernel_version?: string;
product_type_desc?: string;
system_manufacturer?: string;
agent_version?: string;
external_ip?: string;
hostname?: string;
current_mac_address?: string;
current_network_prefix?: string;
internet_exposure?: string;
}

export type ZTA_Score = {
aid: string;
score: number;
Expand Down
10 changes: 5 additions & 5 deletions src/jupiterone/converters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { createMockExecutionContext } from '@jupiterone/integration-sdk-testing';

import {
Application,
DetectedApplication,
Device,
PreventionPolicy,
Vulnerability,
Expand All @@ -18,7 +18,7 @@ import {
createPreventionPolicyEntity,
createProtectionServiceEntity,
createVulnerabilityEntity,
createApplicationEntity,
createDetectedApplicationEntity,
} from './converters';

describe('createAccountEntity', () => {
Expand Down Expand Up @@ -398,9 +398,9 @@ describe('createVulnerabilityEntity', () => {
});
});

describe('createApplicationEntity', () => {
describe('createDetectedApplicationEntity', () => {
test('properties transferred', () => {
const source: Application = {
const source: DetectedApplication = {
product_name_version: 'Windows Server 2016 1607',
sub_status: 'open',
remediation: {
Expand All @@ -411,7 +411,7 @@ describe('createApplicationEntity', () => {
},
};

expect(createApplicationEntity(source)).toEqual({
expect(createDetectedApplicationEntity(source)).toEqual({
_class: ['Application'],
_key: 'windows-server-2016-1607',
_rawData: [
Expand Down
42 changes: 37 additions & 5 deletions src/jupiterone/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
import { Entities } from '../steps/constants';

import {
Application,
DiscoverApplication,
DetectedApplication,
Device,
PreventionPolicy,
Vulnerability,
Expand Down Expand Up @@ -93,6 +94,10 @@ export function buildEc2InstanceArn(source: Device): string | undefined {
return `arn:aws:ec2:${region}:${serviceProviderAccountId}:instance/${instanceId}`;
}

export function createSensorAgentKey(deviceId: string): string {
return deviceId;
}

export function createSensorAgentEntity(source: Device) {
return createIntegrationEntity({
entityData: {
Expand All @@ -106,7 +111,7 @@ export function createSensorAgentEntity(source: Device) {
// version is upgraded. This is listed in their API documentation
// and it notes that this means that it's a valid case for a Host to
// potentially have multiple sensors protecting it.
_key: source.device_id,
_key: createSensorAgentKey(source.device_id),
name: source.hostname,
function: ['anti-malware', 'activity-monitor'],
firstSeenOn: parseTimePropertyValue(source.first_seen),
Expand Down Expand Up @@ -216,13 +221,13 @@ export function createVulnerabilityEntity(source: Vulnerability) {
});
}

export function createApplicationEntity(source: Application) {
export function createDetectedApplicationEntity(source: DetectedApplication) {
return createIntegrationEntity({
entityData: {
source,
assign: {
_class: Entities.APPLICATION._class,
_type: Entities.APPLICATION._type,
_class: Entities.DETECTED_APPLICATION._class,
_type: Entities.DETECTED_APPLICATION._type,
_key: source.product_name_version.toLowerCase().replace(/\s/g, '-'),
name: source.product_name_version,
open: source.sub_status.toLowerCase() === 'open',
Expand All @@ -232,6 +237,33 @@ export function createApplicationEntity(source: Application) {
},
});
}

export function createDiscoverApplicationEntity(source: DiscoverApplication) {
return createIntegrationEntity({
entityData: {
source,
assign: {
_class: Entities.DISCOVER_APPLICATION._class,
_type: Entities.DISCOVER_APPLICATION._type,
_key: source.id,
name: source.name || source.id,
id: source.id,
vendor: source.vendor,
version: source.version,
versioningScheme: source.versioning_scheme,
architectures: source.architectures,
isSuspicious: source.is_suspicious,
isNormalized: source.is_normalized,
installationPaths: source.installation_paths,
installedOn: parseTimePropertyValue(source.installation_timestamp),
firstSeenOn: parseTimePropertyValue(source.first_seen_timestamp),
lastUpdatedOn: parseTimePropertyValue(source.last_updated_timestamp),
lastUsedOn: parseTimePropertyValue(source.last_used_timestamp),
},
},
});
}

export function createZeroTrustAssessmentEntity(source: ZeroTrustAssessment) {
const unmet_os_signals = source.assessment_items.os_signals.filter(
(s) => s.meets_criteria == 'no',
Expand Down
Loading