@corellium/corellium-cli 1.0.9 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|