From c0bb81cbec03c52292344ca4a0d399c9c2bd89ba Mon Sep 17 00:00:00 2001 From: Artem Derevnjuk Date: Fri, 21 Aug 2020 23:09:22 +0300 Subject: [PATCH] feat: rename agent to repeater (#69) BREAKING CHANGES: * **agent**: `agent` command has been renamed to `repeater`. It affected all its options and arguments. Before: ```bash docker run neuralegion/agent \ -e 'AGENT_API_KEY=my-api-tey' \ -e 'AGENT_ID=my-agent-id' \ -e 'AGENT_HEADERS={ "X-Header": "my-header" }' -e 'AGENT_PROXY=socks5://my-proxy:1080/' ``` After: ```bash docker run neuralegion/repeater \ -e 'REPEATER_TOKEN=my-api-tey' \ -e 'REPEATER_AGENT=my-agent-id' \ -e 'REPEATER_HEADERS={ "X-Header": "my-header" }' -e 'REPEATER_PROXY=socks5://my-proxy:1080/' ``` * **archive:generate**: command has been reworking to accept `mockfile` as positional argument. `archive` options has been renamed to `output`. Before: ```bash nexploit-cli archive:generate \ --archive archive.har \ --target url-tested-application \ --mockfile .nexmock \ --header "Authorization: Bearer my-jwt-authentication-token" ``` After: ```bash nexploit-cli archive:generate \ --output archive.har \ --target url-tested-application \ --header "Authorization: Bearer my-jwt-authentication-token" \ .nexmock ``` * `api-key` option has been removed. You should use `token` option instead. Before: ```bash nexploit-cli scan:run \ --name scan-name \ --archive received-archive-id \ --api-key my-jwt-authentication-token ``` After: ```bash nexploit-cli scan:run \ --name scan-name \ --archive received-archive-id \ --token my-jwt-authentication-token ``` closes #68 --- .github/workflows/deploy.yml | 4 +- Dockerfile | 4 +- README.md | 260 ++++++++++--------- src/Commands/GenerateArchive.ts | 29 ++- src/Commands/PollingScanStatus.ts | 15 +- src/Commands/RetestScan.ts | 14 +- src/Commands/{RunAgent.ts => RunRepeater.ts} | 56 ++-- src/Commands/RunScan.ts | 16 +- src/Commands/StopScan.ts | 16 +- src/Commands/UploadArchive.ts | 20 +- src/Commands/index.ts | 2 +- src/Config/CliBuilder.ts | 29 ++- src/Config/ConfigReader.ts | 6 +- src/Config/DefaultConfigReader.ts | 10 +- src/Utils/Helpers.ts | 15 +- src/index.ts | 4 +- 16 files changed, 255 insertions(+), 245 deletions(-) rename src/Commands/{RunAgent.ts => RunRepeater.ts} (70%) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0d56b50d..9fa213ac 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -50,6 +50,6 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GPR_TOKEN }} - - run: docker build . --file Dockerfile --tag "neuralegion/agent:$VERSION" + - run: docker build . --file Dockerfile --tag "neuralegion/repeater:$VERSION" - run: docker login --username=${{ secrets.DOCKER_USER }} --password=${{ secrets.DOCKER_TOKEN }} - - run: docker push neuralegion/agent + - run: docker push neuralegion/repeater diff --git a/Dockerfile b/Dockerfile index 8d830cd5..ebb3b6ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM node:10-alpine as base LABEL org.opencontainers.image.vendor="NeuraLegion" -LABEL org.opencontainers.image.title="Agent" +LABEL org.opencontainers.image.title="Repeater" LABEL org.opencontainers.image.source="https://github.com/NeuraLegion/nexploit-cli" LABEL org.opencontainers.image.authors="Arten Derevnjuk " @@ -20,4 +20,4 @@ RUN npm config -g set user $(whoami) RUN npm i -g -q @neuralegion/nexploit-cli ENTRYPOINT [ "nexploit-cli" ] -CMD ["agent"] +CMD ["repeater"] diff --git a/README.md b/README.md index 9b7d8724..f0889be8 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,19 @@ Some NexPloit CLI features: ## Table of Contents - * [Installing NexPloit CLI](#installing-nexploit-cli) - * [CLI command-language syntax](#cli-command-language-syntax) - * [Configuration files](#configuration-files) - * [Quick Start](#-quick-start) - * [Command Overview](#command-overview) - + [Upload Archive](#-upload-archive) - + [Generate Archive based on NexMock](#-generate-archive-based-on-nexmock) - + [Run Scan](#-run-scan) - + [Start agent](#-start-agent) - + [Check Scan's status](#-check-scan-status) - + [Stop Scan](#-stop-scan) - + [Retest Scan](#-retest-scan) - * [License](#-license) +- [Installing NexPloit CLI](#installing-nexploit-cli) +- [CLI command-language syntax](#cli-command-language-syntax) +- [Configuration files](#configuration-files) +- [Quick Start](#-quick-start) +- [Command Overview](#command-overview) + - [Upload Archive](#-upload-archive) + - [Generate Archive based on NexMock](#-generate-archive-based-on-nexmock) + - [Run Scan](#-run-scan) + - [Start repeater](#-start-repeater) + - [Check Scan's status](#-check-scan-status) + - [Stop Scan](#-stop-scan) + - [Retest Scan](#-retest-scan) +- [License](#-license) ## Installing NexPloit CLI @@ -33,6 +33,7 @@ Install the CLI using the npm package manager: ```bash npm install -g @neuralegion/nexploit-cli ``` + For details about changes between versions, and information about updates on previous releases, see the Releases tab on GitHub: https://github.com/NeuraLegion/nexploit-cli/releases ## CLI command-language syntax @@ -41,27 +42,39 @@ NexPloit CLI accepts a wide variety of configuration arguments, run `nexploit-cl Configuration arguments in the command-line must be passed after the program command that NexPloit CLI is executing. `nexploit-cli command_name required_arg [optional_arg] [options]` -* Most commands, and some options, have aliases. Aliases are shown in the syntax statement for each command. -* Option names are prefixed with a double dash (--). Option aliases are prefixed with a single dash (-). Arguments are not prefixed. For example: + +- Most commands, and some options, have aliases. Aliases are shown in the syntax statement for each command. +- Option names are prefixed with a double dash (--). Option aliases are prefixed with a single dash (-). Arguments are not prefixed. For example: + ```bash -nexploit-cli scan:stop --api-key my-api-kye my-scan-id +nexploit-cli scan:stop --token my-api-tye my-scan-id ``` -* Typically, the generated artifact can be given as an argument to the command or specified with the `--archive` option (See `archive:generate`). + +- Typically, the generated artifact can be given as an argument to the command or specified with the `--output` option (See `archive:generate`). + +#### Common options + +NexPloit CLI provides multiple global options which can affect the behaviour of each command. + +| Option | Description | +| --------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--config=configPath` | Path to the file with configuration. By default, CLI tries to discovery config in package.json in the root directory of your application or the separate file by known name in working dir. For details, see [Configuration files](#configuration-files) | +| `--api=proxyUrl` | NexPloit base URL. Default: `https://nexploit.app/` | +| `--proxy=proxyUrl` | SOCKS4 or SOCKS5 url to proxy all traffic | ## Configuration files Any configuration options that can be set via the command line can also be specified in the `nexploit` stanza of your package.json, or within a seperate configuration file - a variety of flavors are available: -| File name | File Association | -|-----------------|------------------| -| `.nexploit` | JSON | -| `.nexploit.json` | JSON | -| `.nexploit.yaml` | YAML | -| `.nexploit.yml` | YAML | +| File name | File Association | +| -------------------- | ---------------- | +| `.nexploit` | JSON | +| `.nexploit.json` | JSON | +| `.nexploit.yaml` | YAML | +| `.nexploit.yml` | YAML | | `nexploit.config.js` | CommonJS export | -See `nexploit-cli --help` for all options available. -You can set these in any of the files listed above, or from the command line. +See `nexploit-cli --help` for all options available. You can set these in any of the files listed above, or from the command line. ## 🚀 Quick Start @@ -83,13 +96,13 @@ If you already have a prepared mock file, you can generate a HAR file with the f ```bash nexploit-cli archive:generate \ - --mockfile .nexmock \ - --archive archive.har \ + --output archive.har \ --target url-tested-application \ - --header "Authorization: Bearer my-jwt-authentication-token" + --header "Authorization: Bearer my-jwt-authentication-token" \ + .nexmock ``` -Where `mockfile` is the path to your mockfile and `archive` is the path to save the HAR file to. +Where `mockfile` is the path to your mockfile and `output` is the path to save the HAR file to. The [archive:generate](#-generate-archive-based-on-nexmock) command will generate a new archive at the archive path: @@ -106,7 +119,7 @@ During the creating of the archive, you can edit the `.nexploitrc.json` file and ```json { - "api-key": "my-jwt-authentication-token", + "token": "my-jwt-authentication-token", "api": "https://nexploit.app", "bus": "amqps://amq.nexploit.app:5672" } @@ -114,7 +127,7 @@ During the creating of the archive, you can edit the `.nexploitrc.json` file and > ✴ You can also declare one of following .nexploitrc, .nexploitrc.json, .nexploitrc.yml, .nexploitrc.yaml, nexploit.config.js files. -However, most of the time you'll only need to configure api-key, target and maybe host-filter options. +However, most of the time you'll only need to configure token, bus and maybe proxy options. Once the configuration is completed, upload the .HAR (or OAS) file with: @@ -130,7 +143,7 @@ The [archive:upload](#-upload-archive) command will output an ID of a new archiv nexploit-cli scan:run \ --name scan-name \ --archive received-archive-id \ - --api-key my-jwt-authentication-token + --token my-jwt-authentication-token ``` That's it, a new scan will be initiated immediately (or queued) in the [NexPloit application UI](https://nexploit.app). @@ -142,9 +155,9 @@ This section contains information on using NexPloit CLI's commands. ### 📥 Upload Archive -`nexploit-cli archive:upload [options] ` uploads passed HAR (or OAS) into your [NeuraLegion Storage](https://nexploit.app/storage). +`nexploit-cli archive:upload [options] ` uploads passed HAR (or OAS/Postman) into your [NeuraLegion Storage](https://nexploit.app/storage). -> ✴ If you plan to upload OAS file to the storage, you can specify a different discovery option by setting the `--discovery` to `oas`. +> ✴ If you plan to upload OAS file to the storage, you can specify a different discovery option by setting the `--type` to `oas`. The command will output an ID of a new archive, which you can use to run a new scan. @@ -152,56 +165,59 @@ The command will output an ID of a new archive, which you can use to run a new s #### Arguments -| Argument | Description | -|---|:---| +| Argument | Description | +| -------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `` | A collection of your app's http/websockets logs exported into a HAR file. Typically, you can use any browser's dev tools, NeuraLegion's [browser extension](https://chrome.google.com/webstore/detail/nexploit/pgmogkjcjlgjnconlkocehfadbkimjbg) or [Cypress plugin](https://www.npmjs.com/package/@neuralegion/cypress-har-generator) to generate them. In addition, you can use an OAS file that describes your public API. | #### Options -| Option | Description | -|---|:---| -| `--api-key=yourApiKey`, `-K=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | -| `--proxy=proxyUrl` | SOCKS4 or SOCKS5 url to proxy all traffic | -| `--type=har/openapi/postman` | The specification type. Helps determine the best way to parse passed files. Default: `har`| -| `--discard=false/true`, `-d=false/true` | When `true`, removes an archive from the cloud storage after the scan is finished running. Default: `true` | -| `--header=extraHeader`, `-H=extraHeader` | Extra headers to be passed with the OAS/Postman file. Also, it can be used to remove a header by providing a name without content, for example: `-H "Host:"`. **WARNING**: headers set with this option override the archive headers and will be set in all the requests | -| `--variable=envVariable`, `-V=envVariable` | Environment variables to be passed with the Postman file. | +| Option | Description | +| ------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--token=yourApiKey`, `-t=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | +| `--type=har/openapi/postman`, `-T=har/openapi/postman` | The specification type. Helps determine the best way to parse passed files. Default: `har` | +| `--discard=false/true`, `-d=false/true` | When `true`, removes an archive from the cloud storage after the scan is finished running. Default: `true` | +| `--header=extraHeader`, `-H=extraHeader` | Extra headers to be passed with the OAS/Postman file. Also, it can be used to remove a header by providing a name without content, for example: `-H "Host:"`. **WARNING**: headers set with this option override the archive headers and will be set in all the requests | +| `--variable=envVariable`, `-V=envVariable` | Environment variables to be passed with the Postman file. | ### 🛠️ Generate Archive Based on NexMock -`nexploit-cli archive:generate [options]` creates HTTP Archive (HAR) files from mock requests generated by -unit-testing and other local automations. +`nexploit-cli archive:generate [options]` creates HTTP Archive (HAR) files from mock requests generated by +unit-testing and other local automations. Supports the latest [NexMock](https://www.npmjs.com/package/@neuralegion/nexmock) API and provides additional features to help you generate HAR files during CI/CD workflows with ease. Provides the ability to split NexMock file into multiply HAR files. For this purpose, you should specify the `--split` option, which accepts the number of pieces to split into. ```bash nexploit-cli archive:generate \ - -m .nexmock \ - -f archive.har \ + -o archive.har \ -t url-tested-application \ - -s 4 + -s 4 \ + .nexmock ``` -This command will create 4 .HAR files which comply with following pattern: `(_)?.`. +This command will create 4 .HAR files which comply with following pattern: `(_)?.`. E.g. `archive.har`, `archive_2.har` and etc. +#### Arguments + +| Argument | Description | +| ------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `` | A NexMock file is obtained from the NexMock Reporters. See [E2E Guide](https://github.com/NeuraLegion/nexploit-cli/wiki/End-to-End-Guide#karma-or-mocha). | + #### Options -| Option | Description | -|---|:---| -| `--archive=newArchivePath`, `-f=newArchivePath` | The path where the new archives will be created, relative to the new workspace root. | -| `--proxy=proxyUrl` | SOCKS4 or SOCKS5 url to proxy all traffic | -| `--mockfile=nexmockPath`, `-m=nexmockPath` | A NexMock file is obtained from the NexMock Reporters. See [E2E Guide](https://github.com/NeuraLegion/nexploit-cli/wiki/End-to-End-Guide#karma-or-mocha) | -| `--target=hostnameOrIp`, `-t=hostnameOrIp` | The target hostname or IP address. | -| `--header=extraHeader`, `-H=extraHeader` | Extra headers to be passed with the NexMock file. Also, it can be used to remove a header by providing a name without content, for example: `-H "Host:"`. **WARNING**: headers set with this option overide the archive headers and will be set in all the requests | -| `--pool=size`, `-p=size` | The size of the worker pool. Indicates how many requests NexPloit CLI can performs in parallel. Default: `250` | -| `--timeout=milliseconds` | The time to wait for a server to send response headers (and start the response body) before aborting the request. Default: `5000` | -| `--split=numberPieces`, `-s=numberPieces` | The number of the HAR pieces. Allows to split a NexMock file into multiple HAR files. Default: `1` | +| Option | Description | +| ---------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `--output=newArchivePath`, `-o=newArchivePath` | The path where the new archives will be created, relative to the new workspace root. | +| `--target=hostnameOrIp`, `-T=hostnameOrIp` | The target hostname or IP address. | +| `--header=extraHeader`, `-H=extraHeader` | Extra headers to be passed with the NexMock file. Also, it can be used to remove a header by providing a name without content, for example: `-H "Host:"`. **WARNING**: headers set with this option overide the archive headers and will be set in all the requests | +| `--pool=size`, `-p=size` | The size of the worker pool. Indicates how many requests NexPloit CLI can performs in parallel. Default: `250` | +| `--timeout=milliseconds` | The time to wait for a server to send response headers (and start the response body) before aborting the request. Default: `5000` | +| `--split=numberPieces`, `-s=numberPieces` | The number of the HAR pieces. Allows to split a NexMock file into multiple HAR files. Default: `1` | ### 🏃 Run Scan -`nexploit-cli scan:run [options]` starts a new scan with the received configuration. +`nexploit-cli scan:run [options]` starts a new scan with the received configuration. > ✴ If you don't have enough available engines, the scan will be placed in the queue. The new scan will start, if you manually stop another running scan or when it is done. @@ -209,30 +225,29 @@ This command allows you to specify one or more of the discovery strategies, for #### Options -| Option | Description | -|---|:---| -| `--api-key=yourApiKey`, `-K=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | -| `--proxy=proxyUrl` | SOCKS4 or SOCKS5 url to proxy all traffic | -| `--name=extraHeader`, `-n=extraHeader` | The name of the scan. | -| `--archive=archiveID`, `-a=archiveID` | The Archive ID, which can be received via the `archive:upload` command. | -| `--crawler=url`, `-c=url` | Allows to specify a list of specific urls that should be included during crawler discovery. | -| `--agent=uuid` | Allows to specify a list of agent UUIDs that should be connected with the scan. | -| `--smart` | Use automatic smart decisions such as: parameter skipping, detection phases, etc. to minimize scan time. When turned off, all the tests will be run on all the parameters, which increases the coverage at the expense of scan time. | -| `--param=path/query/fragment/header/body` | Defines which part of the request to attack. See details: [here](https://kb.neuralegion.com/#/user-guide/scans/new-scan?id=target-parameter-locations-url-scoping). Default: `body`, `query` and `fragment` | -| `--module=dast/fuzzer` | The `dast` module tests for specific scenarios, such as OWASP top 10 and other common scenarios. The `fuzzer` module generates various new scenarios to test for unknown vulnerabilities, providing automated AI guided fuzz-testing. Default: `dast` | -| `--host-filter=hostOrIp`, `-F=hostOrIp` | The list of specific hosts that should be included in the scan. | -| `--header=extraHeader`, `-H=extraHeader` | Extra headers to be passed with the Archive file. Also, it can be used to remove a header by providing a name without content, for example: `-H "Host:"`. **WARNING**: headers set with this option overide the archive headers and will be set in all the requests | -| `--test=test` | Allows specifying a list of relevant tests to execute during a scan, for example: `--tests default_login_location dom_xss` | +| Option | Description | +| ----------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `--token=yourApiKey`, `-t=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | +| `--name=extraHeader`, `-n=extraHeader` | The name of the scan. | +| `--archive=archiveID`, `-a=archiveID` | The Archive ID, which can be received via the `archive:upload` command. | +| `--crawler=url`, `-c=url` | Allows to specify a list of specific urls that should be included during crawler discovery. | +| `--agent=uuid` | Allows to specify a list of agent UUIDs that should be connected with the scan. | +| `--smart` | Use automatic smart decisions such as: parameter skipping, detection phases, etc. to minimize scan time. When turned off, all the tests will be run on all the parameters, which increases the coverage at the expense of scan time. | +| `--param=path/query/fragment/header/body` | Defines which part of the request to attack. See details: [here](https://kb.neuralegion.com/#/user-guide/scans/new-scan?id=target-parameter-locations-url-scoping). Default: `body`, `query` and `fragment` | +| `--module=dast/fuzzer` | The `dast` module tests for specific scenarios, such as OWASP top 10 and other common scenarios. The `fuzzer` module generates various new scenarios to test for unknown vulnerabilities, providing automated AI guided fuzz-testing. Default: `dast` | +| `--host-filter=hostOrIp`, `-F=hostOrIp` | The list of specific hosts that should be included in the scan. | +| `--header=extraHeader`, `-H=extraHeader` | Extra headers to be passed with the Archive file. Also, it can be used to remove a header by providing a name without content, for example: `-H "Host:"`. **WARNING**: headers set with this option overide the archive headers and will be set in all the requests | +| `--test=test` | Allows specifying a list of relevant tests to execute during a scan, for example: `--tests default_login_location dom_xss` | -### 🕵️ Start agent +### 🕵️ Start repeater -`nexploit-cli agent [options]` starts an agent to connect it to the scans +`nexploit-cli repeater [options]` starts an on-prem agent to connect it to the scans -> ✴ Allows to multiple scans under single agent, decreasing the need for multiple agents. +> ✴ Allows to multiple scans under single on-prem agent, decreasing the need to run multiple repeaters. Repeater allows you to run NexPloit scans without exposing your ports outside. Also, it can be useful, if you want to run a local scan without deploying. -> ✴ The agent requires the ability to have an outbound connection to amq.nexploit.app via the AMQ protocol (over TLS) using port 5672 +> ✴ The repeater requires the ability to have an outbound connection to amq.nexploit.app via the AMQ protocol (over TLS) using port 5672 You also can use Docker image to run it. For example, you can use Docker Compose. It's a tool for defining and running multi-container Docker applications. You can download it from [official Docker page](https://docs.docker.com/compose/install/). @@ -240,58 +255,69 @@ You also can use Docker image to run it. For example, you can use Docker Compose version: '3' services: repeater: - image: 'neuralegion/agent' + image: 'neuralegion/repeater' restart: always environment: - API_KEY: 'my-api-key' - AGENT_ID: 'my-agent-id' - AGENT_HEADERS: '{ "X-Header": "my-header" }' + REPEATER_TOKEN: 'my-api-tey' + REPEATER_AGENT: 'my-agent-id' + REPEATER_HEADERS: '{ "X-Header": "my-header" }' ``` or using Docker CLI directly ```bash -docker run neuralegion/agent \ - -e 'AGENT_API_KEY=my-api-key' \ - -e 'AGENT_ID=my-agent-id' \ - -e 'AGENT_HEADERS={ "X-Header": "my-header" }' +docker run neuralegion/repeater \ + -e 'REPEATER_TOKEN=my-api-tey' \ + -e 'REPEATER_AGENT=my-agent-id' \ + -e 'REPEATER_HEADERS={ "X-Header": "my-header" }' ``` -> ✴ The agent requires `AGENT_API_KEY` with the scope `agents:write:repeater` +> ✴ The repeater requires `REPEATER_TOKEN` with the scope `agents:write:repeater` + +You can use the same options which are provided by CLI. You just need to prefix them with `REPEATER_`. List of available env variables you can set: + +- `REPEATER_TOKEN` +- `REPEATER_AGENT` +- `REPEATER_HEADERS` +- `REPEATER_PROXY` +- `REPEATER_BUS` + +If you need to pass headers option, e.g. `Cookie` and `Authorization`, you could use the `REPEATER_HEADERS` variable in JSON format, e.g. + +``` +REPEATER_HEADERS='{"Cookie": "PHPSESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1", "Authorization": "Basic username:password"}' +``` #### Options -| Option | Description | -|---|:---| -| `--id=agentId` | The ID of an existing agent which you want to use. | -| `--api-key=yourApiKey`, `-K=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | -| `--bus=eventBus` | NexPloit Event Bus URL. Default: `amqps://amq.nexploit.app:5672` | +| Option | Description | +| ---------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--agent=agentId` | The ID of an existing agent which you want to use. | +| `--token=yourApiKey`, `-t=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | +| `--bus=eventBus` | NexPloit Event Bus URL. Default: `amqps://amq.nexploit.app:5672` | | `--header=extraHeader`, `-H=extraHeader` | Extra headers to be passed with each request. Also, it can be used to remove a header by providing a name without content, for example: `-H "Host:"`. **WARNING**: headers set with this option override the original headers and will be set in all the requests | -| `--headers=json` | JSON string which contains header list, which is initially empty and consists of zero or more name and value pairs.. **WARNING**: headers set with this option override the original headers and will be set in all the requests. | -| `--proxy=proxyUrl` | SOCKS4 or SOCKS5 url to proxy all traffic | - +| `--headers=json` | JSON string which contains header list, which is initially empty and consists of zero or more name and value pairs.. **WARNING**: headers set with this option override the original headers and will be set in all the requests. | ### 🚓 Check Scan Status `nexploit-cli scan:polling [options] ` configures an ongoing polling of a scan's status, and helps you follow its progress during CI/CD flows. -After the launch, it will check the scan's status most of the time. If the scan finds at least of one medium severity issue, NexPloit CLI will finish with exit code `50`. +After the launch, it will check the scan's status most of the time. If the scan finds at least of one medium severity issue, NexPloit CLI will finish with exit code `50`. #### Arguments -| Argument | Description | -|---|:---| +| Argument | Description | +| -------- | :-------------------------------------------------- | | `` | The ID of an existing scan which you want to check. | #### Options -| Option | Description | -|---|:---| -| `--api-key=yourApiKey`, `-K=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | -| `--proxy=proxyUrl` | SOCKS4 or SOCKS5 url to proxy all traffic | -| `--breakpoint=any / medium-issue / high-issue.` | A conditional breakpoint that allows to finish the process with exit code `50` only after fulfilling the predefined condition. Default: `any`| -| `--interval=milliseconds.` | The period of time between the end of a timeout period or completion of a scan status request, and the next request for status. Eg: 60, "2min", "10h", "7d". A numeric value is interpreted as a milliseconds count. Default: `5000` | -| `--timeout=milliseconds.` | The allowed maximum time for a polling to end normally. Eg: 60, "2min", "10h", "7d". A numeric value is interpreted as a milliseconds count. | +| Option | Description | +| ----------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--token=yourApiKey`, `-t=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | +| `--breakpoint=any / medium_issue / high_issue.` | A conditional breakpoint that allows to finish the process with exit code `50` only after fulfilling the predefined condition. Default: `any` | +| `--interval=milliseconds.` | The period of time between the end of a timeout period or completion of a scan status request, and the next request for status. Eg: 60, "2min", "10h", "7d". A numeric value is interpreted as a milliseconds count. Default: `5000` | +| `--timeout=milliseconds.` | The allowed maximum time for a polling to end normally. Eg: 60, "2min", "10h", "7d". A numeric value is interpreted as a milliseconds count. | ### 🛑 Stop Scan @@ -299,16 +325,15 @@ After the launch, it will check the scan's status most of the time. If the scan #### Arguments -| Argument | Description | -|---|:---| +| Argument | Description | +| -------- | :------------------------------------------------- | | `` | The ID of an existing scan which you want to stop. | #### Options -| Option | Description | -|---|:---| -| `--api-key=yourApiKey`, `-K=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | -| `--proxy=proxyUrl` | SOCKS4 or SOCKS5 url to proxy all traffic | +| Option | Description | +| ------------------------------------- | :------------------------------------------------------------------------------------------------- | +| `--token=yourApiKey`, `-t=yourApiKey` | The unique identifier used to authenticate a user. It can be issued in your organization dashboard | ### 🔁 Retest Scan @@ -316,16 +341,15 @@ After the launch, it will check the scan's status most of the time. If the scan #### Arguments -| Argument | Description | -|---|:---| +| Argument | Description | +| -------- | :--------------------------------------------------- | | `` | The ID of an existing scan which you want to re-run. | #### Options -| Option | Description | -|---|:---| -| `--proxy=proxyUrl` | SOCKS4 or SOCKS5 url to proxy all traffic | -| `--api-key=yourApiKey`, `-K=yourApiKey` | The unique identifier used to authenticate a user that can be issued in your organization dashboard | +| Option | Description | +| ------------------------------------- | :-------------------------------------------------------------------------------------------------- | +| `--token=yourApiKey`, `-t=yourApiKey` | The unique identifier used to authenticate a user that can be issued in your organization dashboard | ## 📝 License diff --git a/src/Commands/GenerateArchive.ts b/src/Commands/GenerateArchive.ts index 57e0f8df..ec1aa231 100644 --- a/src/Commands/GenerateArchive.ts +++ b/src/Commands/GenerateArchive.ts @@ -11,25 +11,21 @@ import { Har } from 'har-format'; import { basename } from 'path'; export class GenerateArchive implements CommandModule { - public readonly command = 'archive:generate'; + public readonly command = 'archive:generate [options] '; public readonly describe = 'Generates a new archive on base unit-tests.'; public builder(args: Argv): Argv { return args - .option('mockfile', { - alias: 'm', - normalize: true, - describe: 'Mock file.', - demandOption: true - }) .option('pool', { alias: 'p', number: true, + requiresArg: true, default: 250, describe: 'Size of the worker pool.' }) .option('timeout', { number: true, + requiresArg: true, default: 5000, describe: 'Time to wait for a server to send response headers (and start the response body) before aborting the request.' @@ -37,27 +33,36 @@ export class GenerateArchive implements CommandModule { .option('split', { alias: 's', number: true, + requiresArg: true, default: 1, describe: 'Number of the HAR chunks. Allows to split a mock file to into multiple HAR files.' }) - .option('archive', { - alias: 'f', + .option('output', { + alias: 'o', normalize: true, + requiresArg: true, describe: 'Name of the archive.', demandOption: true }) .option('target', { - alias: 't', + alias: 'T', + requiresArg: true, describe: 'Target hostname or IP address.', demandOption: true }) .option('header', { alias: 'H', + requiresArg: true, default: [], array: true, describe: 'A list of specific headers that should be included into request.' + }) + .positional('mockfile', { + normalize: true, + describe: 'Mock file.', + demandOption: true }); } @@ -78,7 +83,7 @@ export class GenerateArchive implements CommandModule { logger.log(`${log.entries.length ?? 0} requests were prepared.`); - const harSplitter = new HarSplitter(args.archive as string); + const harSplitter = new HarSplitter(args.output as string); const fileNames: string[] = await harSplitter.split( args.split as number, @@ -96,7 +101,7 @@ export class GenerateArchive implements CommandModule { ); process.exit(0); } catch (e) { - logger.error(`Error during "archive:generate" run: ${e.message}`); + logger.error(`Error during "archive:generate": ${e.message}`); process.exit(1); } } diff --git a/src/Commands/PollingScanStatus.ts b/src/Commands/PollingScanStatus.ts index 62704973..f8e67d17 100644 --- a/src/Commands/PollingScanStatus.ts +++ b/src/Commands/PollingScanStatus.ts @@ -15,20 +15,12 @@ export class PollingScanStatus implements CommandModule { public builder(args: Argv): Argv { return args - .option('api', { - default: 'https://nexploit.app/', - hidden: true, - describe: 'NexPloit base url' - }) - .option('api-key', { - alias: 'K', + .option('token', { + alias: 't', describe: 'NexPloit API-key', requiresArg: true, demandOption: true }) - .option('proxy', { - describe: 'SOCKS4 or SOCKS5 url to proxy all traffic' - }) .option('interval', { requiresArg: true, describe: @@ -43,6 +35,7 @@ export class PollingScanStatus implements CommandModule { 'Eg: 60, "2min", "10h", "7d". A numeric value is interpreted as a milliseconds count.' }) .option('breakpoint', { + alias: 'b', choices: Helpers.toArray(BreakpointType), string: true, describe: @@ -61,7 +54,7 @@ export class PollingScanStatus implements CommandModule { try { const scanManager = new RestScans({ baseUrl: args.api as string, - apiKey: args.apiKey as string, + apiKey: args.token as string, proxyUrl: args.proxy as string }); const polling = new DefaultPolling({ diff --git a/src/Commands/RetestScan.ts b/src/Commands/RetestScan.ts index fcbf00ee..1a1cac33 100644 --- a/src/Commands/RetestScan.ts +++ b/src/Commands/RetestScan.ts @@ -9,20 +9,12 @@ export class RetestScan implements CommandModule { public builder(args: Argv): Argv { return args - .option('api', { - default: 'https://nexploit.app/', - hidden: true, - describe: 'NexPloit base url' - }) - .option('api-key', { - alias: 'K', + .option('token', { + alias: 't', describe: 'NexPloit API-key', requiresArg: true, demandOption: true }) - .option('proxy', { - describe: 'SOCKS4 or SOCKS5 url to proxy all traffic' - }) .positional('scan', { describe: 'ID of an existing scan which you want to re-run.', type: 'string', @@ -34,7 +26,7 @@ export class RetestScan implements CommandModule { try { const scanManager = new RestScans({ baseUrl: args.api as string, - apiKey: args.apiKey as string, + apiKey: args.token as string, proxyUrl: args.proxy as string }); diff --git a/src/Commands/RunAgent.ts b/src/Commands/RunRepeater.ts similarity index 70% rename from src/Commands/RunAgent.ts rename to src/Commands/RunRepeater.ts index 9eafbb02..d67c3645 100644 --- a/src/Commands/RunAgent.ts +++ b/src/Commands/RunRepeater.ts @@ -6,36 +6,32 @@ import logger from '../Utils/Logger'; import { Arguments, Argv, CommandModule } from 'yargs'; const onError = (e: Error) => { - logger.error(`Error during "agent": ${e.message}`); + logger.error(`Error during "repeater": ${e.message}`); process.exit(1); }; -export class RunAgent implements CommandModule { - public readonly command = 'agent'; - public readonly describe = 'Starts an agent by its ID.'; +export class RunRepeater implements CommandModule { + public readonly command = 'repeater [options]'; + public readonly describe = 'Starts an on-prem agent.'; public builder(args: Argv): Argv { return args - .option('api', { - default: 'https://nexploit.app/', - hidden: true, - describe: 'NexPloit base url' - }) .option('bus', { default: 'amqps://amq.nexploit.app:5672', - hidden: true, + demandOption: true, describe: 'NexPloit Event Bus' }) - .option('api-key', { - alias: 'K', + .option('token', { + alias: 't', describe: 'NexPloit API-key', requiresArg: true, demandOption: true }) - .option('id', { + .option('agent', { describe: 'ID of an existing agent which you want to use to run a new scan.', type: 'string', + requiresArg: true, demandOption: true }) .option('header', { @@ -49,30 +45,32 @@ export class RunAgent implements CommandModule { .option('headers', { requiresArg: true, conflicts: ['header'], - default: '{}', describe: 'JSON string which contains header list, which is initially empty and consists of zero or more name and value pairs.' }) - .option('proxy', { - describe: 'SOCKS4 or SOCKS5 url to proxy all traffic' - }) - .env('AGENT') + .env('REPEATER') .exitProcess(false); } public async handler(args: Arguments): Promise { let bus: Bus; - try { - const stop: () => Promise = async (): Promise => { - await bus.destroy(); - process.exit(0); - }; - process.on('SIGTERM', stop).on('SIGINT', stop).on('SIGHUP', stop); - const headers: Record = (args.header as string[])?.length + let headers: Record = {}; + + try { + headers = (args.header as string[])?.length ? Helpers.parseHeaders(args.header as string[]) : JSON.parse(args.headers as string); + } catch { + // noop + } + + const stop: () => Promise = async (): Promise => { + await bus.destroy(); + process.exit(0); + }; + try { const requestExecutor = new DefaultRequestExecutor({ headers, timeout: 5000, @@ -85,18 +83,20 @@ export class RunAgent implements CommandModule { { onError, exchange: 'EventBus', - clientQueue: `agent:${args.id as string}`, + clientQueue: `agent:${args.agent as string}`, connectTimeout: 10000, url: args.bus as string, proxyUrl: args.proxy as string, credentials: { - username: args.id as string, - password: args.apiKey as string + username: args.agent as string, + password: args.token as string } }, handlerRegistry ); + process.on('SIGTERM', stop).on('SIGINT', stop).on('SIGHUP', stop); + await bus.init(); await bus.subscribe(SendRequestHandler); diff --git a/src/Commands/RunScan.ts b/src/Commands/RunScan.ts index e1e80058..8046adc7 100644 --- a/src/Commands/RunScan.ts +++ b/src/Commands/RunScan.ts @@ -5,25 +5,17 @@ import { AttackParamLocation } from '../Scan/Scans'; import { Arguments, Argv, CommandModule } from 'yargs'; export class RunScan implements CommandModule { - public readonly command = 'scan:run'; + public readonly command = 'scan:run [options]'; public readonly describe = 'Start a new scan for the received configuration.'; public builder(args: Argv): Argv { return args - .option('api', { - default: 'https://nexploit.app/', - hidden: true, - describe: 'NexPloit base url' - }) - .option('api-key', { - alias: 'K', + .option('token', { + alias: 't', describe: 'NexPloit API-key', requiresArg: true, demandOption: true }) - .option('proxy', { - describe: 'SOCKS4 or SOCKS5 url to proxy all traffic' - }) .option('name', { alias: 'n', describe: 'Name of the scan.', @@ -138,7 +130,7 @@ export class RunScan implements CommandModule { try { const scanManager = new RestScans({ baseUrl: args.api as string, - apiKey: args.apiKey as string, + apiKey: args.token as string, proxyUrl: args.proxy as string }); diff --git a/src/Commands/StopScan.ts b/src/Commands/StopScan.ts index aa7a4e39..ad03047b 100644 --- a/src/Commands/StopScan.ts +++ b/src/Commands/StopScan.ts @@ -8,22 +8,16 @@ export class StopScan implements CommandModule { public builder(args: Argv): Argv { return args - .option('api', { - default: 'https://nexploit.app/', - hidden: true, - describe: 'NexPloit base url' - }) - .option('api-key', { - alias: 'K', + .option('token', { + alias: 't', describe: 'NexPloit API-key', requiresArg: true, demandOption: true }) - .option('proxy', { - describe: 'SOCKS4 or SOCKS5 url to proxy all traffic' - }) .positional('scan', { describe: 'ID of an existing scan which you want to stop.', + requiresArg: true, + demandOption: true, type: 'string' }); } @@ -32,7 +26,7 @@ export class StopScan implements CommandModule { try { const scanManager = new RestScans({ baseUrl: args.api as string, - apiKey: args.apiKey as string, + apiKey: args.token as string, proxyUrl: args.proxy as string }); diff --git a/src/Commands/UploadArchive.ts b/src/Commands/UploadArchive.ts index a105056b..2b1b6b36 100644 --- a/src/Commands/UploadArchive.ts +++ b/src/Commands/UploadArchive.ts @@ -15,23 +15,14 @@ export class UploadArchive implements CommandModule { public builder(args: Argv): Argv { return args - .option('api', { - default: 'https://nexploit.app/', - hidden: true, - global: true, - describe: 'NexPloit base url' - }) - .option('api-key', { - alias: 'K', + .option('token', { + alias: 't', describe: 'NexPloit API-key', requiresArg: true, demandOption: true }) - .option('proxy', { - describe: 'SOCKS4 or SOCKS5 url to proxy all traffic' - }) .option('type', { - alias: 't', + alias: 'T', requiresArg: true, describe: 'The specification type', choices: [ @@ -52,6 +43,7 @@ export class UploadArchive implements CommandModule { .option('header', { alias: 'H', default: [], + requiresArg: true, array: true, describe: 'A list of specific headers that should be included into request.' @@ -59,6 +51,7 @@ export class UploadArchive implements CommandModule { .option('variable', { alias: 'V', default: [], + requiresArg: true, array: true, describe: 'A list of specific variables that should be included into request. Only for Postman' @@ -68,6 +61,7 @@ export class UploadArchive implements CommandModule { "A collection your app's http/websockets logs into HAR file. " + 'Usually you can use browser dev tools or our browser web extension', type: 'string', + demandOption: true, normalize: true }) .group(['header'], 'OAS Options') @@ -94,7 +88,7 @@ export class UploadArchive implements CommandModule { const archives = new RestArchives({ baseUrl: args.api as string, - apiKey: args.apiKey as string, + apiKey: args.token as string, proxyUrl: args.proxy as string }); diff --git a/src/Commands/index.ts b/src/Commands/index.ts index 36107a4a..49aa864c 100644 --- a/src/Commands/index.ts +++ b/src/Commands/index.ts @@ -5,4 +5,4 @@ export { UploadArchive } from './UploadArchive'; export { RetestScan } from './RetestScan'; export { StopScan } from './StopScan'; export { PollingScanStatus } from './PollingScanStatus'; -export { RunAgent } from './RunAgent'; +export { RunRepeater } from './RunRepeater'; diff --git a/src/Config/CliBuilder.ts b/src/Config/CliBuilder.ts index 5f4781b8..edaff5a2 100644 --- a/src/Config/CliBuilder.ts +++ b/src/Config/CliBuilder.ts @@ -36,23 +36,42 @@ export class CliBuilder { commands: CommandModule[]; configReader: DefaultConfigReader; }): Argv { - const config: CliConfig = configReader.load(this.cwd).toJSON(); - // eslint-disable-next-line @typescript-eslint/no-var-requires const cli: Argv = require('yargs') + .option('config', { + requiresArg: true, + describe: 'Path to the file with configuration', + config: true, + default: configReader.discovery(this.cwd), + configParser: (configPath: string): CliConfig => + configReader.load(configPath).toJSON() + }) + .option('api', { + default: 'https://nexploit.app/', + requiresArg: true, + demandOption: true, + describe: 'NexPloit base URL' + }) + .option('config', { + requiresArg: true, + describe: 'Path to the file with configuration' + }) + .option('proxy', { + requiresArg: true, + describe: 'SOCKS4 or SOCKS5 URL to proxy all traffic' + }) .usage('Usage: $0 [options] []') .pkgConf('nexploit', this._cwd) .example( '$0 archive:generate --mockfile=.mockfile --name=archive.har', 'output har file on base your mock requests' - ) - .config(config); + ); return commands .reduce((acc: Argv, item: CommandModule) => acc.command(item), cli) .recommendCommands() .demandCommand(1) - .strict(false) + .strict(true) .alias('v', 'version') .help('help') .alias('h', 'help'); diff --git a/src/Config/ConfigReader.ts b/src/Config/ConfigReader.ts index 7ccf867b..02880341 100644 --- a/src/Config/ConfigReader.ts +++ b/src/Config/ConfigReader.ts @@ -1,13 +1,13 @@ export interface CliConfig { - apiKey: string; - url?: string; + token?: string; + api?: string; proxy?: string; bus?: string; agent?: string; } export interface ConfigReader { - load(cwd: string): this; + load(rcPath: string): this; get(key: T): CliConfig[T]; diff --git a/src/Config/DefaultConfigReader.ts b/src/Config/DefaultConfigReader.ts index 1e80cd52..c9e6b1f5 100644 --- a/src/Config/DefaultConfigReader.ts +++ b/src/Config/DefaultConfigReader.ts @@ -18,15 +18,13 @@ export class DefaultConfigReader implements ConfigReader { this.config = new Map(); } - public load(cwd: string): this { - const rcPath: string | null = sync(this.rcOptions, { + public discovery(cwd: string): string | undefined { + return sync(this.rcOptions, { cwd }); + } - if (!rcPath) { - return this; - } - + public load(rcPath: string): this { const rcExt: string = path.extname(rcPath.toLowerCase()); if (rcExt === '.js') { diff --git a/src/Utils/Helpers.ts b/src/Utils/Helpers.ts index 9d26a265..5998f630 100644 --- a/src/Utils/Helpers.ts +++ b/src/Utils/Helpers.ts @@ -50,13 +50,12 @@ export class Helpers { 'First argument must be an instance of String.' ); - return header - ? (header - .split(':', 2) - .map((item: string) => decodeURIComponent(item.trim())) as [ - string, - string - ]) - : undefined; + if (header) { + const [key, ...values]: string[] = header.split(':'); + + return [key, values.join(':')].map((item: string) => + decodeURIComponent(item.trim()) + ) as [string, string]; + } } } diff --git a/src/index.ts b/src/index.ts index dbad86cb..0ba5bfde 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ import { GenerateArchive, PollingScanStatus, RetestScan, - RunAgent, + RunRepeater, RunScan, StopScan, UploadArchive, @@ -20,7 +20,7 @@ cli .build({ configReader: new DefaultConfigReader(), commands: [ - new RunAgent(), + new RunRepeater(), new VersionCommand(), new GenerateArchive(), new PollingScanStatus(),