@jupiterone/graph-cisco-amp
Advanced tools
Comparing version 0.7.2 to 1.0.0
@@ -11,2 +11,24 @@ # Changelog | ||
## 1.0.0 - 2022-04-11 | ||
### Added | ||
- Added new findings step creating the following entities and relationships: | ||
- `cisco_amp_finding` | ||
- `cisco_amp_vulnerability` | ||
- `cisco_amp_finding_is_vulnerability` | ||
- `cisco_amp_endpoint_identified_finding` | ||
- Added `getStepStartStates` which disables the `findings` step by default. To | ||
enable this step, the IntegrationConfig must have `disableFindingsStep` = | ||
false. | ||
### Changed | ||
- Client now uses `iterateResources` pattern for all steps. | ||
- Endpoint protects device relationship is now correctly classified as a | ||
`mappedRelationship` | ||
## 0.7.2 - 2022-03-31 | ||
@@ -13,0 +35,0 @@ |
import { Request } from 'node-fetch'; | ||
import { CiscoAmpComputer, CiscoAmpApiResponse } from './types'; | ||
import { CiscoAmpComputer, CiscoAmpApiResponse, CiscoAmpVulnerability, CiscoAmpComputerWithVulnerability } from './types'; | ||
export interface ServicesClientInput { | ||
@@ -8,2 +8,3 @@ apiEndpoint: string; | ||
} | ||
export declare type ResourceIteratee<T> = (each: T) => Promise<void>; | ||
/** | ||
@@ -17,5 +18,42 @@ * Services Api | ||
constructor(config: ServicesClientInput); | ||
getVersion(): Promise<CiscoAmpApiResponse>; | ||
iterateComputers(): Promise<CiscoAmpComputer[]>; | ||
iterateAll(url: string): Promise<CiscoAmpComputer[]>; | ||
/** | ||
* Gets Cisco AMP Api version | ||
* @returns Promise<CiscoAmpApiResponse<Record<string, never>>> | ||
*/ | ||
getVersion(): Promise<CiscoAmpApiResponse<Record<string, never>>>; | ||
/** | ||
* iterateComputers iterates over all computers | ||
* and calls the provided `ResourceIteratee` for each Computer. | ||
* | ||
* @param fn ResourceIteratee that will be called for each Computer | ||
* @returns Promise<void> | ||
*/ | ||
iterateComputers(fn: ResourceIteratee<CiscoAmpComputer>): Promise<void>; | ||
/** | ||
* iterateVulnerabilities iterates over all vulnerabilities | ||
* and calls the provided `ResourceIteratee` for each vulnerability. | ||
* | ||
* @param fn ResourceIteratee function tha will be called for each Vulnerability | ||
* @returns Promise<void> | ||
*/ | ||
iterateVulnerabilities(fn: ResourceIteratee<CiscoAmpVulnerability>): Promise<void>; | ||
/** | ||
* iteratesComputersWithVulnerability iterates computers with a vulnerability | ||
* identified by a sha256 hash. | ||
* | ||
* This is useful when a vulnerability is present on more than 1000 computers | ||
* as the `/v1/vulnerabilities` response will only contain the first 1000 | ||
* | ||
* @param sha256 The sha256 hash of the vulnerability | ||
* @returns Promise<void> | ||
*/ | ||
iterateComputersWithVulnerability(sha256: string, fn: ResourceIteratee<CiscoAmpComputerWithVulnerability>): Promise<void>; | ||
/** | ||
* createResourceIterator creates a function that will paginate the resources | ||
* and call the provided `ResourceIteratee` for each resource. | ||
* | ||
* @param url the url whose resources will be iterated | ||
* @returns a function that will call ResourceIteratee for each resource | ||
*/ | ||
createResourceIterator(url: string): <T>(fn: ResourceIteratee<T>) => Promise<void>; | ||
fetch<T = object>(url: string, queryParams?: { | ||
@@ -22,0 +60,0 @@ [param: string]: string | string[]; |
@@ -21,22 +21,70 @@ "use strict"; | ||
} | ||
/** | ||
* Gets Cisco AMP Api version | ||
* @returns Promise<CiscoAmpApiResponse<Record<string, never>>> | ||
*/ | ||
getVersion() { | ||
return this.fetch('/v1/version'); | ||
} | ||
iterateComputers() { | ||
return this.iterateAll('/v1/computers'); | ||
/** | ||
* iterateComputers iterates over all computers | ||
* and calls the provided `ResourceIteratee` for each Computer. | ||
* | ||
* @param fn ResourceIteratee that will be called for each Computer | ||
* @returns Promise<void> | ||
*/ | ||
async iterateComputers(fn) { | ||
return await this.createResourceIterator('/v1/computers')(fn); | ||
} | ||
async iterateAll(url) { | ||
const data = []; | ||
/** | ||
* iterateVulnerabilities iterates over all vulnerabilities | ||
* and calls the provided `ResourceIteratee` for each vulnerability. | ||
* | ||
* @param fn ResourceIteratee function tha will be called for each Vulnerability | ||
* @returns Promise<void> | ||
*/ | ||
async iterateVulnerabilities(fn) { | ||
return await this.createResourceIterator('/v1/vulnerabilities')(fn); | ||
} | ||
/** | ||
* iteratesComputersWithVulnerability iterates computers with a vulnerability | ||
* identified by a sha256 hash. | ||
* | ||
* This is useful when a vulnerability is present on more than 1000 computers | ||
* as the `/v1/vulnerabilities` response will only contain the first 1000 | ||
* | ||
* @param sha256 The sha256 hash of the vulnerability | ||
* @returns Promise<void> | ||
*/ | ||
async iterateComputersWithVulnerability(sha256, fn) { | ||
return await this.createResourceIterator(`/v1/vulnerabilities/${sha256}/computers`)(fn); | ||
} | ||
/** | ||
* createResourceIterator creates a function that will paginate the resources | ||
* and call the provided `ResourceIteratee` for each resource. | ||
* | ||
* @param url the url whose resources will be iterated | ||
* @returns a function that will call ResourceIteratee for each resource | ||
*/ | ||
createResourceIterator(url) { | ||
const limit = 500; | ||
let offset = 0; | ||
let total = 0; | ||
do { | ||
const response = await this.fetch(url, { | ||
offset: offset.toString(), | ||
}); | ||
total = response.metadata.results.total; | ||
offset += limit; | ||
data.push(...response.data); | ||
} while (offset < total); | ||
return data; | ||
return async (fn) => { | ||
do { | ||
const response = await this.fetch(url, { | ||
offset: offset.toString(), | ||
}); | ||
total = response.metadata.results.total; | ||
offset += limit; | ||
if (Array.isArray(response.data)) { | ||
for (const data of response.data) { | ||
await fn(data); | ||
} | ||
} | ||
else { | ||
await fn(response.data); | ||
} | ||
} while (offset < total); | ||
}; | ||
} | ||
@@ -43,0 +91,0 @@ fetch(url, queryParams = {}, request) { |
@@ -1,2 +0,1 @@ | ||
import { Opaque } from 'type-fest'; | ||
export interface PaginationInput { | ||
@@ -12,3 +11,6 @@ limit: string; | ||
} | ||
export interface CiscoAmpApiResponse { | ||
/** | ||
* CiscoAmpApiResponse structure | ||
*/ | ||
export interface CiscoAmpApiResponse<T> { | ||
version: string; | ||
@@ -21,6 +23,92 @@ metadata: { | ||
}; | ||
data: T[] | T; | ||
} | ||
export interface ListComputersResponse extends CiscoAmpApiResponse { | ||
data: CiscoAmpComputer[]; | ||
/** | ||
* CiscoAmpComputer resource structure | ||
*/ | ||
export interface CiscoAmpComputer { | ||
connector_guid: string; | ||
hostname: string; | ||
windows_processor_id: string; | ||
active: boolean; | ||
links: { | ||
computer: string; | ||
trajectory: string; | ||
group: string; | ||
}; | ||
connector_version: string; | ||
operating_system: string; | ||
internal_ips: string[]; | ||
external_ip: string; | ||
group_guid: string; | ||
install_date: string; | ||
is_compromised: boolean; | ||
demo: boolean; | ||
network_addresses: { | ||
mac: string; | ||
ip: string; | ||
}[]; | ||
policy: { | ||
guid: string; | ||
name: string; | ||
}; | ||
groups: { | ||
guid: string; | ||
name: string; | ||
}[]; | ||
last_seen: string; | ||
faults: any[]; | ||
isolation: { | ||
available: boolean; | ||
status: string; | ||
}; | ||
orbital: { | ||
status: string; | ||
}; | ||
} | ||
export declare type CiscoAmpComputer = Opaque<any, 'CiscoAmpComputer'>; | ||
export interface CiscoAmpComputerWithVulnerability { | ||
connector_guid: string; | ||
hostname: string; | ||
windows_processor_id: string; | ||
active: boolean; | ||
links: { | ||
computer: string; | ||
trajectory: string; | ||
group: string; | ||
}; | ||
} | ||
export interface CVE { | ||
id: string; | ||
link: string; | ||
cvss: number; | ||
} | ||
export interface CiscoAmpVulnerability { | ||
application: string; | ||
version: string; | ||
file: { | ||
filename: string; | ||
identity: { | ||
sha256: string; | ||
}; | ||
}; | ||
cves: CVE[]; | ||
latest_timestamp: number; | ||
latest_date: string; | ||
groups: { | ||
name: string; | ||
description: string; | ||
guid: string; | ||
}[]; | ||
computers_total_count: number; | ||
computers: { | ||
connector_guid: string; | ||
hostname: string; | ||
windows_processor_id: string; | ||
active: boolean; | ||
links: { | ||
computer: string; | ||
trajectory: string; | ||
group: string; | ||
}; | ||
}[]; | ||
} |
import { CiscoAmpComputer } from '../collector'; | ||
import { createIntegrationEntity } from '@jupiterone/integration-sdk-core'; | ||
import { createIntegrationEntity, Entity } from '@jupiterone/integration-sdk-core'; | ||
export declare const convertComputer: (data: CiscoAmpComputer) => ReturnType<typeof createIntegrationEntity>; | ||
/** | ||
* | ||
* @param id: the instance.id | ||
* @param name: the instance.name | ||
* @param description: the instance.description | ||
* @returns Entity | ||
*/ | ||
export declare function createAccountEntity({ id, name, description, }: { | ||
id: string; | ||
name: string; | ||
description: string | undefined; | ||
}): Entity; | ||
interface NetworkAddress { | ||
@@ -5,0 +17,0 @@ mac?: string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.convertNetworkAddressesToArray = exports.convertComputer = void 0; | ||
exports.convertNetworkAddressesToArray = exports.createAccountEntity = exports.convertComputer = void 0; | ||
const integration_sdk_core_1 = require("@jupiterone/integration-sdk-core"); | ||
const constants_1 = require("../steps/constants"); | ||
const convertComputer = (data) => { | ||
@@ -29,4 +30,4 @@ var _a, _b, _c, _d; | ||
_key: `cisco-amp-endpoint:${data.connector_guid}`, | ||
_type: 'cisco_amp_endpoint', | ||
_class: ['HostAgent'], | ||
_type: constants_1.Entities.COMPUTER._type, | ||
_class: constants_1.Entities.COMPUTER._class, | ||
id: data.connector_guid, | ||
@@ -37,5 +38,5 @@ name: data.hostname, | ||
function: ['endpoint-protection', 'anti-malware'], | ||
hardwareId: data.mac_hardware_id || data.windows_processor_id, | ||
installedOn: integration_sdk_core_1.getTime(data.install_date), | ||
lastSeenOn: integration_sdk_core_1.getTime(data.last_seen), | ||
hardwareId: data.windows_processor_id, | ||
installedOn: integration_sdk_core_1.parseTimePropertyValue(data.install_date), | ||
lastSeenOn: integration_sdk_core_1.parseTimePropertyValue(data.last_seen), | ||
installDate: undefined, | ||
@@ -64,2 +65,25 @@ lastSeen: undefined, | ||
exports.convertComputer = convertComputer; | ||
/** | ||
* | ||
* @param id: the instance.id | ||
* @param name: the instance.name | ||
* @param description: the instance.description | ||
* @returns Entity | ||
*/ | ||
function createAccountEntity({ id, name, description, }) { | ||
return integration_sdk_core_1.createIntegrationEntity({ | ||
entityData: { | ||
source: {}, | ||
assign: { | ||
_key: `cisco-amp-account:${id}`, | ||
_type: constants_1.Entities.ACCOUNT._type, | ||
_class: constants_1.Entities.ACCOUNT._class, | ||
name: name, | ||
displayName: name, | ||
description: description, | ||
}, | ||
}, | ||
}); | ||
} | ||
exports.createAccountEntity = createAccountEntity; | ||
function convertNetworkAddressesToArray(networkAddressData, key) { | ||
@@ -66,0 +90,0 @@ if (Array.isArray(networkAddressData)) { |
@@ -8,8 +8,10 @@ "use strict"; | ||
const config_1 = require("./config"); | ||
const synchronize_1 = __importDefault(require("./steps/synchronize")); | ||
const getStepStartStates_1 = require("./getStepStartStates"); | ||
const steps_1 = require("./steps"); | ||
const validateInvocation_1 = __importDefault(require("./validateInvocation")); | ||
exports.invocationConfig = { | ||
instanceConfigFields: config_1.instanceConfigFields, | ||
getStepStartStates: getStepStartStates_1.getStepStartStates, | ||
validateInvocation: validateInvocation_1.default, | ||
integrationSteps: [synchronize_1.default], | ||
integrationSteps: steps_1.integrationSteps, | ||
}; |
@@ -1,4 +0,4 @@ | ||
import { IntegrationStep } from '@jupiterone/integration-sdk-core'; | ||
import { IntegrationStep, IntegrationStepExecutionContext } from '@jupiterone/integration-sdk-core'; | ||
import { IntegrationConfig } from '../../config'; | ||
declare const step: IntegrationStep<IntegrationConfig>; | ||
export default step; | ||
export declare const synchronizeSteps: IntegrationStep<IntegrationConfig>[]; | ||
export declare function synchronize({ instance, jobState, }: IntegrationStepExecutionContext<IntegrationConfig>): Promise<void>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.synchronize = exports.synchronizeSteps = void 0; | ||
const integration_sdk_core_1 = require("@jupiterone/integration-sdk-core"); | ||
const collector_1 = require("../../collector"); | ||
const converter_1 = require("../../converter"); | ||
const step = { | ||
id: 'synchronize', | ||
name: 'Fetch Cisco AMP entities', | ||
entities: [ | ||
{ | ||
resourceName: 'Account', | ||
_type: 'cisco_amp_account', | ||
_class: 'Account', | ||
}, | ||
{ | ||
resourceName: 'Computer', | ||
_type: 'cisco_amp_endpoint', | ||
_class: 'HostAgent', | ||
}, | ||
], | ||
relationships: [ | ||
{ | ||
_type: 'cisco_amp_account_has_endpoint', | ||
sourceType: 'cisco_amp_account', | ||
_class: integration_sdk_core_1.RelationshipClass.HAS, | ||
targetType: 'cisco_amp_endpoint', | ||
}, | ||
{ | ||
_type: converter_1.ENDPOINT_PROTECTION_RELATIONSHIP, | ||
sourceType: 'cisco_amp_endpoint', | ||
_class: integration_sdk_core_1.RelationshipClass.PROTECTS, | ||
targetType: 'user_endpoint', | ||
}, | ||
], | ||
async executionHandler({ instance, jobState }) { | ||
const client = collector_1.createServicesClient(instance); | ||
const accountEntity = { | ||
_key: `cisco_amp_account:${instance.id}`, | ||
_type: 'cisco_amp_account', | ||
_class: ['Account'], | ||
name: instance.name, | ||
displayName: instance.name, | ||
description: instance.description, | ||
}; | ||
await jobState.addEntities([accountEntity]); | ||
const computers = await client.iterateComputers(); | ||
const computerEntities = computers.map(converter_1.convertComputer); | ||
await jobState.addEntities(computerEntities); | ||
const accountComputerRelationships = computerEntities.map((computerEntity) => integration_sdk_core_1.createDirectRelationship({ | ||
const constants_1 = require("../constants"); | ||
exports.synchronizeSteps = [ | ||
{ | ||
id: 'synchronize', | ||
name: 'Fetch Cisco AMP entities', | ||
entities: [constants_1.Entities.ACCOUNT, constants_1.Entities.COMPUTER], | ||
relationships: [constants_1.Relationships.ACCOUNT_HAS_ENDPOINT], | ||
mappedRelationships: [constants_1.MappedRelationships.ENDPOINT_PROTECTS_DEVICE], | ||
executionHandler: synchronize, | ||
}, | ||
]; | ||
async function synchronize({ instance, jobState, }) { | ||
const client = collector_1.createServicesClient(instance); | ||
const accountEntity = await jobState.addEntity(converter_1.createAccountEntity({ | ||
id: instance.id, | ||
name: instance.name, | ||
description: instance.description, | ||
})); | ||
await client.iterateComputers(async (computer) => { | ||
const computerEntity = await jobState.addEntity(converter_1.convertComputer(computer)); | ||
await jobState.addRelationship(integration_sdk_core_1.createDirectRelationship({ | ||
from: accountEntity, | ||
@@ -54,7 +32,5 @@ to: computerEntity, | ||
})); | ||
await jobState.addRelationships(accountComputerRelationships); | ||
const endpointProtectionRelationships = computerEntities.map(converter_1.mapEndpointProtectionRelationship); | ||
await jobState.addRelationships(endpointProtectionRelationships); | ||
}, | ||
}; | ||
exports.default = step; | ||
await jobState.addRelationship(converter_1.mapEndpointProtectionRelationship(computerEntity)); | ||
}); | ||
} | ||
exports.synchronize = synchronize; |
{ | ||
"name": "@jupiterone/graph-cisco-amp", | ||
"version": "0.7.2", | ||
"version": "1.0.0", | ||
"description": "A graph conversion tool for https://www.cisco.com/c/en/us/products/security/amp-for-endpoints/", | ||
@@ -17,3 +17,3 @@ "license": "MPL-2.0", | ||
"graph": "j1-integration visualize", | ||
"start": "yarn collect; yarn graph", | ||
"start": "yarn collect --disable-schema-validation", | ||
"lint": "eslint . --cache --fix --ext .ts,.tsx", | ||
@@ -23,3 +23,4 @@ "format": "prettier --write '**/*.{ts,js,json,css,md,yml}'", | ||
"type-check": "tsc --noEmit", | ||
"test": "jest --passWithNoTests", | ||
"test": "jest", | ||
"test:env": "LOAD_ENV=1 yarn test", | ||
"test:ci": "yarn lint && yarn type-check && yarn test", | ||
@@ -37,10 +38,10 @@ "prebuild": "yarn test:ci", | ||
"devDependencies": { | ||
"@jupiterone/integration-sdk-core": "^8.8.0", | ||
"@jupiterone/integration-sdk-dev-tools": "^8.8.0", | ||
"@jupiterone/integration-sdk-testing": "^8.8.0", | ||
"@jupiterone/integration-sdk-core": "^8.9.0", | ||
"@jupiterone/integration-sdk-dev-tools": "^8.9.0", | ||
"@jupiterone/integration-sdk-testing": "^8.9.0", | ||
"jest-fetch-mock": "^3.0.3" | ||
}, | ||
"peerDependencies": { | ||
"@jupiterone/integration-sdk-core": "^8.8.0" | ||
"@jupiterone/integration-sdk-core": "^8.9.0" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
151764
38
993
1