@corellium/corellium-cli 1.1.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corellium/corellium-cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Corellium CLI Tool",
5
5
  "scripts": {
6
6
  "corellium": "node index.js",
@@ -33,6 +33,7 @@
33
33
  "progress": "^2.0.3",
34
34
  "prompts": "^2.4.2",
35
35
  "uuid": "^8.3.2",
36
+ "websocket-stream": "^5.5.2",
36
37
  "xhr2": "^0.2.0",
37
38
  "yargs": "^17.5.1"
38
39
  },
@@ -1,4 +1,4 @@
1
- const Client = require('../Client')
1
+ const Client = require('./Client')
2
2
 
3
3
  /**
4
4
  * @typedef {object} AppsResult
@@ -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('./commands/Client')
2
- const util = require('./utils')
3
- const { handler: login } = require('./commands/login')
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,7 +1,7 @@
1
- const Client = require('../Client')
1
+ const Client = require('./Client')
2
2
  const {
3
3
  updateProfile
4
- } = require('../../profile')
4
+ } = require('../profile')
5
5
 
6
6
  /**
7
7
  * Webplayer class
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { displayTable } = require('../../table')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { displayTable } = require('../../table')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { validateNonEmpty } = require('../../utils')
3
3
  const { getErrorMessage } = require('../../error')
4
4
  const log = require('../../logging')
@@ -5,6 +5,7 @@ const subcommands = [
5
5
  require('./stop'),
6
6
  require('./start'),
7
7
  require('./upgrade'),
8
+ require('./netmon'),
8
9
  require('./restoreBackup')
9
10
  ]
10
11
 
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  const log = require('../../logging')
2
- const InstanceCLI = require('./Instance')
2
+ const InstanceCLI = require('../../clients/Instance')
3
3
  const { readProfile } = require('../../profile')
4
4
  const { v4: uuidv4 } = require('uuid')
5
5
  const { basename } = require('path')
@@ -1,7 +1,7 @@
1
1
  const { getErrorMessage } = require('../../error')
2
2
  const asciiArt = require('ascii-art')
3
3
 
4
- const Client = require('../Client')
4
+ const Client = require('../../clients/Client')
5
5
  async function builder (yargs) {
6
6
  yargs.positional('instanceId', {
7
7
  type: 'string',
@@ -1,6 +1,7 @@
1
1
  const { getErrorMessage } = require('../../error')
2
2
  const asciiArt = require('ascii-art')
3
- const { getApi, waitForState } = require('../../utils')
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 api = await getApi()
24
- const instanceId = argv.instanceId
32
+ const { sockcap, paused } = argv
33
+ const instance = new InstanceCLI(argv)
25
34
  try {
26
- await api.v1StartInstance(instanceId)
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, api.v1GetInstance.bind(api), 'on', 60)
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 ${instanceId}: ${errorMessage}`)
45
+ console.error(`Failed to start ${argv.instance}: ${errorMessage}`)
37
46
 
38
- if (argv.detailedErrors) {
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
  }
@@ -1,5 +1,5 @@
1
1
  const log = require('../../logging')
2
- const InstanceCLI = require('./Instance')
2
+ const InstanceCLI = require('../../clients/Instance')
3
3
  const { getErrorMessage } = require('../../error')
4
4
  const { waitForState, waitForIntervalMilliSec } = require('../../utils')
5
5
 
@@ -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.ci-4.corellium.co',
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 Webplayer = require('./Webplayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { displayTable } = require('../../table')
4
4
  const { validateNonEmpty } = require('../../utils')
@@ -1,4 +1,4 @@
1
- const Webplayer = require('./Webplayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,4 +1,4 @@
1
- const Webplayer = require('./Webplayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { displayTable } = require('../../table')
4
4
  const { validateNonEmpty } = require('../../utils')
@@ -1,4 +1,4 @@
1
- const Webplayer = require('./Webplayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -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,35 +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
- * Restore backup to instance
20
- * @returns {Promise<any>}
21
- */
22
- restoreBackup = async () => {
23
- return await this._fetch('POST', `${this.instanceBasePath()}/restoreBackup`)
24
- }
25
-
26
- /**
27
- * Fetch instance information
28
- * @returns {Promise<any>}
29
- */
30
- info = async () => {
31
- return await this._fetch('GET', `${this.instanceBasePath()}`, {})
32
- }
33
- }
34
-
35
- module.exports = InstanceCLI