@corellium/corellium-cli 1.0.9 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +8 -0
- package/.nycrc +16 -0
- package/package.json +4 -2
- package/sonar-project.properties +2 -0
- package/src/{commands/agent → clients}/Agent.js +1 -1
- package/src/clients/Client.js +89 -0
- package/src/clients/Instance.js +84 -0
- package/src/{TrialAccount.js → clients/TrialAccount.js} +3 -3
- package/src/{commands/webplayer/WebPlayer.js → clients/Webplayer.js} +9 -9
- package/src/commands/agent/apps.js +1 -1
- package/src/commands/agent/file.js +1 -1
- package/src/commands/agent/ready.js +1 -1
- package/src/commands/instances/index.js +3 -1
- package/src/commands/instances/netmon/disable.js +44 -0
- package/src/commands/instances/netmon/download.js +50 -0
- package/src/commands/instances/netmon/enable.js +72 -0
- package/src/commands/instances/netmon/index.js +15 -0
- package/src/commands/instances/netmon/stream.js +48 -0
- package/src/commands/instances/restoreBackup.js +77 -0
- package/src/commands/instances/rotate.js +47 -0
- package/src/commands/instances/start.js +18 -9
- package/src/commands/instances/upgrade.js +1 -1
- package/src/commands/signup.js +2 -2
- package/src/commands/webplayer/create.js +5 -5
- package/src/commands/webplayer/destroy.js +6 -6
- package/src/commands/webplayer/index.js +1 -1
- package/src/commands/webplayer/list.js +4 -4
- package/src/commands/webplayer/login.js +6 -6
- package/src/upload.js +6 -4
- package/src/utils.js +18 -0
- package/src/commands/Client.js +0 -45
- package/src/commands/instances/Instance.js +0 -27
package/.gitlab-ci.yml
CHANGED
package/.nycrc
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
{
|
2
|
+
"check-coverage": false,
|
3
|
+
"report-dir": "./coverage",
|
4
|
+
"reporter": ["cobertura", "lcov", "text"],
|
5
|
+
"branches": 60,
|
6
|
+
"lines": 60,
|
7
|
+
"functions": 60,
|
8
|
+
"statements": 60,
|
9
|
+
"temp-dir": "/tmp",
|
10
|
+
"watermarks": {
|
11
|
+
"lines": [60, 80],
|
12
|
+
"functions": [60, 80],
|
13
|
+
"branches": [60, 80],
|
14
|
+
"statements": [60, 80]
|
15
|
+
}
|
16
|
+
}
|
package/package.json
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
{
|
2
2
|
"name": "@corellium/corellium-cli",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.1.1",
|
4
4
|
"description": "Corellium CLI Tool",
|
5
5
|
"scripts": {
|
6
6
|
"corellium": "node index.js",
|
7
7
|
"lint": "eslint .",
|
8
8
|
"lint-staged": "lint-staged",
|
9
|
-
"lint:fix": "eslint --fix ."
|
9
|
+
"lint:fix": "eslint --fix .",
|
10
|
+
"test:ci": "npx nyc report"
|
10
11
|
},
|
11
12
|
"bin": {
|
12
13
|
"corellium": "index.sh"
|
@@ -32,6 +33,7 @@
|
|
32
33
|
"progress": "^2.0.3",
|
33
34
|
"prompts": "^2.4.2",
|
34
35
|
"uuid": "^8.3.2",
|
36
|
+
"websocket-stream": "^5.5.2",
|
35
37
|
"xhr2": "^0.2.0",
|
36
38
|
"yargs": "^17.5.1"
|
37
39
|
},
|
@@ -0,0 +1,89 @@
|
|
1
|
+
const axios = require('axios').default
|
2
|
+
const { readProfile } = require('../profile')
|
3
|
+
const { createWriteStream } = require('fs')
|
4
|
+
const wsstream = require('websocket-stream')
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Client Base class
|
8
|
+
*/
|
9
|
+
class Client {
|
10
|
+
constructor (argv) {
|
11
|
+
this.argv = Object.assign({}, argv)
|
12
|
+
this.project = argv.project || argv.projectId
|
13
|
+
this.instance = argv.instance || argv.instanceId
|
14
|
+
this._profile = readProfile()
|
15
|
+
this._version = require('../../package.json').version
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* @protected
|
20
|
+
* @returns default headers
|
21
|
+
*/
|
22
|
+
_headers = () => ({
|
23
|
+
Authorization: `Bearer ${this._profile.token}`,
|
24
|
+
'User-Agent': `corellium-cli v${this._version}`
|
25
|
+
})
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Retrieve file stream
|
29
|
+
* @protected
|
30
|
+
* @param {string} path
|
31
|
+
* @param {PathLike} outputFile
|
32
|
+
* @returns {Promise<any>}
|
33
|
+
*/
|
34
|
+
_fetchStream = async (path, outputFile) => {
|
35
|
+
const response = await axios({
|
36
|
+
method: 'GET',
|
37
|
+
url: `${this._profile.endpoint}/api/${path}`,
|
38
|
+
headers: this._headers(),
|
39
|
+
responseType: 'stream'
|
40
|
+
})
|
41
|
+
|
42
|
+
const writeStream = createWriteStream(outputFile)
|
43
|
+
response.data.pipe(writeStream)
|
44
|
+
|
45
|
+
return new Promise((resolve, reject) => {
|
46
|
+
writeStream.on('finish', resolve)
|
47
|
+
writeStream.on('error', reject)
|
48
|
+
})
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Get Websocket stream
|
53
|
+
* @protected
|
54
|
+
* @param {string} token
|
55
|
+
* @returns {wsstream.WebSocketDuplex}
|
56
|
+
*/
|
57
|
+
_fetchWSS = (token) => {
|
58
|
+
const url = `wss://${this._profile.endpoint}/api/v1/agent/${token}`
|
59
|
+
return wsstream(url, ['binary'])
|
60
|
+
}
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Perform HTTP transaction
|
64
|
+
* @protected
|
65
|
+
* @param {string} method
|
66
|
+
* @param {string} path
|
67
|
+
* @param {object} data
|
68
|
+
* @returns {Promise<any>}
|
69
|
+
*/
|
70
|
+
_fetch = async (method, path, data) => {
|
71
|
+
const headers = this._headers()
|
72
|
+
let response
|
73
|
+
switch (method) {
|
74
|
+
case 'GET':
|
75
|
+
response = await axios.get(`${this._profile.endpoint}/api/${path}`, { headers })
|
76
|
+
break
|
77
|
+
case 'POST':
|
78
|
+
response = await axios.post(`${this._profile.endpoint}/api/${path}`, data, { headers })
|
79
|
+
break
|
80
|
+
case 'DELETE':
|
81
|
+
response = await axios.delete(`${this._profile.endpoint}/api/${path}`, { headers })
|
82
|
+
break
|
83
|
+
}
|
84
|
+
if (response.status >= 400) { throw new Error(response.statusText) }
|
85
|
+
return response.data || {}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
module.exports = Client
|
@@ -0,0 +1,84 @@
|
|
1
|
+
const Client = require('./Client')
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Instance class
|
5
|
+
*/
|
6
|
+
class InstanceCLI extends Client {
|
7
|
+
instanceBasePath = () => `v1/instances/${this.instance}`
|
8
|
+
preauthedBasePath = () => 'v1/preauthed'
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Upgrade iOS version on instance
|
12
|
+
* @returns {Promise<any>}
|
13
|
+
*/
|
14
|
+
upgrade = async (options) => {
|
15
|
+
const { os, osbuild } = options
|
16
|
+
return await this._fetch('POST', `${this.instanceBasePath()}/upgrade`, { os, osbuild })
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Get websocket stream from token
|
21
|
+
* @returns {wsstream.WebSocketDuplex}
|
22
|
+
*/
|
23
|
+
wss = (token) => {
|
24
|
+
return this._fetchWSS(token)
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Start instance
|
29
|
+
*/
|
30
|
+
start = async (options) => {
|
31
|
+
const { sockcap, paused } = options
|
32
|
+
return await this._fetch('POST', `${this.instanceBasePath()}/start`, { sockcap, paused })
|
33
|
+
}
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Restore backup to instance
|
37
|
+
* @returns {Promise<any>}
|
38
|
+
*/
|
39
|
+
restoreBackup = async () => {
|
40
|
+
return await this._fetch('POST', `${this.instanceBasePath()}/restoreBackup`)
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Enable monitoring
|
45
|
+
* @returns {Promise<any>}
|
46
|
+
*/
|
47
|
+
enableMonitoring = async (options) => {
|
48
|
+
return await this._fetch('POST', `${this.instanceBasePath()}/netdump/enable`, options)
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Disable monitoring
|
53
|
+
* @returns {Promise<any>}
|
54
|
+
*/
|
55
|
+
disableMonitoring = async () => {
|
56
|
+
return await this._fetch('POST', `${this.instanceBasePath()}/netdump/disable`)
|
57
|
+
}
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Authorize pcap download
|
61
|
+
* @returns {Promise<any>}
|
62
|
+
*/
|
63
|
+
authorizePcap = async () => {
|
64
|
+
return await this._fetch('POST', `${this.instanceBasePath()}/netdump-authorize`)
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Download file
|
69
|
+
* @returns {Promise<any>}
|
70
|
+
*/
|
71
|
+
download = async (token, file, outputFile) => {
|
72
|
+
return await this._fetchStream(`${this.preauthedBasePath()}/${token}/${file}`, outputFile)
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Fetch instance information
|
77
|
+
* @returns {Promise<any>}
|
78
|
+
*/
|
79
|
+
info = async () => {
|
80
|
+
return await this._fetch('GET', `${this.instanceBasePath()}`, {})
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
module.exports = InstanceCLI
|
@@ -1,6 +1,6 @@
|
|
1
|
-
const Client = require('./
|
2
|
-
const util = require('
|
3
|
-
const { handler: login } = require('
|
1
|
+
const Client = require('./Client')
|
2
|
+
const util = require('../utils')
|
3
|
+
const { handler: login } = require('../commands/login')
|
4
4
|
|
5
5
|
class TrialAccount extends Client {
|
6
6
|
constructor (argv) {
|
@@ -1,12 +1,12 @@
|
|
1
|
-
const Client = require('
|
1
|
+
const Client = require('./Client')
|
2
2
|
const {
|
3
3
|
updateProfile
|
4
|
-
} = require('
|
4
|
+
} = require('../profile')
|
5
5
|
|
6
6
|
/**
|
7
|
-
*
|
7
|
+
* Webplayer class
|
8
8
|
*/
|
9
|
-
class
|
9
|
+
class Webplayer extends Client {
|
10
10
|
constructor (argv) {
|
11
11
|
super(argv)
|
12
12
|
const { features, permissions, expiresIn } = this.argv
|
@@ -16,7 +16,7 @@ class WebPlayer extends Client {
|
|
16
16
|
}
|
17
17
|
|
18
18
|
/**
|
19
|
-
* Create a
|
19
|
+
* Create a Webplayer session
|
20
20
|
* @returns {Promise<Array<object>>}
|
21
21
|
*/
|
22
22
|
createSession = async () => this._fetch('POST', 'v1/webplayer', {
|
@@ -28,14 +28,14 @@ class WebPlayer extends Client {
|
|
28
28
|
})
|
29
29
|
|
30
30
|
/**
|
31
|
-
* Destroy a
|
31
|
+
* Destroy a Webplayer session
|
32
32
|
* @param {string} sessionId
|
33
33
|
* @returns {Promise<{object}>}
|
34
34
|
*/
|
35
35
|
destroySession = async (sessionId) => this._fetch('DELETE', `v1/webplayer/${sessionId}`, {})
|
36
36
|
|
37
37
|
/**
|
38
|
-
* List
|
38
|
+
* List Webplayer sessions
|
39
39
|
* @returns {Promise<Array<object>>}
|
40
40
|
*/
|
41
41
|
listSessions = async (sessionId) => {
|
@@ -44,7 +44,7 @@ class WebPlayer extends Client {
|
|
44
44
|
}
|
45
45
|
|
46
46
|
/**
|
47
|
-
* Log into
|
47
|
+
* Log into Webplayer session by activating Webplayer credentials
|
48
48
|
* @param {string} sessionId
|
49
49
|
* @returns {Promise<void>}
|
50
50
|
*/
|
@@ -54,4 +54,4 @@ class WebPlayer extends Client {
|
|
54
54
|
}
|
55
55
|
}
|
56
56
|
|
57
|
-
module.exports =
|
57
|
+
module.exports = Webplayer
|
@@ -0,0 +1,44 @@
|
|
1
|
+
const log = require('../../../logging')
|
2
|
+
const InstanceCLI = require('../../../clients/Instance')
|
3
|
+
|
4
|
+
async function builder (yargs) {
|
5
|
+
yargs
|
6
|
+
.positional('instanceId', {
|
7
|
+
type: 'string',
|
8
|
+
describe: 'Instance id',
|
9
|
+
demandOption: true
|
10
|
+
})
|
11
|
+
.option('verbose', {
|
12
|
+
alias: 'v',
|
13
|
+
type: 'boolean',
|
14
|
+
describe: 'Console will receive verbose error output',
|
15
|
+
demandOption: false
|
16
|
+
})
|
17
|
+
}
|
18
|
+
|
19
|
+
async function handler (argv) {
|
20
|
+
try {
|
21
|
+
const instance = new InstanceCLI(argv)
|
22
|
+
await instance.disableMonitoring()
|
23
|
+
log.info('Success')
|
24
|
+
} catch (e) {
|
25
|
+
if (e.response) {
|
26
|
+
log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
|
27
|
+
} else {
|
28
|
+
if (e.message) {
|
29
|
+
log.error(e.message)
|
30
|
+
} else {
|
31
|
+
log.error('Urecognized error, use --verbose for details')
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
if (argv.verbose) { log.error(JSON.stringify(e)) }
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
module.exports = {
|
40
|
+
builder,
|
41
|
+
handler,
|
42
|
+
command: 'disable <instanceId> [--verbose]',
|
43
|
+
describe: 'Disable network monitoring'
|
44
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
const log = require('../../../logging')
|
2
|
+
const InstanceCLI = require('../../../clients/Instance')
|
3
|
+
|
4
|
+
async function builder (yargs) {
|
5
|
+
yargs
|
6
|
+
.positional('instanceId', {
|
7
|
+
type: 'string',
|
8
|
+
describe: 'Instance id',
|
9
|
+
demandOption: true
|
10
|
+
})
|
11
|
+
.positional('outputFile', {
|
12
|
+
type: 'string',
|
13
|
+
describe: 'Path to output',
|
14
|
+
demandOption: true
|
15
|
+
})
|
16
|
+
.option('verbose', {
|
17
|
+
alias: 'v',
|
18
|
+
type: 'boolean',
|
19
|
+
describe: 'Console will receive verbose error output',
|
20
|
+
demandOption: false
|
21
|
+
})
|
22
|
+
}
|
23
|
+
|
24
|
+
async function handler (argv) {
|
25
|
+
try {
|
26
|
+
const instance = new InstanceCLI(argv)
|
27
|
+
const { token } = await instance.authorizePcap()
|
28
|
+
await instance.download(token, 'netdump.pcap', argv.outputFile)
|
29
|
+
log.info(`Success, pcap saved to ${argv.outputFile}`)
|
30
|
+
} catch (e) {
|
31
|
+
if (e.response) {
|
32
|
+
log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
|
33
|
+
} else {
|
34
|
+
if (e.message) {
|
35
|
+
log.error(e.message)
|
36
|
+
} else {
|
37
|
+
log.error('Urecognized error, use --verbose for details')
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
if (argv.verbose) { log.error(JSON.stringify(e)) }
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
module.exports = {
|
46
|
+
builder,
|
47
|
+
handler,
|
48
|
+
command: 'download <instanceId> <outputFile> [--verbose]',
|
49
|
+
describe: 'Download pcap file'
|
50
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
const log = require('../../../logging')
|
2
|
+
const InstanceCLI = require('../../../clients/Instance')
|
3
|
+
|
4
|
+
async function builder (yargs) {
|
5
|
+
yargs
|
6
|
+
.positional('instanceId', {
|
7
|
+
type: 'string',
|
8
|
+
describe: 'Instance id',
|
9
|
+
demandOption: true
|
10
|
+
})
|
11
|
+
.option('srcPorts', {
|
12
|
+
type: 'array',
|
13
|
+
describe: 'Source ports to filter'
|
14
|
+
})
|
15
|
+
.option('dstPorts', {
|
16
|
+
type: 'array',
|
17
|
+
describe: 'Destination ports to filter'
|
18
|
+
})
|
19
|
+
.option('ports', {
|
20
|
+
type: 'array',
|
21
|
+
describe: 'Source or destination ports to filter'
|
22
|
+
})
|
23
|
+
.option('processes', {
|
24
|
+
type: 'array',
|
25
|
+
describe: 'List of process names or pids to filter'
|
26
|
+
})
|
27
|
+
.option('portRanges', {
|
28
|
+
type: 'array',
|
29
|
+
describe: 'List of port ranges in that format \'[beginPort]-[endport]\''
|
30
|
+
})
|
31
|
+
.option('protocols', {
|
32
|
+
type: 'array',
|
33
|
+
describe: 'List of protocols'
|
34
|
+
})
|
35
|
+
.option('verbose', {
|
36
|
+
alias: 'v',
|
37
|
+
type: 'boolean',
|
38
|
+
describe: 'Console will receive verbose error output',
|
39
|
+
demandOption: false
|
40
|
+
})
|
41
|
+
}
|
42
|
+
|
43
|
+
async function handler (argv) {
|
44
|
+
try {
|
45
|
+
const { srcPorts, dstPorts, ports, processes, portRanges, protocols } = argv
|
46
|
+
|
47
|
+
const instance = new InstanceCLI(argv)
|
48
|
+
await instance.enableMonitoring({
|
49
|
+
srcPorts, dstPorts, ports, processes, portRanges, protocols
|
50
|
+
})
|
51
|
+
log.info('Success')
|
52
|
+
} catch (e) {
|
53
|
+
if (e.response) {
|
54
|
+
log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
|
55
|
+
} else {
|
56
|
+
if (e.message) {
|
57
|
+
log.error(e.message)
|
58
|
+
} else {
|
59
|
+
log.error('Urecognized error, use --verbose for details')
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
if (argv.verbose) { log.error(JSON.stringify(e)) }
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
module.exports = {
|
68
|
+
builder,
|
69
|
+
handler,
|
70
|
+
command: 'enable <instanceId> [--verbose]',
|
71
|
+
describe: 'Enable network monitoring'
|
72
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
const subcommands = [
|
2
|
+
require('./enable'),
|
3
|
+
require('./disable'),
|
4
|
+
require('./download'),
|
5
|
+
require('./stream')
|
6
|
+
]
|
7
|
+
|
8
|
+
let _yargs
|
9
|
+
|
10
|
+
module.exports = {
|
11
|
+
builder: yargs => (_yargs = yargs) && yargs.command(subcommands).scriptName(''),
|
12
|
+
handler: argv => !argv[1] && _yargs.showHelp(),
|
13
|
+
command: 'netmon',
|
14
|
+
describe: 'Network Monitoring related commands'
|
15
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
const log = require('../../../logging')
|
2
|
+
const InstanceCLI = require('../../../clients/Instance')
|
3
|
+
|
4
|
+
async function builder (yargs) {
|
5
|
+
yargs
|
6
|
+
.positional('instanceId', {
|
7
|
+
type: 'string',
|
8
|
+
describe: 'Instance id',
|
9
|
+
demandOption: true
|
10
|
+
})
|
11
|
+
.option('verbose', {
|
12
|
+
alias: 'v',
|
13
|
+
type: 'boolean',
|
14
|
+
describe: 'Console will receive verbose error output',
|
15
|
+
demandOption: false
|
16
|
+
})
|
17
|
+
}
|
18
|
+
|
19
|
+
async function handler (argv) {
|
20
|
+
try {
|
21
|
+
const instance = new InstanceCLI(argv)
|
22
|
+
const { netdump } = await instance.info()
|
23
|
+
if (netdump && netdump.enabled) {
|
24
|
+
instance.wss(netdump.info).pipe(process.stdout)
|
25
|
+
} else {
|
26
|
+
log.error('Network monitoring is not enabled')
|
27
|
+
}
|
28
|
+
} catch (e) {
|
29
|
+
if (e.response) {
|
30
|
+
log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
|
31
|
+
} else {
|
32
|
+
if (e.message) {
|
33
|
+
log.error(e.message)
|
34
|
+
} else {
|
35
|
+
log.error('Urecognized error, use --verbose for details')
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
if (argv.verbose) { log.error(JSON.stringify(e)) }
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
module.exports = {
|
44
|
+
builder,
|
45
|
+
handler,
|
46
|
+
command: 'stream <instanceId> [--verbose]',
|
47
|
+
describe: 'Stream monitoring events'
|
48
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
const log = require('../../logging')
|
2
|
+
const InstanceCLI = require('../../clients/Instance')
|
3
|
+
const { readProfile } = require('../../profile')
|
4
|
+
const { v4: uuidv4 } = require('uuid')
|
5
|
+
const { basename } = require('path')
|
6
|
+
const MultiProgress = require('multi-progress')
|
7
|
+
const { uploadWithProgress } = require('../../upload')
|
8
|
+
const { waitForTaskState } = require('../../utils')
|
9
|
+
|
10
|
+
async function builder (yargs) {
|
11
|
+
yargs
|
12
|
+
.positional('instanceId', {
|
13
|
+
type: 'string',
|
14
|
+
describe: 'Instance id',
|
15
|
+
demandOption: true
|
16
|
+
})
|
17
|
+
.positional('zipFile', {
|
18
|
+
type: 'string',
|
19
|
+
describe: 'Path to backup zip file',
|
20
|
+
demandOption: true
|
21
|
+
})
|
22
|
+
.option('verbose', {
|
23
|
+
alias: 'v',
|
24
|
+
type: 'boolean',
|
25
|
+
describe: 'Console will receive verbose error output',
|
26
|
+
demandOption: false
|
27
|
+
})
|
28
|
+
}
|
29
|
+
|
30
|
+
async function handler (argv) {
|
31
|
+
const profile = readProfile()
|
32
|
+
const { instanceId, zipFile } = argv
|
33
|
+
const name = basename(zipFile)
|
34
|
+
const image = uuidv4()
|
35
|
+
const url = profile.endpoint +
|
36
|
+
'/api/v1/instances' +
|
37
|
+
'/' +
|
38
|
+
encodeURIComponent(instanceId) +
|
39
|
+
'/' +
|
40
|
+
'image-upload/backup' +
|
41
|
+
'/' +
|
42
|
+
encodeURIComponent(image) +
|
43
|
+
'/' +
|
44
|
+
encodeURIComponent(name)
|
45
|
+
|
46
|
+
const multi = new MultiProgress(process.stderr)
|
47
|
+
|
48
|
+
try {
|
49
|
+
await uploadWithProgress(url, profile.token, zipFile, multi, {
|
50
|
+
'x-corellium-image-encapsulated': 'false'
|
51
|
+
})
|
52
|
+
console.log(`Backup uploaded ${image}`)
|
53
|
+
|
54
|
+
const instance = new InstanceCLI(argv)
|
55
|
+
await instance.restoreBackup()
|
56
|
+
|
57
|
+
await waitForTaskState(instanceId, instance.info.bind(instance), 'restoring-backup')
|
58
|
+
console.log('Restoring in progress')
|
59
|
+
await waitForTaskState(instanceId, instance.info.bind(instance), 'none')
|
60
|
+
|
61
|
+
log.info(`Restored backup ${zipFile} to ${argv.instanceId}`)
|
62
|
+
} catch (err) {
|
63
|
+
const errorMessage = err.message
|
64
|
+
log.error(`Restore backup failed: ${errorMessage}`)
|
65
|
+
if (err.response && err.response.data && err.response.data.error) {
|
66
|
+
log.error(err.response.data.error)
|
67
|
+
}
|
68
|
+
if (argv.verbose) { log.error(err) }
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
module.exports = {
|
73
|
+
builder,
|
74
|
+
handler,
|
75
|
+
command: 'restore-backup <instanceId> <zipFile> [--verbose]',
|
76
|
+
describe: 'Restore iOS backup to instance'
|
77
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
const { getErrorMessage } = require('../../error')
|
2
|
+
const asciiArt = require('ascii-art')
|
3
|
+
|
4
|
+
const Client = require('../../clients/Client')
|
5
|
+
async function builder (yargs) {
|
6
|
+
yargs.positional('instanceId', {
|
7
|
+
type: 'string',
|
8
|
+
describe: 'Instance id',
|
9
|
+
demandOption: true
|
10
|
+
})
|
11
|
+
.option('verbose', {
|
12
|
+
alias: 'v',
|
13
|
+
type: 'boolean',
|
14
|
+
describe: 'Console will receive verbose error output'
|
15
|
+
})
|
16
|
+
.option('orientation', {
|
17
|
+
alias: 'o',
|
18
|
+
type: 'number',
|
19
|
+
describe: 'Orientation type'
|
20
|
+
})
|
21
|
+
}
|
22
|
+
|
23
|
+
async function handler (argv) {
|
24
|
+
const client = new Client(argv)
|
25
|
+
const instanceId = argv.instanceId
|
26
|
+
const orientation = argv.orientation || 4
|
27
|
+
try {
|
28
|
+
await client._fetch('POST', `v1/instances/${instanceId}/rotate`, { orientation })
|
29
|
+
console.log(
|
30
|
+
asciiArt.style(`Successfully rotated instance ${instanceId}`, 'green', true)
|
31
|
+
)
|
32
|
+
} catch (error) {
|
33
|
+
const errorMessage = getErrorMessage(error)
|
34
|
+
console.error(`Failed to rotate ${instanceId}: ${errorMessage}`)
|
35
|
+
|
36
|
+
if (argv.detailedErrors) {
|
37
|
+
console.log(error)
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
module.exports = {
|
43
|
+
builder,
|
44
|
+
handler,
|
45
|
+
command: 'rotate <instanceId> [orientation]',
|
46
|
+
describe: 'Rotate instance'
|
47
|
+
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
const { getErrorMessage } = require('../../error')
|
2
2
|
const asciiArt = require('ascii-art')
|
3
|
-
const {
|
3
|
+
const { waitForState } = require('../../utils')
|
4
|
+
const InstanceCLI = require('../../clients/Instance')
|
4
5
|
|
5
6
|
async function builder (yargs) {
|
6
7
|
yargs.positional('instanceId', {
|
@@ -17,25 +18,33 @@ async function builder (yargs) {
|
|
17
18
|
type: 'boolean',
|
18
19
|
describe: 'Wait for the instance to be fully started'
|
19
20
|
})
|
21
|
+
.option('paused', {
|
22
|
+
type: 'boolean',
|
23
|
+
describe: 'Start instance in a paused state'
|
24
|
+
})
|
25
|
+
.option('sockcap', {
|
26
|
+
type: 'boolean',
|
27
|
+
describe: 'Start sockcap add-on if loaded'
|
28
|
+
})
|
20
29
|
}
|
21
30
|
|
22
31
|
async function handler (argv) {
|
23
|
-
const
|
24
|
-
const
|
32
|
+
const { sockcap, paused } = argv
|
33
|
+
const instance = new InstanceCLI(argv)
|
25
34
|
try {
|
26
|
-
await
|
35
|
+
await instance.start({ sockcap, paused })
|
27
36
|
if (argv.wait) {
|
28
37
|
// Wait 60 attempts for the instance to be created (5 minutes)
|
29
|
-
await waitForState(instanceId,
|
38
|
+
await waitForState(argv.instanceId, instance.info.bind(instance), 'on', 60)
|
30
39
|
}
|
31
40
|
console.log(
|
32
|
-
asciiArt.style(`Successfully started instance ${instanceId}`, 'green', true)
|
41
|
+
asciiArt.style(`Successfully started instance ${argv.instanceId}`, 'green', true)
|
33
42
|
)
|
34
43
|
} catch (error) {
|
35
44
|
const errorMessage = getErrorMessage(error)
|
36
|
-
console.error(`Failed to start ${
|
45
|
+
console.error(`Failed to start ${argv.instance}: ${errorMessage}`)
|
37
46
|
|
38
|
-
if (argv.
|
47
|
+
if (argv.verbose) {
|
39
48
|
console.log(error)
|
40
49
|
}
|
41
50
|
}
|
@@ -44,6 +53,6 @@ async function handler (argv) {
|
|
44
53
|
module.exports = {
|
45
54
|
builder,
|
46
55
|
handler,
|
47
|
-
command: 'start <instanceId> [wait]',
|
56
|
+
command: 'start <instanceId> [--wait] [--verbose] [--paused] [--sockcap]',
|
48
57
|
describe: 'Start instance'
|
49
58
|
}
|
package/src/commands/signup.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
const TrialAccount = require('../TrialAccount')
|
1
|
+
const TrialAccount = require('../clients/TrialAccount')
|
2
2
|
const { makePassword, validateNonEmpty } = require('../utils')
|
3
3
|
const { getErrorMessage } = require('../error')
|
4
4
|
const log = require('../logging')
|
@@ -7,7 +7,7 @@ async function builder (yargs) {
|
|
7
7
|
yargs.options({
|
8
8
|
endpoint: {
|
9
9
|
type: 'input',
|
10
|
-
describe: 'Server endpoint e.g. https://app.
|
10
|
+
describe: 'Server endpoint e.g. https://app.corellium.co',
|
11
11
|
demandOption: true,
|
12
12
|
string: true,
|
13
13
|
validateNonEmpty
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const
|
1
|
+
const Webplayer = require('../../clients/Webplayer')
|
2
2
|
const log = require('../../logging')
|
3
3
|
const { displayTable } = require('../../table')
|
4
4
|
const { validateNonEmpty } = require('../../utils')
|
@@ -49,13 +49,13 @@ async function builder (yargs) {
|
|
49
49
|
|
50
50
|
async function handler (argv) {
|
51
51
|
try {
|
52
|
-
const webplayer = new
|
52
|
+
const webplayer = new Webplayer(argv)
|
53
53
|
const session = await webplayer.createSession()
|
54
|
-
// log.info('Created
|
54
|
+
// log.info('Created Webplayer session')
|
55
55
|
await displayTable(argv.format || 'json', [session], ['identifier', 'token', 'expiration', 'features'])
|
56
56
|
} catch (err) {
|
57
57
|
const errorMessage = getErrorMessage(err)
|
58
|
-
log.error(`Create
|
58
|
+
log.error(`Create Webplayer session failed: ${errorMessage}`)
|
59
59
|
if (argv.detailedErrors) { log.error(err) }
|
60
60
|
}
|
61
61
|
}
|
@@ -64,5 +64,5 @@ module.exports = {
|
|
64
64
|
builder,
|
65
65
|
handler,
|
66
66
|
command: 'create',
|
67
|
-
describe: 'Create a
|
67
|
+
describe: 'Create a Webplayer session returning the URL and token'
|
68
68
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const
|
1
|
+
const Webplayer = require('../../clients/Webplayer')
|
2
2
|
const log = require('../../logging')
|
3
3
|
const { validateNonEmpty } = require('../../utils')
|
4
4
|
const { getErrorMessage } = require('../../error')
|
@@ -16,7 +16,7 @@ async function builder (yargs) {
|
|
16
16
|
validateNonEmpty
|
17
17
|
}).option('session', {
|
18
18
|
type: 'input',
|
19
|
-
describe: '
|
19
|
+
describe: 'Webplayer Session ID',
|
20
20
|
demandOption: true,
|
21
21
|
string: true,
|
22
22
|
validateNonEmpty
|
@@ -26,12 +26,12 @@ async function builder (yargs) {
|
|
26
26
|
async function handler (argv) {
|
27
27
|
const { session: sessionId } = argv
|
28
28
|
try {
|
29
|
-
const webplayer = new
|
29
|
+
const webplayer = new Webplayer(argv)
|
30
30
|
await webplayer.destroySession(sessionId)
|
31
|
-
log.info(`Destroyed
|
31
|
+
log.info(`Destroyed Webplayer session ${sessionId}`)
|
32
32
|
} catch (err) {
|
33
33
|
const errorMessage = getErrorMessage(err)
|
34
|
-
log.error(`Destroy
|
34
|
+
log.error(`Destroy Webplayer session ${sessionId} failed: ${errorMessage}`)
|
35
35
|
if (argv.detailedErrors) { log.error(err) }
|
36
36
|
}
|
37
37
|
}
|
@@ -40,5 +40,5 @@ module.exports = {
|
|
40
40
|
builder,
|
41
41
|
handler,
|
42
42
|
command: 'destroy',
|
43
|
-
describe: 'Destroy a
|
43
|
+
describe: 'Destroy a Webplayer session'
|
44
44
|
}
|
@@ -11,5 +11,5 @@ module.exports = {
|
|
11
11
|
builder: yargs => (_yargs = yargs) && yargs.command(subcommands).scriptName(''),
|
12
12
|
handler: argv => !argv[1] && _yargs.showHelp(),
|
13
13
|
command: 'webplayer',
|
14
|
-
describe: '
|
14
|
+
describe: 'Webplayer related commands'
|
15
15
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const
|
1
|
+
const Webplayer = require('../../clients/Webplayer')
|
2
2
|
const log = require('../../logging')
|
3
3
|
const { displayTable } = require('../../table')
|
4
4
|
const { validateNonEmpty } = require('../../utils')
|
@@ -17,7 +17,7 @@ async function builder (yargs) {
|
|
17
17
|
validateNonEmpty
|
18
18
|
}).option('session', {
|
19
19
|
type: 'input',
|
20
|
-
describe: '
|
20
|
+
describe: 'Webplayer Session ID',
|
21
21
|
demandOption: false,
|
22
22
|
string: true
|
23
23
|
}).option('format', {
|
@@ -30,7 +30,7 @@ async function builder (yargs) {
|
|
30
30
|
|
31
31
|
async function handler (argv) {
|
32
32
|
try {
|
33
|
-
const webplayer = new
|
33
|
+
const webplayer = new Webplayer(argv)
|
34
34
|
const result = await webplayer.listSessions(argv.session)
|
35
35
|
if (!result.length && argv.format !== 'json') {
|
36
36
|
console.error('Found no matching Webplayer sessions')
|
@@ -63,5 +63,5 @@ module.exports = {
|
|
63
63
|
builder,
|
64
64
|
handler,
|
65
65
|
command: 'list',
|
66
|
-
describe: 'List
|
66
|
+
describe: 'List Webplayer sessions for a project'
|
67
67
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
const
|
1
|
+
const Webplayer = require('../../clients/Webplayer')
|
2
2
|
const log = require('../../logging')
|
3
3
|
const { validateNonEmpty } = require('../../utils')
|
4
4
|
const { getErrorMessage } = require('../../error')
|
@@ -16,7 +16,7 @@ async function builder (yargs) {
|
|
16
16
|
validateNonEmpty
|
17
17
|
}).option('session', {
|
18
18
|
type: 'input',
|
19
|
-
describe: '
|
19
|
+
describe: 'Webplayer Session ID',
|
20
20
|
demandOption: true,
|
21
21
|
string: true,
|
22
22
|
validateNonEmpty
|
@@ -26,12 +26,12 @@ async function builder (yargs) {
|
|
26
26
|
async function handler (argv) {
|
27
27
|
const { session: sessionId } = argv
|
28
28
|
try {
|
29
|
-
const webplayer = new
|
29
|
+
const webplayer = new Webplayer(argv)
|
30
30
|
await webplayer.login(sessionId)
|
31
|
-
log.info(`Login to
|
31
|
+
log.info(`Login to Webplayer session ${sessionId} successful`)
|
32
32
|
} catch (err) {
|
33
33
|
const errorMessage = getErrorMessage(err)
|
34
|
-
log.error(`Login to
|
34
|
+
log.error(`Login to Webplayer session ${sessionId} failed: ${errorMessage}`)
|
35
35
|
if (argv.detailedErrors) {
|
36
36
|
log.error(err)
|
37
37
|
}
|
@@ -42,5 +42,5 @@ module.exports = {
|
|
42
42
|
builder,
|
43
43
|
handler,
|
44
44
|
command: 'login',
|
45
|
-
describe: 'Log CLI into a
|
45
|
+
describe: 'Log CLI into a Webplayer session'
|
46
46
|
}
|
package/src/upload.js
CHANGED
@@ -19,13 +19,14 @@ class File {
|
|
19
19
|
}
|
20
20
|
}
|
21
21
|
|
22
|
-
async function uploadFile (token, url, filePath, progress) {
|
22
|
+
async function uploadFile (token, url, filePath, progress, headers) {
|
23
23
|
return new Promise((resolve, reject) => {
|
24
24
|
const r = new Resumable({
|
25
25
|
target: url,
|
26
26
|
headers: {
|
27
27
|
authorization: token,
|
28
|
-
'x-corellium-image-encoding': 'plain'
|
28
|
+
'x-corellium-image-encoding': 'plain',
|
29
|
+
...headers
|
29
30
|
},
|
30
31
|
uploadMethod: 'PUT',
|
31
32
|
chunkSize: 5 * 1024 * 1024,
|
@@ -64,7 +65,7 @@ async function uploadFile (token, url, filePath, progress) {
|
|
64
65
|
})
|
65
66
|
}
|
66
67
|
|
67
|
-
async function uploadWithProgress (url, token, file, multi) {
|
68
|
+
async function uploadWithProgress (url, token, file, multi, headers) {
|
68
69
|
const fileName = basename(file)
|
69
70
|
|
70
71
|
const bar = multi.newBar(` loading ${fileName} [:bar] :percent`, {
|
@@ -82,7 +83,8 @@ async function uploadWithProgress (url, token, file, multi) {
|
|
82
83
|
(progress) => {
|
83
84
|
const ticks = Math.ceil(100 * progress)
|
84
85
|
bar.tick(ticks)
|
85
|
-
}
|
86
|
+
},
|
87
|
+
headers
|
86
88
|
)
|
87
89
|
return res
|
88
90
|
} catch (error) {
|
package/src/utils.js
CHANGED
@@ -63,6 +63,23 @@ const waitForState = async (instanceId, fn, state, attempts = 20, reporterFn = n
|
|
63
63
|
}
|
64
64
|
}
|
65
65
|
|
66
|
+
const waitForTaskState = async (instanceId, fn, taskState, attempts = 20) => {
|
67
|
+
let response = await fn(instanceId)
|
68
|
+
|
69
|
+
while (response.taskState !== taskState) {
|
70
|
+
if (response.state === 'error') {
|
71
|
+
throw new Error(response.error)
|
72
|
+
}
|
73
|
+
|
74
|
+
if (attempts <= 0) {
|
75
|
+
throw new Error(`Timed out waiting for taskState ${taskState}`)
|
76
|
+
}
|
77
|
+
--attempts
|
78
|
+
await sleep(waitForIntervalMilliSec)
|
79
|
+
response = await fn(instanceId)
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
66
83
|
async function getApi () {
|
67
84
|
const profile = readProfile()
|
68
85
|
const api = new CorelliumClient.CorelliumApi()
|
@@ -145,6 +162,7 @@ module.exports = {
|
|
145
162
|
planOptions,
|
146
163
|
makePassword,
|
147
164
|
waitForState,
|
165
|
+
waitForTaskState,
|
148
166
|
getApi,
|
149
167
|
getAxios,
|
150
168
|
validateNonEmpty,
|
package/src/commands/Client.js
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
const axios = require('axios').default
|
2
|
-
const { readProfile } = require('../profile')
|
3
|
-
|
4
|
-
/**
|
5
|
-
* Client Base class
|
6
|
-
*/
|
7
|
-
class Client {
|
8
|
-
constructor (argv) {
|
9
|
-
this.argv = Object.assign({}, argv)
|
10
|
-
this.project = argv.project || argv.projectId
|
11
|
-
this.instance = argv.instance || argv.instanceId
|
12
|
-
this._profile = readProfile()
|
13
|
-
}
|
14
|
-
|
15
|
-
/**
|
16
|
-
* Perform HTTP transaction
|
17
|
-
* @protected
|
18
|
-
* @param {string} method
|
19
|
-
* @param {string} path
|
20
|
-
* @param {object} data
|
21
|
-
* @returns {Promise<any>}
|
22
|
-
*/
|
23
|
-
_fetch = async (method, path, data) => {
|
24
|
-
const headers = {
|
25
|
-
Authorization: `Bearer ${this._profile.token}`,
|
26
|
-
'User-Agent': `corellium-cli v${require('../../package.json').version}`
|
27
|
-
}
|
28
|
-
let response
|
29
|
-
switch (method) {
|
30
|
-
case 'GET':
|
31
|
-
response = await axios.get(`${this._profile.endpoint}/api/${path}`, { headers })
|
32
|
-
break
|
33
|
-
case 'POST':
|
34
|
-
response = await axios.post(`${this._profile.endpoint}/api/${path}`, data, { headers })
|
35
|
-
break
|
36
|
-
case 'DELETE':
|
37
|
-
response = await axios.delete(`${this._profile.endpoint}/api/${path}`, { headers })
|
38
|
-
break
|
39
|
-
}
|
40
|
-
if (response.status >= 400) { throw new Error(response.statusText) }
|
41
|
-
return response.data || {}
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
|
-
module.exports = Client
|
@@ -1,27 +0,0 @@
|
|
1
|
-
const Client = require('../Client')
|
2
|
-
|
3
|
-
/**
|
4
|
-
* Instance class
|
5
|
-
*/
|
6
|
-
class InstanceCLI extends Client {
|
7
|
-
instanceBasePath = () => `v1/instances/${this.instance}`
|
8
|
-
|
9
|
-
/**
|
10
|
-
* Upgrade iOS version on instance
|
11
|
-
* @returns {Promise<any>}
|
12
|
-
*/
|
13
|
-
upgrade = async (options) => {
|
14
|
-
const { os, osbuild } = options
|
15
|
-
return await this._fetch('POST', `${this.instanceBasePath()}/upgrade`, { os, osbuild })
|
16
|
-
}
|
17
|
-
|
18
|
-
/**
|
19
|
-
* Fetch instance information
|
20
|
-
* @returns {Promise<any>}
|
21
|
-
*/
|
22
|
-
info = async () => {
|
23
|
-
return await this._fetch('GET', `${this.instanceBasePath()}`, {})
|
24
|
-
}
|
25
|
-
}
|
26
|
-
|
27
|
-
module.exports = InstanceCLI
|