purpleteam
Advanced tools
Comparing version 2.0.0-alpha.3 to 3.0.0-alpha.3
@@ -185,4 +185,4 @@ // Copyright (C) 2017-2021 BinaryMist Limited. All rights reserved. | ||
doc: 'The version of the Job accepted by the PurpleTeam API.', | ||
format: ['0.1.0-alpha.1', '1.0.0-alpha.3', '2.0.0-alpha.3'], | ||
default: '2.0.0-alpha.3' | ||
format: ['0.1.0-alpha.1', '1.0.0-alpha.3', '2.0.0-alpha.3', '3.0.0-alpha.3'], | ||
default: '3.0.0-alpha.3' | ||
} | ||
@@ -189,0 +189,0 @@ }, |
@@ -6,3 +6,3 @@ { | ||
}, | ||
"version": "2.0.0-alpha.3", | ||
"version": "3.0.0-alpha.3", | ||
"description": "CLI for driving purpleteam -- security regression testing SaaS", | ||
@@ -9,0 +9,0 @@ "main": "src/index.js", |
@@ -31,3 +31,3 @@ <div align="center"> | ||
If you are planning on running the `local` environment, once you have installed, configured and are ready to run the _PurpleTeam_ CLI, head back to the [local setup](https://purpleteam-labs.com/doc/local/set-up/) documentation and make sure all of the otther _PurpleTeam_ components are also set-up and ready to run. After that work through the [local workflow](https://purpleteam-labs.com/doc/local/workflow/) documentation. | ||
If you are planning on running the `local` environment, once you have installed, configured and are ready to run the _PurpleTeam_ CLI, head back to the [local setup](https://purpleteam-labs.com/doc/local/set-up/) documentation and make sure all of the other _PurpleTeam_ components are also set-up and ready to run. After that work through the [local workflow](https://purpleteam-labs.com/doc/local/workflow/) documentation. | ||
@@ -46,4 +46,4 @@ If you are planning on targeting the `cloud` environment, the _PurpleTeam_ CLI is all you need to have set-up. | ||
* [Configure](#configure) | ||
* [CLI](#cli) | ||
* [Job File](#job-file) | ||
* [CLI](#cli) | ||
* [Run](#run) | ||
@@ -76,3 +76,3 @@ * [Clone the git repository option](#clone-the-git-repository-option) | ||
If you are planning on running/debugging _purpleteam_ standalone, cloning is a good option. | ||
If you are planning on running/debugging _purpleteam_ stand-alone, cloning is a good option. | ||
@@ -204,15 +204,2 @@ From a directory that you would like the CLI cloned to run the following command: | ||
## Job File | ||
The [_Job_](https://purpleteam-labs.com/doc/definitions/) file is what purpleteam uses to do the following. Most properties should be self documenting, although the official documentation is [here](https://purpleteam-labs.com/doc/job-file/). If you are unsure of any of the properties, start a [Github discussion](https://github.com/purpleteam-labs/purpleteam/discussions) or reach out in the [#project-purpleteam channel of OWASP Slack](https://owasp.slack.com/messages/project-purpleteam). | ||
Examples of _Job_ files that the PurpleTeam-Labs team uses can be found [here](https://github.com/purpleteam-labs/purpleteam/tree/main/testResources/jobs). You will need to define the following: | ||
* Authenticate to your [System Under Test (_SUT_)](https://purpleteam-labs.com/doc/definitions/) | ||
* Locate your _SUT_ | ||
* Which browser to use to test your application in | ||
* Define your [_Test Session_](https://purpleteam-labs.com/doc/definitions/)(s) | ||
* Define `alertThreshold`s | ||
* Define routes to test | ||
* Define fields of each specific route, other fields "may" also be tested | ||
## CLI | ||
@@ -249,3 +236,3 @@ | ||
If you installed the _PurpleTeam_ CLI via `git clone` (You are intending to run _PurpleTeam_ CLI standalone), then a relative directory path from the root of the repository ("./testResources/jobs/your_job_file") is acceptable. | ||
If you installed the _PurpleTeam_ CLI via `git clone` (You are intending to run _PurpleTeam_ CLI stand-alone), then a relative directory path from the root of the repository ("./testResources/jobs/your_job_file") is acceptable. | ||
If you installed the _PurpleTeam_ CLI via `npm install` Then it's more likely that you will need this path to be absolute, as the current directory (./) is more than likely not going to be within the _PurpleTeam_ CLI project itself, but rather wherever the purpleteam binary is itself. | ||
@@ -296,4 +283,4 @@ | ||
`Tester failure: The only valid number of appScanner resource objects is from 1-12 inclusive. Please modify your Job file.` | ||
* `Tester failure: S2 app containers were not ready. app Tester(s) failed initialisation. Test Run aborted` - This occurres in the `cloud` environment if ECS doesn't bring the stage two containers up in time. The App Tester gives ECS 2 minutes to bring the stage two containers up, usually they come up from cold start with 40 seconds to spare, if they don't come up in 2 minutes then the App _Tester_ decides it is unable to start a _Test Run_ due to circumstances outside of it's control (ECS is not going to bring the stage two containers up) and the _orchestrator_ aborts the _Test Run_ with this message. The _orchestrator_ then issues the order to bring all stage two containers down (clean-up). | ||
As the _Build User_ you can rely on the text `Tester failure:` to mean you will need to initiate a retry. You can do this after some time, or continue to issue the CLI `status` command, after aproximatly 50 seconds the response will change from `Test Run is in progress.` to `orchestrator is ready to take orders.`, at which point you can initiate a retry (run the `test` command again) | ||
* `Tester failure: S2 app containers were not ready. app Tester(s) failed initialisation. Test Run aborted` - This occurs in the `cloud` environment if ECS doesn't bring the stage two containers up in time. The App Tester gives ECS 2 minutes to bring the stage two containers up, usually they come up from cold start with 40 seconds to spare, if they don't come up in 2 minutes then the App _Tester_ decides it is unable to start a _Test Run_ due to circumstances outside of it's control (ECS is not going to bring the stage two containers up) and the _orchestrator_ aborts the _Test Run_ with this message. The _orchestrator_ then issues the order to bring all stage two containers down (clean-up). | ||
As the _Build User_ you can rely on the text `Tester failure:` to mean you will need to initiate a retry. You can do this after some time, or continue to issue the CLI `status` command, after approximately 50 seconds the response will change from `Test Run is in progress.` to `orchestrator is ready to take orders.`, at which point you can initiate a retry (run the `test` command again) | ||
* `testplan`: Writes to file using purpleteam-logger configured with the `File` transport | ||
@@ -307,3 +294,3 @@ * If the orchestrator/API is down `orchestrator is down, or an incorrect URL has been specified in the CLI config` is written using the `SignaleTransport`. Exits with code: "0" | ||
## Sensitive Values (`cloud` environment only) | ||
### Sensitive Values (`cloud` environment only) | ||
@@ -321,2 +308,17 @@ There are several ways you can handle the sensitive values that need to be read into the _PurpleTeam_ CLI to access your instance of the _PurpleTeam_ `cloud` service: | ||
## Job File | ||
The [_Job_](https://purpleteam-labs.com/doc/definitions/) file is what purpleteam uses to do the following. Most properties should be self documenting, although the official documentation is [here](https://purpleteam-labs.com/doc/jobfile/). If you are unsure of any of the properties, start a [Github discussion](https://github.com/purpleteam-labs/purpleteam/discussions) or reach out in the [#project-purpleteam channel of OWASP Slack](https://owasp.slack.com/messages/project-purpleteam). | ||
Examples of _Job_ files that the PurpleTeam-Labs team use can be found [here](https://github.com/purpleteam-labs/purpleteam/tree/main/testResources/jobs). Once you have defined the location of your SUT, you may want to consider defining some of the following: | ||
* Defining your [_Test Session_](https://purpleteam-labs.com/doc/definitions/)(s) | ||
* Authentication to your [System Under Test (_SUT_)](https://purpleteam-labs.com/doc/definitions/) | ||
* Which browser you may want to use to test your application in (not applicable to APIs) | ||
* Defining `alertThreshold`s | ||
* Defining specific routes to test (for browser based applications), or an API definition | ||
* Defining fields of each specific route (for browser based applications), other fields "may" also be tested | ||
* Work through the _Job_ file documentation, there are many additional knobs and levers you can apply and tweak | ||
Remember to keep it simple to start with. | ||
# Run | ||
@@ -368,3 +370,3 @@ | ||
For further details around running and debuging review the [documentation](https://purpleteam-labs.com/doc/local/workflow/). | ||
For further details around running and debugging review the [documentation](https://purpleteam-labs.com/doc/local/workflow/). | ||
@@ -519,3 +521,3 @@ ### Run the bin/purpleteam file directly | ||
* [down-arrow], [up-arrow] to highlight the different Running Statistics of the _Testers_ as they are provided in real-time courtesy of the _PurpleTeam_ API | ||
* `testplan`: Once the test plans have been retreived, you can [right-arrow], [left-arrow] through the terminal screens to view the test plans of each specific [_Tester_](https://purpleteam-labs.com/doc/definitions/) | ||
* `testplan`: Once the test plans have been retrieved, you can [right-arrow], [left-arrow] through the terminal screens to view the test plans of each specific [_Tester_](https://purpleteam-labs.com/doc/definitions/) | ||
@@ -522,0 +524,0 @@ # Trouble-Shooting |
@@ -7,4 +7,252 @@ const internals = { config: null }; | ||
const schema = {}; | ||
const schema = { | ||
$schema: 'http://json-schema.org/draft-07/schema#', | ||
$ref: '#/definitions/Job', | ||
definitions: { | ||
Job: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
data: { $ref: '#/definitions/Data' }, | ||
included: { | ||
type: 'array', | ||
items: { $ref: '#/definitions/TopLevelResourceObject' } | ||
} | ||
}, | ||
required: [ | ||
'data', | ||
'included' | ||
], | ||
title: 'Job' | ||
}, | ||
Data: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
type: { type: 'string', enum: ['Api'] }, | ||
attributes: { $ref: '#/definitions/DataAttributes' }, | ||
relationships: { $ref: '#/definitions/Relationships' } | ||
}, | ||
required: [ | ||
'attributes', | ||
'relationships', | ||
'type' | ||
], | ||
title: 'Data' | ||
}, | ||
DataAttributes: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
version: { type: 'string', get const() { return internals.config.job.version; } }, | ||
sutAuthentication: { $ref: '#/definitions/SutAuthentication' }, | ||
sutIp: { type: 'string', oneOf: [{ format: 'ipv6' }, { format: 'hostname' }] }, | ||
sutPort: { type: 'integer', minimum: 1, maximum: 65535 }, | ||
sutProtocol: { type: 'string', enum: ['https', 'http'], default: 'https' }, | ||
loggedInIndicator: { type: 'string', minLength: 1 }, | ||
loggedOutIndicator: { type: 'string', minLength: 1 } | ||
}, | ||
oneOf: [ | ||
{ required: ['loggedInIndicator'] }, | ||
{ required: ['loggedOutIndicator'] } | ||
], | ||
required: [ | ||
'sutAuthentication', | ||
'sutIp', | ||
'sutPort', | ||
'sutProtocol', | ||
'version' | ||
], | ||
title: 'DataAttributes' | ||
}, | ||
SutAuthentication: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
emissaryAuthenticationStrategy: { type: 'string', enum: ['MaintainJwt'], default: 'MaintainJwt' }, | ||
route: { type: 'string', pattern: '^/[-?&=\\w/]{1,1000}$' } | ||
}, | ||
required: [ | ||
'route' | ||
], | ||
title: 'SutAuthentication' | ||
}, | ||
Relationships: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
data: { | ||
type: 'array', | ||
items: { $ref: '#/definitions/ResourceLinkage' } | ||
} | ||
}, | ||
required: [ | ||
'data' | ||
], | ||
title: 'Relationships' | ||
}, | ||
ResourceLinkage: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
type: { type: 'string', enum: ['tlsScanner', 'appScanner'] }, | ||
id: { type: 'string' } | ||
}, | ||
required: ['id', 'type'], | ||
if: { properties: { type: { enum: ['tlsScanner'] } } }, | ||
then: { properties: { id: { type: 'string', pattern: 'NA' } } }, | ||
else: { | ||
if: { properties: { type: { enum: ['appScanner'] } } }, | ||
then: { properties: { id: { type: 'string', pattern: '^\\w[-\\w]{1,200}$' } } } | ||
}, | ||
title: 'ResourceLinkage' | ||
}, | ||
TopLevelResourceObject: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
type: { type: 'string', enum: ['tlsScanner', 'appScanner'] }, | ||
id: { type: 'string' }, | ||
attributes: {}, | ||
relationships: {} | ||
}, | ||
required: [ | ||
'attributes', | ||
'id', | ||
'type' | ||
], | ||
if: { properties: { type: { enum: ['tlsScanner'] } } }, | ||
then: { | ||
properties: { | ||
id: { type: 'string', pattern: 'NA' }, | ||
attributes: { $ref: '#/definitions/AttributesObjOfTopLevelResourceObjectOfTypeTlsScanner' } | ||
} | ||
}, | ||
// If we want to use flags for regex, etc, then need to use ajv-keywords: https://github.com/epoberezkin/ajv-keywords#regexp | ||
else: { | ||
if: { properties: { type: { enum: ['appScanner'] } } }, | ||
then: { | ||
properties: { | ||
id: { type: 'string', pattern: '^\\w[-\\w]{1,200}$' }, | ||
attributes: { $ref: '#/definitions/AttributesObjOfTopLevelResourceObjectOfTypeAppScanner' } | ||
} | ||
} | ||
}, | ||
title: 'TopLevelResourceObject', | ||
errorMessage: { | ||
properties: { | ||
type: 'should be one of either tlsScanner or appScanner', | ||
id: 'If type is tlsScanner, the id should be NA. If type is appScanner, the id should be a valid appScanner.' | ||
} | ||
} | ||
}, | ||
AttributesObjOfTopLevelResourceObjectOfTypeTlsScanner: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
tlsScannerSeverity: { type: 'string', enum: ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] }, | ||
alertThreshold: { type: 'integer', minimum: 0, maximum: 9999 } | ||
}, | ||
required: [], | ||
title: 'AttributesObjOfTopLevelResourceObjectOfTypeTlsScanner' | ||
}, | ||
AttributesObjOfTopLevelResourceObjectOfTypeAppScanner: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
username: { type: 'string', pattern: '^([a-zA-Z0-9_-]{1,100}|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z0-9]{2,})$' }, // https://www.py4u.net/discuss/1646374 | ||
aScannerAttackStrength: { type: 'string', enum: ['LOW', 'MEDIUM', 'HIGH', 'INSANE'] }, | ||
aScannerAlertThreshold: { type: 'string', enum: ['LOW', 'MEDIUM', 'HIGH'] }, | ||
alertThreshold: { type: 'integer', minimum: 0, maximum: 9999 }, | ||
sitesTreePopulationStrategy: { type: 'string', enum: ['ImportUrls', 'OpenApi', 'Soap', 'GraphQl'], default: 'ImportUrls' }, | ||
spiderStrategy: { type: 'string', enum: ['Standard'], default: 'Standard' }, | ||
scannersStrategy: { type: 'string', enum: ['ApiStandard'], default: 'ApiStandard' }, | ||
scanningStrategy: { type: 'string', enum: ['ApiStandard'], default: 'ApiStandard' }, | ||
postScanningStrategy: { type: 'string', enum: ['ApiStandard'], default: 'ApiStandard' }, | ||
reportingStrategy: { type: 'string', enum: ['Standard'], default: 'Standard' }, | ||
openApi: { $ref: '#/definitions/OpenApi' }, | ||
soap: { $ref: '#/definitions/Soap' }, | ||
graphQl: { $ref: '#/definitions/GraphQl' }, | ||
importUrls: { $ref: '#/definitions/ImportUrls' }, | ||
excludedRoutes: { | ||
type: 'array', | ||
items: { type: 'string' }, | ||
uniqueItems: true, | ||
minItems: 0 | ||
} | ||
}, | ||
oneOf: [ | ||
{ required: ['openApi'] }, | ||
{ required: ['soap'] }, | ||
{ required: ['graphQl'] }, | ||
{ required: ['importUrls'] } | ||
], | ||
required: ['username'], | ||
title: 'AttributesObjOfTopLevelResourceObjectOfTypeAppScanner' | ||
}, | ||
OpenApi: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
importFileContentBase64: { type: 'string', pattern: '^(?:[A-Za-z\\d+/]{4})*(?:[A-Za-z\\d+/]{3}=|[A-Za-z\\d+/]{2}==)?$' }, // https://regexland.com/base64/ | ||
importUrl: { type: 'string', format: 'uri' } | ||
}, | ||
oneOf: [ | ||
{ required: ['importFileContentBase64'] }, | ||
{ required: ['importUrl'] } | ||
], | ||
required: [], | ||
title: 'OpenApi' | ||
}, | ||
Soap: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
importFileContentBase64: { type: 'string', pattern: '^(?:[A-Za-z\\d+/]{4})*(?:[A-Za-z\\d+/]{3}=|[A-Za-z\\d+/]{2}==)?$' }, // https://regexland.com/base64/ | ||
importUrl: { type: 'string', format: 'uri' } | ||
}, | ||
oneOf: [ | ||
{ required: ['importFileContentBase64'] }, | ||
{ required: ['importUrl'] } | ||
], | ||
required: [], | ||
title: 'Soap' | ||
}, | ||
GraphQl: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
importFileContentBase64: { type: 'string', pattern: '^(?:[A-Za-z\\d+/]{4})*(?:[A-Za-z\\d+/]{3}=|[A-Za-z\\d+/]{2}==)?$' }, // https://regexland.com/base64/ | ||
importUrl: { type: 'string', format: 'uri' }, | ||
// If the following are not set, then no changes to Zaproxy defaults are made. | ||
maxQueryDepth: { type: 'integer', minimum: 0, maximum: 100 }, // Zaproxy default: 5 | ||
maxArgsDepth: { type: 'integer', minimum: 0, maximum: 100 }, // Zaproxy default: 5 | ||
optionalArgsEnabled: { type: 'boolean' }, // Zaproxy default: true | ||
argsType: { type: 'string', enum: ['INLINE', 'VARIABLES', 'BOTH'] }, // Zaproxy default: 'BOTH' | ||
querySplitType: { type: 'string', enum: ['LEAF', 'ROOT_FIELD', 'OPERATION'] }, // Zaproxy default: 'LEAF' | ||
requestMethod: { type: 'string', enum: ['POST_JSON', 'POST_GRAPHQL', 'GET'] } // Zaproxy default: 'POST_JSON' | ||
}, | ||
oneOf: [ | ||
{ required: ['importFileContentBase64'] }, | ||
{ required: ['importUrl'] } | ||
], | ||
required: [], | ||
title: 'GraphQl' | ||
}, | ||
ImportUrls: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { importFileContentBase64: { type: 'string', pattern: '^(?:[A-Za-z\\d+/]{4})*(?:[A-Za-z\\d+/]{3}=|[A-Za-z\\d+/]{2}==)?$' } }, // https://regexland.com/base64/ | ||
required: ['importFileContentBase64'], | ||
title: 'ImportUrls' | ||
} | ||
} | ||
}; | ||
module.exports = { init, schema }; |
@@ -177,3 +177,3 @@ const internals = { config: null }; | ||
tlsScannerSeverity: { type: 'string', enum: ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] }, | ||
alertThreshold: { type: 'integer', minimum: 0, maximum: 1000 } | ||
alertThreshold: { type: 'integer', minimum: 0, maximum: 9999 } | ||
}, | ||
@@ -192,12 +192,12 @@ required: [], | ||
aScannerAlertThreshold: { type: 'string', enum: ['LOW', 'MEDIUM', 'HIGH'] }, | ||
alertThreshold: { type: 'integer', minimum: 0, maximum: 1000 }, | ||
alertThreshold: { type: 'integer', minimum: 0, maximum: 9999 }, | ||
sitesTreePopulationStrategy: { type: 'string', enum: ['WebDriverStandard'], default: 'WebDriverStandard' }, | ||
spiderStrategy: { type: 'string', enum: ['BrowserAppStandard'], default: 'BrowserAppStandard' }, | ||
spiderStrategy: { type: 'string', enum: ['Standard'], default: 'Standard' }, | ||
scannersStrategy: { type: 'string', enum: ['BrowserAppStandard'], default: 'BrowserAppStandard' }, | ||
scanningStrategy: { type: 'string', enum: ['Standard'], default: 'Standard' }, | ||
postScanningStrategy: { type: 'string', enum: ['Standard'], default: 'Standard' }, | ||
scanningStrategy: { type: 'string', enum: ['BrowserAppStandard'], default: 'BrowserAppStandard' }, | ||
postScanningStrategy: { type: 'string', enum: ['BrowserAppStandard'], default: 'BrowserAppStandard' }, | ||
reportingStrategy: { type: 'string', enum: ['Standard'], default: 'Standard' }, | ||
excludedRoutes: { | ||
type: 'array', | ||
items: { type: 'string', pattern: '^/[-?&=.*\\w/]{1,1000}$' }, | ||
items: { type: 'string' }, | ||
uniqueItems: true, | ||
@@ -207,3 +207,3 @@ minItems: 0 | ||
}, | ||
required: [], | ||
required: ['username'], | ||
title: 'AttributesObjOfTopLevelResourceObjectOfTypeAppScanner' | ||
@@ -210,0 +210,0 @@ }, |
@@ -82,6 +82,6 @@ // Copyright (C) 2017-2021 BinaryMist Limited. All rights reserved. | ||
strokeWidth: 0.9, | ||
elements: 2, | ||
display: '00', | ||
elementSpacing: 4, | ||
elementPadding: 4, | ||
elements: 3, | ||
display: '000', | ||
elementSpacing: 3, | ||
elementPadding: 3, | ||
color: 'blue', | ||
@@ -88,0 +88,0 @@ style: { fg: 'default', bg: 'default', border: { fg: 'magenta', bg: 'default' } } |
@@ -164,3 +164,5 @@ // Copyright (C) 2017-2021 BinaryMist Limited. All rights reserved. | ||
infoOuts[testerType].testerPctComplete.percent = infoOuts[testerType] | ||
.statTable.records.reduce((accum, curr) => accum + curr.pctComplete, 0) / infoOuts[testerType].statTable.records.length; | ||
.statTable.records.reduce((accum, curr) => accum + (curr.pctComplete), 0) | ||
/ infoOuts[testerType].statTable.records.length | ||
/ 100; | ||
infoOuts[testerType].testerPctComplete.color = colourOfDonut(infoOuts[testerType].testerPctComplete.percent); | ||
@@ -167,0 +169,0 @@ // totalProgress |
172094
2338
518