-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add commands to control discoveries (#621)
Add commands to control discoveries: - `discovery:run [options]` to start a new discovery for the received configuration. - `discovery:stop [options] <discoveryId>` to stop a discovery by id. - `discovery:rerun [options] <discoveryId>` to request to start a new discovery using the same configuration as an existing discovery, by discovery ID. --------- Co-authored-by: Or Rubin <[email protected]>
- Loading branch information
1 parent
a4a690d
commit 8010670
Showing
8 changed files
with
556 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Discoveries, RestDiscoveryOptions } from 'src/Discovery'; | ||
import { ErrorMessageFactory, logger } from 'src/Utils'; | ||
import { container } from 'tsyringe'; | ||
import { Arguments, Argv, CommandModule } from 'yargs'; | ||
|
||
export class RerunDiscovery implements CommandModule { | ||
public readonly command = 'discovery:rerun [options] <discoveryId>'; | ||
public readonly describe = | ||
'Request to start a new discovery using the same configuration as an existing discovery, by discovery ID.'; | ||
|
||
public builder(argv: Argv): Argv { | ||
return argv | ||
.option('token', { | ||
alias: 't', | ||
describe: 'Bright API-key', | ||
string: true, | ||
requiresArg: true, | ||
demandOption: true | ||
}) | ||
.positional('discoveryId', { | ||
describe: 'ID of an existing discovery which you want to re-run.', | ||
requiresArg: true, | ||
demandOption: true, | ||
type: 'string' | ||
}) | ||
.option('project', { | ||
alias: 'p', | ||
describe: 'ID of the project', | ||
string: true, | ||
requiresArg: true, | ||
demandOption: true | ||
}) | ||
.middleware((args: Arguments) => | ||
container.register<RestDiscoveryOptions>(RestDiscoveryOptions, { | ||
useValue: { | ||
insecure: args.insecure as boolean, | ||
baseURL: args.api as string, | ||
apiKey: args.token as string, | ||
proxyURL: (args.proxyBright ?? args.proxy) as string, | ||
timeout: args.timeout as number | ||
} | ||
}) | ||
); | ||
} | ||
|
||
public async handler(args: any): Promise<void> { | ||
try { | ||
const discoveryManager: Discoveries = container.resolve(Discoveries); | ||
const projectId = args.project as string; | ||
const discoveryId = args.discoveryId as string; | ||
const newDiscoveryId = await discoveryManager.rerun( | ||
projectId, | ||
discoveryId | ||
); | ||
|
||
// eslint-disable-next-line no-console | ||
console.log(newDiscoveryId); | ||
process.exit(0); | ||
} catch (error) { | ||
logger.error( | ||
ErrorMessageFactory.genericCommandError({ | ||
error, | ||
command: 'discovery:rerun' | ||
}) | ||
); | ||
process.exit(1); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
import { Discoveries, DiscoveryConfig } from '../Discovery'; | ||
import { ErrorMessageFactory, logger } from '../Utils'; | ||
import { RestDiscoveryOptions } from 'src/Discovery/RestDiscoveries'; | ||
import { container } from 'tsyringe'; | ||
import { Arguments, Argv, CommandModule } from 'yargs'; | ||
|
||
export class RunDiscovery implements CommandModule { | ||
public readonly command = 'discovery:run [options]'; | ||
public readonly describe = | ||
'Start a new discovery for the received configuration.'; | ||
|
||
public builder(argv: Argv): Argv { | ||
return argv | ||
.option('token', { | ||
alias: 't', | ||
describe: 'Bright API-key', | ||
string: true, | ||
requiresArg: true, | ||
demandOption: true | ||
}) | ||
.option('project', { | ||
alias: 'p', | ||
describe: 'ID of the project', | ||
string: true, | ||
requiresArg: true, | ||
demandOption: true | ||
}) | ||
.option('name', { | ||
alias: 'n', | ||
describe: 'Name of the discovery.', | ||
string: true, | ||
requiresArg: true, | ||
demandOption: true | ||
}) | ||
.option('auth', { | ||
alias: 'o', | ||
describe: 'Auth object ID.', | ||
string: true, | ||
requiresArg: true | ||
}) | ||
.option('repeater', { | ||
alias: 'agent', | ||
requiresArg: true, | ||
array: true, | ||
describe: 'ID of any repeaters connected with the discovery.' | ||
}) | ||
.option('archive', { | ||
alias: 'a', | ||
normalize: true, | ||
requiresArg: true, | ||
describe: | ||
"A collection of your app's http/websockets logs into HAR file. " + | ||
'Usually you can use browser dev tools or our browser web extension' | ||
}) | ||
.option('crawler', { | ||
alias: 'c', | ||
requiresArg: true, | ||
array: true, | ||
describe: | ||
'A list of specific urls that should be included into crawler.', | ||
demandOption: true | ||
}) | ||
.option('host-filter', { | ||
alias: 'F', | ||
requiresArg: true, | ||
array: true, | ||
describe: 'A list of specific hosts that should be included into scan.' | ||
}) | ||
.option('header', { | ||
alias: 'H', | ||
requiresArg: true, | ||
array: true, | ||
describe: | ||
'A list of specific headers that should be included into request.' | ||
}) | ||
.option('smart', { | ||
boolean: true, | ||
describe: | ||
'Use automatic smart decisions such as: parameter skipping, detection phases, etc. to minimize scan time.' | ||
}) | ||
.option('crawl-parent-subdomains', { | ||
boolean: true, | ||
describe: 'Crawl parent path folders and subdomains', | ||
default: false | ||
}) | ||
.option('concurrency', { | ||
number: true, | ||
default: 10, | ||
describe: | ||
'Number of maximum concurrent requests allowed to be sent to the target, can range between 1 to 50 (default: 10).', | ||
requiresArg: true | ||
}) | ||
.option('interactions-depth', { | ||
number: true, | ||
default: 3, | ||
describe: | ||
'Number of maximum interactions with nested objects, can range between 1 to 5 (default: 3).', | ||
requiresArg: true | ||
}) | ||
.middleware((args: Arguments) => | ||
container.register<RestDiscoveryOptions>(RestDiscoveryOptions, { | ||
useValue: { | ||
insecure: args.insecure as boolean, | ||
baseURL: args.api as string, | ||
apiKey: args.token as string, | ||
proxyURL: (args.proxyBright ?? args.proxy) as string, | ||
timeout: args.timeout as number | ||
} | ||
}) | ||
); | ||
} | ||
|
||
public async handler(args: Arguments): Promise<void> { | ||
try { | ||
const discoveryManager: Discoveries = container.resolve(Discoveries); | ||
|
||
const projectId = args.project as string; | ||
|
||
const { id: discoveryId, warnings } = await discoveryManager.create( | ||
projectId, | ||
{ | ||
name: args.name, | ||
authObjectId: args.auth, | ||
hostsFilter: args.hostFilter, | ||
crawlerUrls: args.crawler, | ||
fileId: args.archive, | ||
repeaters: args.repeater, | ||
optimizedCrawler: args.smart, | ||
poolSize: args.concurrency, | ||
maxInteractionsChainLength: args.interactionsDepth, | ||
subdomainsCrawl: args.crawlParentSubdomains, | ||
headers: args.header | ||
} as DiscoveryConfig | ||
); | ||
|
||
// eslint-disable-next-line no-console | ||
console.log(discoveryId); | ||
|
||
if (warnings?.length) { | ||
logger.warn( | ||
`${warnings.map((warning) => warning.message).join('\n')}\n` | ||
); | ||
} | ||
|
||
process.exit(0); | ||
} catch (error) { | ||
logger.error( | ||
ErrorMessageFactory.genericCommandError({ | ||
error, | ||
command: 'discovery:run' | ||
}) | ||
); | ||
process.exit(1); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { Discoveries, RestDiscoveryOptions } from '../Discovery'; | ||
import { ErrorMessageFactory, logger } from '../Utils'; | ||
import { container } from 'tsyringe'; | ||
import { Arguments, Argv, CommandModule } from 'yargs'; | ||
|
||
export class StopDiscovery implements CommandModule { | ||
public readonly command = 'discovery:stop [options] <discoveryId>'; | ||
public readonly describe = 'Stop discovery by id.'; | ||
|
||
public builder(argv: Argv): Argv { | ||
return argv | ||
.option('token', { | ||
alias: 't', | ||
describe: 'Bright API-key', | ||
string: true, | ||
requiresArg: true, | ||
demandOption: true | ||
}) | ||
.option('project', { | ||
alias: 'p', | ||
requiresArg: true, | ||
string: true, | ||
describe: 'ID of the project', | ||
demandOption: true | ||
}) | ||
.positional('discoveryId', { | ||
describe: 'ID of an existing discovery which you want to stop.', | ||
requiresArg: true, | ||
demandOption: true, | ||
type: 'string' | ||
}) | ||
.middleware((args: Arguments) => | ||
container.register<RestDiscoveryOptions>(RestDiscoveryOptions, { | ||
useValue: { | ||
insecure: args.insecure as boolean, | ||
baseURL: args.api as string, | ||
apiKey: args.token as string, | ||
proxyURL: (args.proxyBright ?? args.proxy) as string, | ||
timeout: args.timeout as number | ||
} | ||
}) | ||
); | ||
} | ||
|
||
public async handler(args: Arguments): Promise<void> { | ||
try { | ||
const discoveryManager: Discoveries = container.resolve(Discoveries); | ||
|
||
await discoveryManager.stop( | ||
args.project as string, | ||
args.discoveryId as string | ||
); | ||
process.exit(0); | ||
} catch (error) { | ||
logger.error( | ||
ErrorMessageFactory.genericCommandError({ | ||
error, | ||
command: 'discovery:stop' | ||
}) | ||
); | ||
process.exit(1); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
export interface DiscoveryConfig { | ||
name: string; | ||
authObjectId?: string; | ||
poolSize?: number; | ||
crawlerUrls?: string[]; | ||
extraHosts?: Record<string, string>; | ||
headers?: Record<string, string> | Header[]; | ||
fileId?: string; | ||
targetId?: string; | ||
hostsFilter?: string[]; | ||
optimizedCrawler?: boolean; | ||
maxInteractionsChainLength: number; | ||
subdomainsCrawl: boolean; | ||
exclusions?: Exclusions; | ||
repeaters?: string[]; | ||
discoveryTypes?: DiscoveryType[]; | ||
targetTimeout: number; | ||
} | ||
|
||
export interface Header { | ||
name: string; | ||
value: string; | ||
mergeStrategy: 'replace'; | ||
} | ||
|
||
export interface Discoveries { | ||
create( | ||
projectId: string, | ||
config: DiscoveryConfig | ||
): Promise<DiscoveryCreateResponse>; | ||
|
||
rerun(projectId: string, discoveryId: string): Promise<string>; | ||
|
||
stop(projectId: string, discoveryId: string): Promise<void>; | ||
|
||
delete(projectId: string, discoveryId: string): Promise<void>; | ||
} | ||
|
||
export const Discoveries: unique symbol = Symbol('Discoveries'); | ||
|
||
export interface DiscoveryWarning { | ||
code: string; | ||
message: string; | ||
} | ||
|
||
export interface DiscoveryCreateResponse { | ||
id: string; | ||
warnings?: DiscoveryWarning[]; | ||
} | ||
|
||
export enum DiscoveryType { | ||
CRAWLER = 'crawler', | ||
ARCHIVE = 'archive', | ||
OAS = 'oas' | ||
} | ||
|
||
export interface RequestExclusion { | ||
patterns: string[]; | ||
methods: string[]; | ||
} | ||
|
||
export interface Exclusions { | ||
params: string[]; | ||
requests: RequestExclusion[]; | ||
} | ||
|
||
export interface StorageFile { | ||
id: string; | ||
type: SourceType; | ||
} | ||
|
||
export enum SourceType { | ||
OPEN_API = 'openapi', | ||
RAML = 'raml', | ||
POSTMAN = 'postman', | ||
HAR = 'har' | ||
} |
Oops, something went wrong.