Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #249 from JupiterOne/tenable-enhancement-dev
Browse files Browse the repository at this point in the history
Tenable enhancement dev
  • Loading branch information
gastonyelmini authored Jul 30, 2024
2 parents c6c4c22 + 9dedbd3 commit 15bdfd6
Show file tree
Hide file tree
Showing 23 changed files with 786 additions and 11 deletions.
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
ACCESS_KEY=tenable-access-key
SECRET_KEY=tenable-secret-key
ASSET_API_TIMEOUT_IN_MINUTES=30

# Configuration filters for Vulnerabilities
VULNERABILITY_API_TIMEOUT_IN_MINUTES=30
VULNERABILITY_SEVERITIES=info,low,medium,high,critical
VULNERABILITY_STATES=open,reopened,fixed

# Configuration filters for Compliance Findings
COMPLIANCE_LAST_SEEN=15,30,60,90
COMPLIANCE_STATE=OPEN,REOPENED,FIXED
COMPLIANCE_RESULT=PASSED,FAILED,WARNING,SKIPPED,UNKNOWN,ERROR
COMPLIANCE_NUM_FINDINGS=10000

2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ cvss3Vector - cvssVector - hasPatch

## [8.5.1] 2022-08-05

- fix tenable_asset `firstSeen` and `lastSeen` properties to be human-readable
- fix tenable_asset `firstSeen` and `complianceLastSeen` properties to be human-readable

## [8.5.0] 2022-06-08

Expand Down
2 changes: 2 additions & 0 deletions docs/jupiterone.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ The following entities are created:
| Account | `tenable_account` | `Account` |
| Agent | `tenable_agent` | `HostAgent` |
| Asset | `tenable_asset` | `Record` |
| Compliance Finding | `tenable_compliance_finding` | `Finding` |
| Container Finding | `tenable_container_finding` | `Finding` |
| Container Image | `tenable_container_image` | `Image` |
| Container Malware | `tenable_container_malware` | `Finding` |
Expand All @@ -113,6 +114,7 @@ The following relationships are created:
| `tenable_account` | **HAS** | `tenable_user` |
| `tenable_account` | **PROVIDES** | `tenable_scanner` |
| `tenable_agent` | **PROTECTS** | `tenable_asset` |
| `tenable_asset` | **HAS** | `tenable_compliance_finding` |
| `tenable_asset` | **HAS** | `tenable_vulnerability_finding` |
| `tenable_container_image` | **HAS** | `tenable_container_finding` |
| `tenable_container_image` | **HAS** | `tenable_container_malware` |
Expand Down
20 changes: 20 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ export interface IntegrationConfig extends IntegrationInstanceConfig {
accessKey: string;
secretKey: string;
vulnerabilityApiTimeoutInMinutes?: number;
complianceApiTimeoutInMinutes?: number;
assetApiTimeoutInMinutes?: number;
vulnerabilitySeverities?: string;
vulnerabilityStates?: string;
complianceLastSeen?: string;
complianceState?: string;
complianceResult?: string;
complianceNumFindings?: number;
}

export const instanceConfigFields: IntegrationInstanceConfigFieldMap = {
Expand All @@ -32,4 +37,19 @@ export const instanceConfigFields: IntegrationInstanceConfigFieldMap = {
vulnerabilityStates: {
type: 'string',
},
complianceLastSeen: {
type: 'string',
},
complianceApiTimeoutInMinutes: {
type: 'string',
},
complianceState: {
type: 'string',
},
complianceResult: {
type: 'string',
},
complianceNumFindings: {
type: 'string',
},
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { IntegrationConfig, instanceConfigFields } from './config';
import validateInvocation from './invocationValidator';
import { integrationSteps } from './steps';
import getStepStartStates from './getStepStartStates';
import { ingestionConfig } from './ingestionConfig';

export const invocationConfig: IntegrationInvocationConfig<IntegrationConfig> =
{
instanceConfigFields,
validateInvocation,
integrationSteps,
getStepStartStates,
ingestionConfig,
};
60 changes: 60 additions & 0 deletions src/ingestionConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { IntegrationIngestionConfigFieldMap } from '@jupiterone/integration-sdk-core';
import { INGESTION_SOURCE_IDS } from './steps/constants';

export const ingestionConfig: IntegrationIngestionConfigFieldMap = {
[INGESTION_SOURCE_IDS.ACCOUNT]: {
title: 'Account',
description: 'Tenable accounts',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.SERVICE]: {
title: 'Service',
description: 'Service descriptions',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.ASSETS]: {
title: 'Assets',
description: 'Asset descriptions',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.VULNERABILITIES]: {
title: 'Vulnerabilities',
description: 'Vulnerability descriptions',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.USERS]: {
title: 'Users',
description: 'User information',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.CONTAINER_IMAGES]: {
title: 'Container Images',
description: 'Container image descriptions',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.CONTAINER_REPOSITORIES]: {
title: 'Container Repositories',
description: 'Container repository descriptions',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.CONTAINER_REPORTS]: {
title: 'Container Reports',
description: 'Reports on container statuses',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.SCANNER_IDS]: {
title: 'Scanner IDs',
description: 'Scanner ID information',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.AGENTS]: {
title: 'Agents',
description: 'Agent information',
defaultsToDisabled: false,
},
[INGESTION_SOURCE_IDS.COMPLIANCE_FINDINGS]: {
title: 'Compliance Findings',
description: 'Compliance findings',
defaultsToDisabled: true,
},
};
62 changes: 62 additions & 0 deletions src/invocationValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import TenableClient from './tenable/TenableClient';
import {
VALID_VULNERABILITY_STATES,
VALID_VULNERABILITY_SEVERITIES,
VALID_COMPLIANCE_STATES,
VALID_COMPLIANCE_RESULT,
} from './tenable/client';
import { toNum } from './utils/dataType';

Expand Down Expand Up @@ -50,6 +52,38 @@ function validateVulnerabilityStates(states: string) {
}
}

function validateComplianceStates(states: string) {
const statesValues = states.replace(/\s+/g, '').split(',');
for (const state of statesValues) {
if (!(VALID_COMPLIANCE_STATES as unknown as string[]).includes(state)) {
throw new IntegrationValidationError(
`States - ${state} - is not valid. Valid Compliance states include ${VALID_COMPLIANCE_STATES.map(
(v) => v,
)}`,
);
}
}
}

function validateComplianceResults(complianceResult: string) {
const complianceResultValues = complianceResult
.replace(/\s+/g, '')
.split(',');
for (const complianceResult of complianceResultValues) {
if (
!(VALID_COMPLIANCE_RESULT as unknown as string[]).includes(
complianceResult,
)
) {
throw new IntegrationValidationError(
`complianceResult - ${complianceResult} - is not valid. Valid complianceResult include ${VALID_COMPLIANCE_RESULT.map(
(v) => v,
)}`,
);
}
}
}

/**
* Performs validation of the execution before the execution handler function is
* invoked.
Expand Down Expand Up @@ -118,6 +152,34 @@ export default async function validateInvocation(
validateVulnerabilityStates(vulnerabilityStates);
}

if (config.complianceStates) {
const complianceStates =
(executionContext.instance.config.complianceStates =
typeof config.complianceStates === 'string'
? config.complianceStates.replace(/\s+/g, '')
: config.complianceStates);
validateComplianceStates(complianceStates);
}

if (config.complianceResult) {
const complianceResult =
(executionContext.instance.config.complianceResult =
typeof config.complianceResult === 'string'
? config.complianceResult.replace(/\s+/g, '')
: config.complianceResult);
validateComplianceResults(complianceResult);
}

if (config.complianceNumFindings) {
const numFindings = Number(config.complianceNumFindings);
if (isNaN(numFindings) || numFindings < 50 || numFindings > 10000) {
throw new IntegrationConfigLoadError(
`'numFindings' config value is invalid (val=${numFindings}, min=50, max=10000)`,
);
}
executionContext.instance.config.complianceNumFindings = numFindings;
}

const provider = new TenableClient({
logger,
accessToken: config.accessKey,
Expand Down
8 changes: 7 additions & 1 deletion src/steps/access/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import {
Step,
} from '@jupiterone/integration-sdk-core';
import { IntegrationConfig } from '../../config';
import { Entities, Relationships, StepIds } from '../constants';
import {
Entities,
INGESTION_SOURCE_IDS,
Relationships,
StepIds,
} from '../constants';
import { getAccount } from '../account/util';
import TenableClient from '../../tenable/TenableClient';
import { createAccountUserRelationship, createUserEntity } from './converters';
Expand Down Expand Up @@ -35,6 +40,7 @@ export const userStep: Step<
id: StepIds.USERS,
name: 'Fetch Users',
entities: [Entities.USER],
ingestionSourceId: INGESTION_SOURCE_IDS.USERS,
relationships: [Relationships.ACCOUNT_HAS_USER],
dependsOn: [StepIds.ACCOUNT],
executionHandler: fetchUsers,
Expand Down
3 changes: 2 additions & 1 deletion src/steps/account/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
Step,
} from '@jupiterone/integration-sdk-core';
import { IntegrationConfig } from '../../config';
import { Entities, StepIds } from '../constants';
import { Entities, INGESTION_SOURCE_IDS, StepIds } from '../constants';
import { createAccountEntity } from './converters';
import { getAccount } from './util';

Expand All @@ -19,6 +19,7 @@ export const accountStep: Step<
id: StepIds.ACCOUNT,
name: 'Fetch Account',
entities: [Entities.ACCOUNT],
ingestionSourceId: INGESTION_SOURCE_IDS.ACCOUNT,
relationships: [],
dependsOn: [],
executionHandler: fetchAccount,
Expand Down
9 changes: 8 additions & 1 deletion src/steps/agents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
Step,
createDirectRelationship,
} from '@jupiterone/integration-sdk-core';
import { Entities, StepIds, Relationships } from '../constants';
import {
Entities,
StepIds,
Relationships,
INGESTION_SOURCE_IDS,
} from '../constants';
import { IntegrationConfig } from '../../config';
import { DATA_SCANNER_IDS } from '../scanners/constants';
import { createAgentEntity } from './converters';
Expand Down Expand Up @@ -125,6 +130,7 @@ export const agentsSteps: Step<
id: StepIds.AGENTS,
name: 'Fetch Agents',
entities: [Entities.AGENT],
ingestionSourceId: INGESTION_SOURCE_IDS.AGENTS,
relationships: [Relationships.ACCOUNT_HAS_AGENT],
dependsOn: [StepIds.ACCOUNT, StepIds.SCANNER_IDS],
executionHandler: fetchAgents,
Expand All @@ -133,6 +139,7 @@ export const agentsSteps: Step<
id: StepIds.AGENT_RELATIONSHIPS,
name: 'Build Host Agent Protects Agents Relationship',
entities: [],
ingestionSourceId: INGESTION_SOURCE_IDS.AGENTS,
relationships: [Relationships.HOSTAGENT_PROTECTS_DEVICE],
dependsOn: [StepIds.ASSETS, StepIds.AGENTS],
executionHandler: buildAgentRelationships,
Expand Down
38 changes: 38 additions & 0 deletions src/steps/compliance-finding/converter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Entities } from '../constants';
import {
createIntegrationEntity,
Entity,
} from '@jupiterone/integration-sdk-core';
import { generateEntityKey } from '../../utils/generateKey';

export function createComplianceFindingEntity(complianceChunk): Entity {
return createIntegrationEntity({
entityData: {
source: complianceChunk,
assign: {
_class: Entities.COMPLIANCE_FINDINGS._class,
_type: Entities.COMPLIANCE_FINDINGS._type,
_key: generateEntityKey(
Entities.COMPLIANCE_FINDINGS._type,
complianceChunk.uuid,
),

// Schema required fields.
category: ['network', 'host'],
severity: ['low', 'medium'],
numericSeverity: [1, 2],
id: String(complianceChunk.id),
agentId: complianceChunk.id,
displayName: complianceChunk.name,
open: complianceChunk.state === 'OPEN',

// Entity additional data.
name: complianceChunk.name,
status: complianceChunk.status,
firstSeen: complianceChunk.first_seen,
lastSeen: complianceChunk.last_seen,
agentName: complianceChunk.agent_name,
},
},
});
}
56 changes: 56 additions & 0 deletions src/steps/compliance-finding/filters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { IntegrationConfig } from '../../config';
import {
ExportComplianceFindingsFilter,
complianceChunkState,
complianceChunkResult,
} from '../../tenable/client';
import { subDays, getUnixTime } from 'date-fns';

const DEFAULT_STATES: complianceChunkState[] = ['OPEN', 'REOPENED', 'FIXED'];
const DEFAULT_RESULTS: complianceChunkResult[] = [
'PASSED',
'FAILED',
'WARNING',
'SKIPPED',
'UNKNOWN',
'ERROR',
];
const DEFAULT_LAST_SEEN_DAYS = 30; // Default to 30 days if not provided

function parseComplianceStates(states: string): complianceChunkState[] {
return states.split(',') as complianceChunkState[];
}

function parseComplianceResults(results: string): complianceChunkResult[] {
return results.split(',') as complianceChunkResult[];
}

function calculateLastSeenTimestamp(daysAgo: number): number {
const lastSeenDate = subDays(new Date(), daysAgo);
return getUnixTime(lastSeenDate);
}

export function buildComplianceFilters(
config: IntegrationConfig,
): ExportComplianceFindingsFilter {
const lastSeenDays = config.complianceLastSeen
? Number(config.complianceLastSeen)
: DEFAULT_LAST_SEEN_DAYS;
if (isNaN(lastSeenDays)) {
throw new Error(
`Invalid complianceLastSeen value: ${config.complianceLastSeen}`,
);
}

const lastSeenTimestamp = calculateLastSeenTimestamp(lastSeenDays);

return {
state: config.complianceState
? parseComplianceStates(config.complianceState)
: DEFAULT_STATES,
compliance_results: config.complianceResults
? parseComplianceResults(config.complianceResults)
: DEFAULT_RESULTS,
last_seen: lastSeenTimestamp,
};
}
Loading

0 comments on commit 15bdfd6

Please sign in to comment.