@alwaysai/device-agent 1.3.1 → 1.4.0

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.
Files changed (91) hide show
  1. package/lib/application-control/environment-variables.d.ts.map +1 -1
  2. package/lib/application-control/environment-variables.js +9 -4
  3. package/lib/application-control/environment-variables.js.map +1 -1
  4. package/lib/application-control/environment-variables.test.js +1 -1
  5. package/lib/application-control/environment-variables.test.js.map +1 -1
  6. package/lib/application-control/install.d.ts.map +1 -1
  7. package/lib/application-control/install.js +6 -2
  8. package/lib/application-control/install.js.map +1 -1
  9. package/lib/application-control/models.d.ts.map +1 -1
  10. package/lib/application-control/models.js +4 -2
  11. package/lib/application-control/models.js.map +1 -1
  12. package/lib/application-control/status.js +4 -5
  13. package/lib/application-control/status.js.map +1 -1
  14. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +3 -3
  15. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  16. package/lib/cloud-connection/device-agent-cloud-connection.js +114 -99
  17. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  18. package/lib/cloud-connection/live-updates-handler.d.ts +1 -0
  19. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  20. package/lib/cloud-connection/live-updates-handler.js +22 -4
  21. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  22. package/lib/cloud-connection/messages.d.ts.map +1 -1
  23. package/lib/cloud-connection/messages.js +3 -4
  24. package/lib/cloud-connection/messages.js.map +1 -1
  25. package/lib/cloud-connection/shadow-handler.d.ts +14 -21
  26. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  27. package/lib/cloud-connection/shadow-handler.js +162 -108
  28. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  29. package/lib/cloud-connection/shadow-handler.test.js +100 -83
  30. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  31. package/lib/device-control/device-control.d.ts +7 -14
  32. package/lib/device-control/device-control.d.ts.map +1 -1
  33. package/lib/device-control/device-control.js +37 -14
  34. package/lib/device-control/device-control.js.map +1 -1
  35. package/lib/secure-tunneling/secure-tunneling.d.ts +105 -0
  36. package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
  37. package/lib/secure-tunneling/secure-tunneling.js +435 -0
  38. package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
  39. package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
  40. package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
  41. package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
  42. package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
  43. package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
  44. package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
  45. package/lib/secure-tunneling/spawner-detached.js +107 -0
  46. package/lib/secure-tunneling/spawner-detached.js.map +1 -0
  47. package/lib/subcommands/app/analytics.d.ts.map +1 -1
  48. package/lib/subcommands/app/analytics.js +9 -13
  49. package/lib/subcommands/app/analytics.js.map +1 -1
  50. package/lib/subcommands/app/env-vars.d.ts.map +1 -1
  51. package/lib/subcommands/app/env-vars.js +11 -16
  52. package/lib/subcommands/app/env-vars.js.map +1 -1
  53. package/lib/subcommands/app/models.d.ts.map +1 -1
  54. package/lib/subcommands/app/models.js +12 -16
  55. package/lib/subcommands/app/models.js.map +1 -1
  56. package/lib/subcommands/device/clean.d.ts.map +1 -1
  57. package/lib/subcommands/device/clean.js +3 -1
  58. package/lib/subcommands/device/clean.js.map +1 -1
  59. package/lib/subcommands/device/device.d.ts.map +1 -1
  60. package/lib/subcommands/device/device.js +14 -6
  61. package/lib/subcommands/device/device.js.map +1 -1
  62. package/lib/util/cloud-mode-ready.d.ts +1 -0
  63. package/lib/util/cloud-mode-ready.d.ts.map +1 -1
  64. package/lib/util/cloud-mode-ready.js +36 -1
  65. package/lib/util/cloud-mode-ready.js.map +1 -1
  66. package/package.json +2 -2
  67. package/src/application-control/environment-variables.test.ts +1 -1
  68. package/src/application-control/environment-variables.ts +9 -6
  69. package/src/application-control/install.ts +7 -3
  70. package/src/application-control/models.ts +11 -6
  71. package/src/application-control/status.ts +8 -8
  72. package/src/cloud-connection/device-agent-cloud-connection.ts +161 -131
  73. package/src/cloud-connection/live-updates-handler.ts +34 -6
  74. package/src/cloud-connection/messages.ts +3 -4
  75. package/src/cloud-connection/shadow-handler.test.ts +101 -84
  76. package/src/cloud-connection/shadow-handler.ts +275 -133
  77. package/src/device-control/device-control.ts +46 -19
  78. package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
  79. package/src/secure-tunneling/secure-tunneling.ts +606 -0
  80. package/src/secure-tunneling/spawner-detached.ts +123 -0
  81. package/src/subcommands/app/analytics.ts +16 -13
  82. package/src/subcommands/app/env-vars.ts +18 -16
  83. package/src/subcommands/app/models.ts +20 -16
  84. package/src/subcommands/device/clean.ts +4 -1
  85. package/src/subcommands/device/device.ts +26 -10
  86. package/src/util/cloud-mode-ready.ts +36 -0
  87. package/lib/secure-tunneling/index.d.ts +0 -5
  88. package/lib/secure-tunneling/index.d.ts.map +0 -1
  89. package/lib/secure-tunneling/index.js +0 -64
  90. package/lib/secure-tunneling/index.js.map +0 -1
  91. package/src/secure-tunneling/index.ts +0 -74
@@ -0,0 +1,1070 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const paths_1 = require("alwaysai/lib/paths");
4
+ const path_1 = require("path");
5
+ const urls_1 = require("../urls");
6
+ const directories_1 = require("../util/directories");
7
+ const download_file_1 = require("../util/download-file");
8
+ const system_info_1 = require("../util/system-info");
9
+ const secure_tunneling_1 = require("./secure-tunneling");
10
+ const spawner_detached_1 = require("./spawner-detached");
11
+ //-----------------------------------------------------------------------------
12
+ // mocks
13
+ //-----------------------------------------------------------------------------
14
+ jest.mock('alwaysai/lib/util/spawner');
15
+ const mockJsSpawner = {
16
+ exiresolvePathsts: jest.fn(),
17
+ readdir: jest.fn(),
18
+ readFile: jest.fn(),
19
+ writeFile: jest.fn(),
20
+ mkdirp: jest.fn(),
21
+ rimraf: jest.fn(),
22
+ tar: jest.fn(),
23
+ rename: jest.fn(),
24
+ untar: jest.fn(),
25
+ exists: jest.fn(),
26
+ run: jest.fn()
27
+ };
28
+ jest.mock('alwaysai/lib/util/spawner', () => {
29
+ return Object.assign(Object.assign({}, jest.requireActual('alwaysai/lib/util/spawner')), { JsSpawner: jest.fn(() => mockJsSpawner) });
30
+ });
31
+ jest.mock('../util/system-info', () => ({
32
+ getArch: jest.fn(),
33
+ getOsVersion: jest.fn(),
34
+ getDistribution: jest.fn()
35
+ }));
36
+ jest.mock('../util/download-file', () => ({
37
+ downloadFile: jest.fn()
38
+ }));
39
+ jest.mock('./spawner-detached', () => ({
40
+ runDetachedProcess: jest.fn(),
41
+ killDetachedProcess: jest.fn()
42
+ }));
43
+ //-----------------------------------------------------------------------------
44
+ // constants
45
+ //-----------------------------------------------------------------------------
46
+ const ST_START_PORT_NUMBER = 5010;
47
+ const disabledSshPortInfo = {
48
+ enabled: false,
49
+ type: 'SSH',
50
+ ip: '0.0.0.0',
51
+ port: 22
52
+ };
53
+ const enabledSshPortInfo = {
54
+ enabled: true,
55
+ type: 'SSH',
56
+ ip: '0.0.0.0',
57
+ port: 22
58
+ };
59
+ const invalidSshPortInfo = {
60
+ enabled: true,
61
+ type: 'SSH',
62
+ ip: '192.168.0.255',
63
+ port: 80
64
+ };
65
+ const disabledHttpPortInfo_1 = {
66
+ enabled: false,
67
+ type: 'HTTP',
68
+ ip: '192.168.0.10',
69
+ port: 1
70
+ };
71
+ const enabledHttpPortInfo_1 = {
72
+ enabled: true,
73
+ type: 'HTTP',
74
+ ip: '192.168.0.10',
75
+ port: 1
76
+ };
77
+ const disabledHttpPortInfo_2 = {
78
+ enabled: false,
79
+ type: 'HTTP',
80
+ ip: '192.168.0.20',
81
+ port: 2
82
+ };
83
+ const enabledHttpPortInfo_2 = {
84
+ enabled: true,
85
+ type: 'HTTP',
86
+ ip: '192.168.0.20',
87
+ port: 2
88
+ };
89
+ const disabledHttpPortInfo_3 = {
90
+ enabled: false,
91
+ type: 'HTTP',
92
+ ip: '192.168.0.30',
93
+ port: 3
94
+ };
95
+ const enabledHttpPortInfo_3 = {
96
+ enabled: true,
97
+ type: 'HTTP',
98
+ ip: '192.168.0.30',
99
+ port: 3
100
+ };
101
+ describe('SecureTunnelHandlerSingleton', () => {
102
+ //---------------------------------------------------------------------------
103
+ // test variables
104
+ //---------------------------------------------------------------------------
105
+ let testStHandlerSingleton;
106
+ let shadowVersion = 0;
107
+ let shadowTimestamp = 1708726929;
108
+ const testDefaultShadow = {
109
+ st_ports: [disabledSshPortInfo]
110
+ };
111
+ beforeEach(() => {
112
+ jest.clearAllMocks();
113
+ jest.resetModules();
114
+ testStHandlerSingleton = secure_tunneling_1.SecureTunnelHandlerSingleton.getInstance();
115
+ shadowVersion++;
116
+ shadowTimestamp++;
117
+ });
118
+ afterEach(async () => {
119
+ await testStHandlerSingleton.destroy();
120
+ });
121
+ //---------------------------------------------------------------------------
122
+ // help functions
123
+ //---------------------------------------------------------------------------
124
+ function createDeltaShadowMsg(stPorts) {
125
+ const stPortsCopy = JSON.parse(JSON.stringify(stPorts));
126
+ const deltaShadowMsg = {
127
+ version: shadowVersion,
128
+ timestamp: shadowTimestamp,
129
+ state: { st_ports: stPortsCopy }
130
+ };
131
+ return deltaShadowMsg;
132
+ }
133
+ function transformDeltaToUpdateReported(deltaMsg) {
134
+ const { version, state } = deltaMsg;
135
+ const reportedStateReported = JSON.parse(JSON.stringify(state));
136
+ return reportedStateReported;
137
+ }
138
+ //---------------------------------------------------------------------------
139
+ // test class methods
140
+ //---------------------------------------------------------------------------
141
+ it('should be a singleton', () => {
142
+ const handler = secure_tunneling_1.SecureTunnelHandlerSingleton.getInstance();
143
+ expect(testStHandlerSingleton).toBe(handler);
144
+ });
145
+ it('should have a getSecureTunnelShadow method', () => {
146
+ expect(testStHandlerSingleton.getSecureTunnelShadow).toBeDefined();
147
+ });
148
+ it('should have a syncShadowToDeviceState method', () => {
149
+ expect(testStHandlerSingleton.syncShadowToDeviceState).toBeDefined();
150
+ });
151
+ it('should have a secureTunnelNotifyHandler method', () => {
152
+ expect(testStHandlerSingleton.secureTunnelNotifyHandler).toBeDefined();
153
+ });
154
+ it('should have a destroy method', () => {
155
+ expect(testStHandlerSingleton.destroy).toBeDefined();
156
+ });
157
+ it('should initialize reportedShadowState to default', () => {
158
+ const actualReportedShadow = testStHandlerSingleton.getSecureTunnelShadow();
159
+ expect(actualReportedShadow).toEqual(testDefaultShadow);
160
+ });
161
+ //---------------------------------------------------------------------------
162
+ // test syncShadowToDeviceState function
163
+ //---------------------------------------------------------------------------
164
+ it('should update reportedShadowSate to 1 SSH and 1 HTTP port config', async () => {
165
+ // Arrange
166
+ // --------------------------------------------------------------------
167
+ const deltaShadowMsg = createDeltaShadowMsg([
168
+ disabledSshPortInfo,
169
+ disabledHttpPortInfo_1
170
+ ]);
171
+ const expUpdateReported = transformDeltaToUpdateReported(deltaShadowMsg);
172
+ // Act
173
+ // --------------------------------------------------------------------
174
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
175
+ // Assert
176
+ // --------------------------------------------------------------------
177
+ expect(actualReportedShadow).toEqual(expUpdateReported);
178
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(0);
179
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
180
+ });
181
+ it('should update reportedShadowState to 1 SSH and 2 HTTP port config', async () => {
182
+ // Arrange
183
+ // --------------------------------------------------------------------
184
+ const deltaShadowMsg = createDeltaShadowMsg([
185
+ disabledSshPortInfo,
186
+ disabledHttpPortInfo_1,
187
+ disabledHttpPortInfo_2
188
+ ]);
189
+ const expUpdateReported = transformDeltaToUpdateReported(deltaShadowMsg);
190
+ // Act
191
+ // --------------------------------------------------------------------
192
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
193
+ // Assert
194
+ // --------------------------------------------------------------------
195
+ expect(actualReportedShadow).toEqual(expUpdateReported);
196
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(0);
197
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
198
+ });
199
+ it('should update reportedShadowState to 1 SSH and 2 HTTP port config, order is important', async () => {
200
+ // Arrange
201
+ // --------------------------------------------------------------------
202
+ const deltaShadowMsg = createDeltaShadowMsg([
203
+ disabledHttpPortInfo_1,
204
+ disabledSshPortInfo,
205
+ disabledHttpPortInfo_2
206
+ ]);
207
+ const expUpdateReported = transformDeltaToUpdateReported(deltaShadowMsg);
208
+ // Act
209
+ // --------------------------------------------------------------------
210
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
211
+ // Assert
212
+ // --------------------------------------------------------------------
213
+ expect(actualReportedShadow).toEqual(expUpdateReported);
214
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(0);
215
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
216
+ });
217
+ it('should update reportedShadowState to only 1 HTTP port config', async () => {
218
+ // Arrange
219
+ // --------------------------------------------------------------------
220
+ const deltaShadowMsg = createDeltaShadowMsg([disabledHttpPortInfo_1]);
221
+ const expUpdateReported = transformDeltaToUpdateReported(deltaShadowMsg);
222
+ // Act
223
+ // --------------------------------------------------------------------
224
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
225
+ // Assert
226
+ // --------------------------------------------------------------------
227
+ expect(actualReportedShadow).toEqual(expUpdateReported);
228
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(0);
229
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
230
+ });
231
+ it('should update reportedShadowState from 1 SSH port to 3 HTTP ports config', async () => {
232
+ // Arrange
233
+ // --------------------------------------------------------------------
234
+ const deltaShadowMsg = createDeltaShadowMsg([
235
+ disabledHttpPortInfo_1,
236
+ disabledHttpPortInfo_2,
237
+ disabledHttpPortInfo_3
238
+ ]);
239
+ const expUpdateReported = transformDeltaToUpdateReported(deltaShadowMsg);
240
+ // Act
241
+ // --------------------------------------------------------------------
242
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
243
+ // Assert
244
+ // --------------------------------------------------------------------
245
+ expect(actualReportedShadow).toEqual(expUpdateReported);
246
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(0);
247
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
248
+ });
249
+ it('should update reportedShadowState from 3 HTTP ports to only 1 SSH port config', async () => {
250
+ // Arrange
251
+ // --------------------------------------------------------------------
252
+ const orgDeltaShadowMsg = createDeltaShadowMsg([
253
+ disabledHttpPortInfo_1,
254
+ disabledHttpPortInfo_2,
255
+ disabledHttpPortInfo_3
256
+ ]);
257
+ const orgUpdateReported = transformDeltaToUpdateReported(orgDeltaShadowMsg);
258
+ let actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(orgDeltaShadowMsg);
259
+ expect(actualReportedShadow).toEqual(orgUpdateReported);
260
+ const expDeltaShadowMsg = createDeltaShadowMsg([disabledSshPortInfo]);
261
+ const expUpdateReported = transformDeltaToUpdateReported(expDeltaShadowMsg);
262
+ // Act
263
+ // --------------------------------------------------------------------
264
+ actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(expDeltaShadowMsg);
265
+ // Assert
266
+ // --------------------------------------------------------------------
267
+ expect(actualReportedShadow).toEqual(expUpdateReported);
268
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(0);
269
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
270
+ });
271
+ it('should update reportedShadowState, when the 1st HTTP shadow changes from disabled to enabled', async () => {
272
+ // Arrange
273
+ // --------------------------------------------------------------------
274
+ const orgDeltaShadowMsg = createDeltaShadowMsg([
275
+ disabledSshPortInfo,
276
+ disabledHttpPortInfo_1,
277
+ disabledHttpPortInfo_2
278
+ ]);
279
+ const orgUpdateReported = transformDeltaToUpdateReported(orgDeltaShadowMsg);
280
+ let actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(orgDeltaShadowMsg);
281
+ expect(actualReportedShadow).toEqual(orgUpdateReported);
282
+ const expDeltaShadowMsg = createDeltaShadowMsg([
283
+ disabledSshPortInfo,
284
+ enabledHttpPortInfo_1,
285
+ disabledHttpPortInfo_2
286
+ ]);
287
+ const expUpdateReported = transformDeltaToUpdateReported(expDeltaShadowMsg);
288
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
289
+ // Act
290
+ // --------------------------------------------------------------------
291
+ actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(expDeltaShadowMsg);
292
+ // Assert
293
+ // --------------------------------------------------------------------
294
+ expect(actualReportedShadow).toEqual(expUpdateReported);
295
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(1);
296
+ expect(spawner_detached_1.runDetachedProcess).toBeCalledWith('socat', [
297
+ `tcp4-listen:${ST_START_PORT_NUMBER + 1},fork`,
298
+ `tcp4:${enabledHttpPortInfo_1.ip}:${enabledHttpPortInfo_1.port}`
299
+ ]);
300
+ expect(spawner_detached_1.killDetachedProcess).not.toBeCalled();
301
+ });
302
+ it('should update reportedShadowState, when the 1st HTTP shadow changes from enabled to disabled', async () => {
303
+ // Arrange
304
+ // --------------------------------------------------------------------
305
+ const orgDeltaShadowMsg = createDeltaShadowMsg([
306
+ disabledSshPortInfo,
307
+ enabledHttpPortInfo_1,
308
+ disabledHttpPortInfo_2
309
+ ]);
310
+ const orgUpdateReported = transformDeltaToUpdateReported(orgDeltaShadowMsg);
311
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
312
+ let actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(orgDeltaShadowMsg);
313
+ expect(actualReportedShadow).toEqual(orgUpdateReported);
314
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(1);
315
+ const socatArgs = [
316
+ `tcp4-listen:${ST_START_PORT_NUMBER + 1},fork`,
317
+ `tcp4:${enabledHttpPortInfo_1.ip}:${enabledHttpPortInfo_1.port}`
318
+ ];
319
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledWith('socat', socatArgs);
320
+ const expDeltaShadowMsg = createDeltaShadowMsg([
321
+ disabledSshPortInfo,
322
+ disabledHttpPortInfo_1,
323
+ disabledHttpPortInfo_2
324
+ ]);
325
+ const expUpdateReported = transformDeltaToUpdateReported(expDeltaShadowMsg);
326
+ jest.clearAllMocks();
327
+ jest.resetModules();
328
+ jest.mocked(spawner_detached_1.killDetachedProcess).mockResolvedValueOnce(undefined);
329
+ // Act
330
+ // --------------------------------------------------------------------
331
+ actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(expDeltaShadowMsg);
332
+ // Assert
333
+ // --------------------------------------------------------------------
334
+ expect(actualReportedShadow).toEqual(expUpdateReported);
335
+ expect(spawner_detached_1.runDetachedProcess).not.toBeCalled();
336
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(1);
337
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledWith({}, [
338
+ ['socat', ...socatArgs].join(' ')
339
+ ]);
340
+ });
341
+ it('should update reportedShadowState, all 3 HTTP services enabled, with SSH disabled', async () => {
342
+ // Arrange
343
+ // --------------------------------------------------------------------
344
+ const expDeltaShadowMsg = createDeltaShadowMsg([
345
+ disabledSshPortInfo,
346
+ enabledHttpPortInfo_1,
347
+ enabledHttpPortInfo_2,
348
+ enabledHttpPortInfo_3
349
+ ]);
350
+ const expUpdateReported = transformDeltaToUpdateReported(expDeltaShadowMsg);
351
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
352
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
353
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
354
+ // Act
355
+ // --------------------------------------------------------------------
356
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(expDeltaShadowMsg);
357
+ // Assert
358
+ // --------------------------------------------------------------------
359
+ expect(actualReportedShadow).toEqual(expUpdateReported);
360
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(3);
361
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(1, 'socat', [
362
+ `tcp4-listen:${ST_START_PORT_NUMBER + 1},fork`,
363
+ `tcp4:${enabledHttpPortInfo_1.ip}:${enabledHttpPortInfo_1.port}`
364
+ ]);
365
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(2, 'socat', [
366
+ `tcp4-listen:${ST_START_PORT_NUMBER + 2},fork`,
367
+ `tcp4:${enabledHttpPortInfo_2.ip}:${enabledHttpPortInfo_2.port}`
368
+ ]);
369
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(3, 'socat', [
370
+ `tcp4-listen:${ST_START_PORT_NUMBER + 3},fork`,
371
+ `tcp4:${enabledHttpPortInfo_3.ip}:${enabledHttpPortInfo_3.port}`
372
+ ]);
373
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
374
+ });
375
+ it('should update reportedShadowState, all 1 SSH and 2 HTTP services enabled', async () => {
376
+ // Arrange
377
+ // --------------------------------------------------------------------
378
+ const expDeltaShadowMsg = createDeltaShadowMsg([
379
+ enabledSshPortInfo,
380
+ enabledHttpPortInfo_1,
381
+ enabledHttpPortInfo_2
382
+ ]);
383
+ const expUpdateReported = transformDeltaToUpdateReported(expDeltaShadowMsg);
384
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
385
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
386
+ // Act
387
+ // --------------------------------------------------------------------
388
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(expDeltaShadowMsg);
389
+ // Assert
390
+ // --------------------------------------------------------------------
391
+ expect(actualReportedShadow).toEqual(expUpdateReported);
392
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(2);
393
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(1, 'socat', [
394
+ `tcp4-listen:${ST_START_PORT_NUMBER + 1},fork`,
395
+ `tcp4:${enabledHttpPortInfo_1.ip}:${enabledHttpPortInfo_1.port}`
396
+ ]);
397
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(2, 'socat', [
398
+ `tcp4-listen:${ST_START_PORT_NUMBER + 2},fork`,
399
+ `tcp4:${enabledHttpPortInfo_2.ip}:${enabledHttpPortInfo_2.port}`
400
+ ]);
401
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
402
+ });
403
+ it('should update reportedShadowState, SSH1, HTTP1 and HTTP3 services enabled', async () => {
404
+ // Arrange
405
+ // --------------------------------------------------------------------
406
+ const expDeltaShadowMsg = createDeltaShadowMsg([
407
+ enabledSshPortInfo,
408
+ enabledHttpPortInfo_1,
409
+ disabledHttpPortInfo_2,
410
+ enabledHttpPortInfo_3
411
+ ]);
412
+ const expUpdateReported = transformDeltaToUpdateReported(expDeltaShadowMsg);
413
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
414
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
415
+ // Act
416
+ // --------------------------------------------------------------------
417
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(expDeltaShadowMsg);
418
+ // Assert
419
+ // --------------------------------------------------------------------
420
+ expect(actualReportedShadow).toEqual(expUpdateReported);
421
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(2);
422
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(1, 'socat', [
423
+ `tcp4-listen:${ST_START_PORT_NUMBER + 1},fork`,
424
+ `tcp4:${enabledHttpPortInfo_1.ip}:${enabledHttpPortInfo_1.port}`
425
+ ]);
426
+ expect(spawner_detached_1.runDetachedProcess).not.toHaveBeenCalledWith('socat', [
427
+ `tcp4-listen:${ST_START_PORT_NUMBER + 2},fork`,
428
+ `tcp4:${enabledHttpPortInfo_2.ip}:${enabledHttpPortInfo_2.port}`
429
+ ]);
430
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(2, 'socat', [
431
+ `tcp4-listen:${ST_START_PORT_NUMBER + 2},fork`,
432
+ `tcp4:${enabledHttpPortInfo_3.ip}:${enabledHttpPortInfo_3.port}`
433
+ ]);
434
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
435
+ });
436
+ it('should update last HTTP reportedShadowState, all 1 SSH and 3 HTTP services enabled', async () => {
437
+ // Arrange
438
+ // --------------------------------------------------------------------
439
+ const expectedShadow = createDeltaShadowMsg([
440
+ enabledSshPortInfo,
441
+ enabledHttpPortInfo_1,
442
+ enabledHttpPortInfo_2,
443
+ enabledHttpPortInfo_3
444
+ ]);
445
+ const expUpdateReported = transformDeltaToUpdateReported(expectedShadow);
446
+ const desiredDeltaMsg = createDeltaShadowMsg([
447
+ enabledSshPortInfo,
448
+ enabledHttpPortInfo_1,
449
+ enabledHttpPortInfo_2,
450
+ enabledHttpPortInfo_3
451
+ ]);
452
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
453
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
454
+ // Act
455
+ // --------------------------------------------------------------------
456
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(desiredDeltaMsg);
457
+ // Assert
458
+ // --------------------------------------------------------------------
459
+ expect(actualReportedShadow).toEqual(expUpdateReported);
460
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(3);
461
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(1, 'socat', [
462
+ `tcp4-listen:${ST_START_PORT_NUMBER + 1},fork`,
463
+ `tcp4:${enabledHttpPortInfo_1.ip}:${enabledHttpPortInfo_1.port}`
464
+ ]);
465
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(2, 'socat', [
466
+ `tcp4-listen:${ST_START_PORT_NUMBER + 2},fork`,
467
+ `tcp4:${enabledHttpPortInfo_2.ip}:${enabledHttpPortInfo_2.port}`
468
+ ]);
469
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledWith('socat', [
470
+ `tcp4-listen:${ST_START_PORT_NUMBER + 3},fork`,
471
+ `tcp4:${enabledHttpPortInfo_3.ip}:${enabledHttpPortInfo_3.port}`
472
+ ]);
473
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
474
+ });
475
+ it('should update last HTTP reportedShadowState, when ports changing the state from disabled to enabled', async () => {
476
+ // Arrange
477
+ // --------------------------------------------------------------------
478
+ const disableDeltaMsg = createDeltaShadowMsg([
479
+ disabledSshPortInfo,
480
+ disabledHttpPortInfo_1,
481
+ disabledHttpPortInfo_2,
482
+ disabledHttpPortInfo_3
483
+ ]);
484
+ const disableUpdateReported = transformDeltaToUpdateReported(disableDeltaMsg);
485
+ const disabledReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(disableDeltaMsg);
486
+ expect(disabledReportedShadow).toEqual(disableUpdateReported);
487
+ const enableDeltaMsg = createDeltaShadowMsg([
488
+ enabledSshPortInfo,
489
+ enabledHttpPortInfo_1,
490
+ enabledHttpPortInfo_2,
491
+ enabledHttpPortInfo_3
492
+ ]);
493
+ const expectedDeltaShadow = createDeltaShadowMsg([
494
+ enabledSshPortInfo,
495
+ enabledHttpPortInfo_1,
496
+ enabledHttpPortInfo_2,
497
+ enabledHttpPortInfo_3
498
+ ]);
499
+ const expectedUpdateReported = transformDeltaToUpdateReported(expectedDeltaShadow);
500
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
501
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
502
+ // Act
503
+ // --------------------------------------------------------------------
504
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(enableDeltaMsg);
505
+ // Assert
506
+ // --------------------------------------------------------------------
507
+ expect(actualReportedShadow).toEqual(expectedUpdateReported);
508
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(3);
509
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(1, 'socat', [
510
+ `tcp4-listen:${ST_START_PORT_NUMBER + 1},fork`,
511
+ `tcp4:${enabledHttpPortInfo_1.ip}:${enabledHttpPortInfo_1.port}`
512
+ ]);
513
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenNthCalledWith(2, 'socat', [
514
+ `tcp4-listen:${ST_START_PORT_NUMBER + 2},fork`,
515
+ `tcp4:${enabledHttpPortInfo_2.ip}:${enabledHttpPortInfo_2.port}`
516
+ ]);
517
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledWith('socat', [
518
+ `tcp4-listen:${ST_START_PORT_NUMBER + 3},fork`,
519
+ `tcp4:${enabledHttpPortInfo_3.ip}:${enabledHttpPortInfo_3.port}`
520
+ ]);
521
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
522
+ });
523
+ //---------------------------------------------------------------------------
524
+ // test destroy function
525
+ //---------------------------------------------------------------------------
526
+ it('should destroy all socat processes', async () => {
527
+ // Arrange
528
+ // --------------------------------------------------------------------
529
+ const stConfig = [enabledSshPortInfo, enabledHttpPortInfo_1];
530
+ const deltaShadowMsg = createDeltaShadowMsg(stConfig);
531
+ const updateReported = transformDeltaToUpdateReported(deltaShadowMsg);
532
+ stConfig
533
+ .filter((portInfo) => portInfo.type === 'HTTP')
534
+ .forEach((portInfo) => {
535
+ jest
536
+ .mocked(spawner_detached_1.runDetachedProcess)
537
+ .mockResolvedValueOnce({});
538
+ });
539
+ const newReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
540
+ expect(newReportedShadow).toEqual(updateReported);
541
+ jest.clearAllMocks(); // after setting up the shadow, we need to reset mocks again
542
+ jest.resetModules();
543
+ const expStConfig = [disabledSshPortInfo];
544
+ const expDeltaShadowMsg = createDeltaShadowMsg(expStConfig);
545
+ const expUpdateReported = transformDeltaToUpdateReported(expDeltaShadowMsg);
546
+ // Act
547
+ // --------------------------------------------------------------------
548
+ await testStHandlerSingleton.destroy();
549
+ const actualReportedShadow = testStHandlerSingleton.getSecureTunnelShadow();
550
+ // Assert
551
+ // --------------------------------------------------------------------
552
+ expect(actualReportedShadow).toEqual(expUpdateReported);
553
+ });
554
+ //---------------------------------------------------------------------------
555
+ // test secureTunnelNotifyHandler function
556
+ //---------------------------------------------------------------------------
557
+ it('should start Secure Tunnel, given localproxy already downloaded and default shadow is not enabled, that is default SSH connection', async () => {
558
+ // Arrange
559
+ // --------------------------------------------------------------------
560
+ mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that localproxy already exists on file system
561
+ const message = {
562
+ clientAccessToken: 'DefaultSSHConnection_001',
563
+ region: 'us-west-2',
564
+ services: ['SSH']
565
+ };
566
+ const expectedLocalproxyArgs = [
567
+ '--destination-app',
568
+ '22',
569
+ '--region',
570
+ message.region,
571
+ '--capath',
572
+ directories_1.AWS_ROOT_CERTIFICATE_FILE_PATH,
573
+ '--local-bind-address',
574
+ '0.0.0.0',
575
+ '-t',
576
+ message.clientAccessToken
577
+ ];
578
+ // Act
579
+ // --------------------------------------------------------------------
580
+ await testStHandlerSingleton.secureTunnelNotifyHandler(message);
581
+ // Assert
582
+ // --------------------------------------------------------------------
583
+ expect(mockJsSpawner.exists).toHaveBeenCalledTimes(1);
584
+ expect(system_info_1.getArch).not.toHaveBeenCalled();
585
+ expect(system_info_1.getOsVersion).not.toHaveBeenCalled();
586
+ expect(system_info_1.getDistribution).not.toHaveBeenCalled();
587
+ expect(mockJsSpawner.mkdirp).not.toHaveBeenCalled();
588
+ expect(mockJsSpawner.run).not.toHaveBeenCalled();
589
+ expect(download_file_1.downloadFile).not.toHaveBeenCalled();
590
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(1);
591
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledWith(directories_1.SECURE_TUNNEL_BIN_PATH, expectedLocalproxyArgs);
592
+ expect(spawner_detached_1.killDetachedProcess).not.toHaveBeenCalled();
593
+ });
594
+ const testCasesForDIfferentLocalproxyEnv = [
595
+ // linuxDistro, osVersion, arch
596
+ ['debian', '12', 'aarch64'],
597
+ ['debian', '12', 'arm64'],
598
+ ['macos', '13.4', 'arm64v8'],
599
+ ['raspbian', '9.13', 'armhf'],
600
+ ['raspbian', '11', 'armhf'],
601
+ ['ubuntu', '18.04', 'amd64'],
602
+ ['ubuntu', '18.04', 'arm64'],
603
+ ['ubuntu', '18.04', 'armhf'],
604
+ ['ubuntu', '20.04', 'amd64'],
605
+ ['ubuntu', '20.04', 'arm64'],
606
+ ['ubuntu', '20.04', 'armhf'],
607
+ ['ubuntu', '22.04', 'amd64'],
608
+ ['ubuntu', '22.04', 'arm64'],
609
+ ['ubuntu', '23.04', 'amd64'],
610
+ ['ubuntu', '23.04', 'arm64'],
611
+ ['ubuntu', '23.10', 'amd64'],
612
+ ['ubuntu', '23.10', 'arm64']
613
+ ];
614
+ test.each(testCasesForDIfferentLocalproxyEnv)('should start Secure Tunnel, given localproxy downloaded needed and default shadow is not enabled, that is default SSH connection', async (linuxDistro, osVersion, arch) => {
615
+ // Arrange
616
+ // --------------------------------------------------------------------
617
+ mockJsSpawner.exists.mockResolvedValueOnce(false); // mock that localproxy does not exist on file system
618
+ jest.mocked(system_info_1.getArch).mockResolvedValueOnce(arch);
619
+ jest.mocked(system_info_1.getOsVersion).mockResolvedValueOnce(osVersion);
620
+ jest.mocked(system_info_1.getDistribution).mockResolvedValueOnce(linuxDistro);
621
+ const expectedUrl = `${urls_1.aaiArtifactsBucketUrl}/securetunnel/${linuxDistro}/${osVersion}/${arch}/${directories_1.SECURE_TUNNEL_BIN_NAME}`;
622
+ const message = {
623
+ clientAccessToken: 'DefaultSSHConnection_001',
624
+ region: 'us-west-2',
625
+ services: ['SSH']
626
+ };
627
+ const expectedLocalproxyArgs = [
628
+ '--destination-app',
629
+ '22',
630
+ '--region',
631
+ message.region,
632
+ '--capath',
633
+ directories_1.AWS_ROOT_CERTIFICATE_FILE_PATH,
634
+ '--local-bind-address',
635
+ '0.0.0.0',
636
+ '-t',
637
+ message.clientAccessToken
638
+ ];
639
+ // Act
640
+ // --------------------------------------------------------------------
641
+ await testStHandlerSingleton.secureTunnelNotifyHandler(message);
642
+ // Assert
643
+ // --------------------------------------------------------------------
644
+ expect(mockJsSpawner.exists).toHaveBeenCalledTimes(1);
645
+ expect(mockJsSpawner.exists).toHaveBeenCalledWith(directories_1.SECURE_TUNNEL_BIN_PATH);
646
+ expect(system_info_1.getArch).toHaveBeenCalledTimes(1);
647
+ expect(system_info_1.getOsVersion).toHaveBeenCalledTimes(1);
648
+ expect(system_info_1.getDistribution).toHaveBeenCalledTimes(1);
649
+ expect(mockJsSpawner.mkdirp).toHaveBeenCalledTimes(1);
650
+ expect(mockJsSpawner.mkdirp).toHaveBeenCalledWith((0, path_1.join)(paths_1.AAI_DIR, directories_1.SECURE_TUNNEL_BIN_DIR));
651
+ expect(mockJsSpawner.run).toHaveBeenCalledTimes(1);
652
+ expect(mockJsSpawner.run).toHaveBeenCalledWith({
653
+ exe: 'chmod',
654
+ args: ['+x', directories_1.SECURE_TUNNEL_BIN_PATH]
655
+ });
656
+ expect(download_file_1.downloadFile).toHaveBeenCalledTimes(1);
657
+ expect(download_file_1.downloadFile).toHaveBeenCalledWith({
658
+ url: expectedUrl,
659
+ path: directories_1.SECURE_TUNNEL_BIN_PATH,
660
+ errorMessage: `Secure Tunnel bin for ${linuxDistro} ${osVersion} ${arch} not found}`
661
+ });
662
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(1);
663
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledWith(directories_1.SECURE_TUNNEL_BIN_PATH, expectedLocalproxyArgs);
664
+ expect(spawner_detached_1.killDetachedProcess).not.toHaveBeenCalled();
665
+ });
666
+ const testCasesForInvalidSecureTunnelNotificationParameters = [
667
+ [{ clientAccessToken: '', region: '', services: [] }],
668
+ [
669
+ {
670
+ clientAccessToken: '',
671
+ region: 'us-west-2',
672
+ services: ['SSH']
673
+ }
674
+ ],
675
+ [
676
+ {
677
+ clientAccessToken: '001',
678
+ region: 'us-west-1',
679
+ services: ['SSH']
680
+ }
681
+ ],
682
+ [
683
+ {
684
+ clientAccessToken: '002',
685
+ region: 'us-east-2',
686
+ services: ['SSH']
687
+ }
688
+ ],
689
+ [
690
+ {
691
+ clientAccessToken: '003',
692
+ region: 'eu-central-1',
693
+ services: ['SSH']
694
+ }
695
+ ],
696
+ [
697
+ {
698
+ clientAccessToken: '004',
699
+ region: 'invalid region',
700
+ services: ['SSH']
701
+ }
702
+ ],
703
+ [
704
+ {
705
+ clientAccessToken: '005',
706
+ region: 'us-west-2',
707
+ services: [] // no service field
708
+ }
709
+ ],
710
+ [
711
+ {
712
+ clientAccessToken: '006',
713
+ region: 'us-west-2',
714
+ services: [''] // empty field in service
715
+ }
716
+ ],
717
+ [
718
+ {
719
+ clientAccessToken: '007',
720
+ region: 'us-west-2',
721
+ services: ['SSH', ''] // one of the fields is empty in service
722
+ }
723
+ ],
724
+ [
725
+ {
726
+ clientAccessToken: '008',
727
+ region: 'us-west-2',
728
+ services: ['SSH1', '', 'HTTP1'] // one of the fields is empty in service
729
+ }
730
+ ],
731
+ [
732
+ {
733
+ clientAccessToken: '009',
734
+ region: 'us-west-2',
735
+ services: ['SSH1', 'NOT_VALID'] // one of the fields is invalid
736
+ }
737
+ ],
738
+ [
739
+ {
740
+ clientAccessToken: '010',
741
+ region: 'us-west-2',
742
+ services: ['FTP', 'HTTP1'] // one of the fields is invalid
743
+ }
744
+ ],
745
+ [
746
+ {
747
+ clientAccessToken: '011',
748
+ region: 'us-west-2',
749
+ services: ['SSH1', 'FTP1', 'HTTP1'] // one of the fields is invalid
750
+ }
751
+ ]
752
+ ];
753
+ test.each(testCasesForInvalidSecureTunnelNotificationParameters)('should NOT start Secure Tunnel, given one of the fields of message is invalid', async (message) => {
754
+ // Arrange
755
+ // --------------------------------------------------------------------
756
+ mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that localproxy already exists on file system
757
+ const expectedLocalproxyArgs = [
758
+ '--destination-app',
759
+ '22',
760
+ '--region',
761
+ message.region,
762
+ '--capath',
763
+ directories_1.AWS_ROOT_CERTIFICATE_FILE_PATH,
764
+ '--local-bind-address',
765
+ '0.0.0.0',
766
+ '-t',
767
+ message.clientAccessToken
768
+ ];
769
+ // Act
770
+ // --------------------------------------------------------------------
771
+ await testStHandlerSingleton.secureTunnelNotifyHandler(message);
772
+ // Assert
773
+ // --------------------------------------------------------------------
774
+ expect(mockJsSpawner.exists).toHaveBeenCalledTimes(0);
775
+ expect(system_info_1.getArch).not.toHaveBeenCalled();
776
+ expect(system_info_1.getOsVersion).not.toHaveBeenCalled();
777
+ expect(system_info_1.getDistribution).not.toHaveBeenCalled();
778
+ expect(mockJsSpawner.mkdirp).not.toHaveBeenCalled();
779
+ expect(download_file_1.downloadFile).not.toHaveBeenCalled();
780
+ expect(mockJsSpawner.run).not.toHaveBeenCalled();
781
+ expect(spawner_detached_1.runDetachedProcess).not.toHaveBeenCalled();
782
+ expect(spawner_detached_1.killDetachedProcess).not.toHaveBeenCalled();
783
+ });
784
+ const testCasesMismatchBetweenServicesAndConfig = [
785
+ [
786
+ {
787
+ clientAccessToken: '001',
788
+ region: 'us-west-2',
789
+ services: ['SSH']
790
+ },
791
+ [enabledHttpPortInfo_1]
792
+ ],
793
+ [
794
+ {
795
+ clientAccessToken: '002',
796
+ region: 'us-west-2',
797
+ services: ['SSH1', 'HTTP1', 'HTTP2']
798
+ },
799
+ [enabledHttpPortInfo_1, enabledHttpPortInfo_2, enabledHttpPortInfo_3]
800
+ ],
801
+ [
802
+ {
803
+ clientAccessToken: '003',
804
+ region: 'us-west-2',
805
+ services: ['HTTP1', 'HTTP2']
806
+ },
807
+ [enabledHttpPortInfo_1, enabledHttpPortInfo_2, enabledHttpPortInfo_3]
808
+ ],
809
+ [
810
+ {
811
+ clientAccessToken: '004',
812
+ region: 'us-west-2',
813
+ services: ['HTTP1', 'HTTP2', 'HTTP3']
814
+ },
815
+ [enabledHttpPortInfo_1, enabledHttpPortInfo_2]
816
+ ],
817
+ [
818
+ {
819
+ clientAccessToken: '005',
820
+ region: 'us-west-2',
821
+ services: ['HTTP1', 'HTTP2', 'HTTP3', 'HTTP4']
822
+ },
823
+ [enabledHttpPortInfo_1, enabledHttpPortInfo_2, enabledHttpPortInfo_3]
824
+ ],
825
+ [
826
+ {
827
+ clientAccessToken: '006',
828
+ region: 'us-west-2',
829
+ services: ['SSH', 'HTTP1']
830
+ },
831
+ [enabledSshPortInfo]
832
+ ],
833
+ [
834
+ {
835
+ clientAccessToken: '006',
836
+ region: 'us-west-2',
837
+ services: ['SSH1', 'SSH2']
838
+ },
839
+ [enabledSshPortInfo, invalidSshPortInfo]
840
+ ]
841
+ ];
842
+ test.each(testCasesMismatchBetweenServicesAndConfig)('should NOT start Secure Tunnel, given services mismatch received config', async (message, stConfig) => {
843
+ // Arrange
844
+ // --------------------------------------------------------------------
845
+ const deltaShadowMsg = createDeltaShadowMsg(stConfig);
846
+ const updateReported = transformDeltaToUpdateReported(deltaShadowMsg);
847
+ stConfig
848
+ .filter((portInfo) => portInfo.type === 'HTTP')
849
+ .forEach((portInfo) => {
850
+ jest
851
+ .mocked(spawner_detached_1.runDetachedProcess)
852
+ .mockResolvedValueOnce({});
853
+ });
854
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
855
+ expect(actualReportedShadow).toEqual(updateReported);
856
+ jest.clearAllMocks(); // after setting up the shadow, we need to reset mocks again
857
+ jest.resetModules();
858
+ mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that localproxy already exists on file system
859
+ const expectedLocalproxyArgs = [
860
+ '--destination-app',
861
+ '22',
862
+ '--region',
863
+ message.region,
864
+ '--capath',
865
+ directories_1.AWS_ROOT_CERTIFICATE_FILE_PATH,
866
+ '--local-bind-address',
867
+ '0.0.0.0',
868
+ '-t',
869
+ message.clientAccessToken
870
+ ];
871
+ // Act
872
+ // --------------------------------------------------------------------
873
+ await testStHandlerSingleton.secureTunnelNotifyHandler(message);
874
+ // Assert
875
+ // --------------------------------------------------------------------
876
+ expect(mockJsSpawner.exists).toHaveBeenCalledTimes(0);
877
+ expect(system_info_1.getArch).not.toHaveBeenCalled();
878
+ expect(system_info_1.getOsVersion).not.toHaveBeenCalled();
879
+ expect(system_info_1.getDistribution).not.toHaveBeenCalled();
880
+ expect(mockJsSpawner.mkdirp).not.toHaveBeenCalled();
881
+ expect(download_file_1.downloadFile).not.toHaveBeenCalled();
882
+ expect(spawner_detached_1.runDetachedProcess).not.toHaveBeenCalled();
883
+ expect(spawner_detached_1.killDetachedProcess).not.toHaveBeenCalled();
884
+ });
885
+ const testCasesMatchBetweenServicesAndConfig = [
886
+ [
887
+ {
888
+ clientAccessToken: '001',
889
+ region: 'us-west-2',
890
+ services: ['SSH']
891
+ },
892
+ [enabledSshPortInfo],
893
+ '22'
894
+ ],
895
+ [
896
+ {
897
+ clientAccessToken: '002',
898
+ region: 'us-west-2',
899
+ services: ['SSH1', 'HTTP1', 'HTTP2']
900
+ },
901
+ [
902
+ enabledSshPortInfo,
903
+ enabledHttpPortInfo_1,
904
+ enabledHttpPortInfo_2,
905
+ disabledHttpPortInfo_3
906
+ ],
907
+ 'SSH1=22,HTTP1=5011,HTTP2=5012'
908
+ ],
909
+ [
910
+ {
911
+ clientAccessToken: '003',
912
+ region: 'us-west-2',
913
+ services: ['HTTP1', 'HTTP2']
914
+ },
915
+ [enabledHttpPortInfo_1, enabledHttpPortInfo_2],
916
+ 'HTTP1=5011,HTTP2=5012'
917
+ ],
918
+ [
919
+ {
920
+ clientAccessToken: '004',
921
+ region: 'us-west-2',
922
+ services: ['HTTP1', 'HTTP2', 'HTTP3']
923
+ },
924
+ [enabledHttpPortInfo_1, enabledHttpPortInfo_2, enabledHttpPortInfo_3],
925
+ 'HTTP1=5011,HTTP2=5012,HTTP3=5013'
926
+ ],
927
+ [
928
+ {
929
+ clientAccessToken: '005',
930
+ region: 'us-west-2',
931
+ services: ['HTTP1', 'HTTP2', 'HTTP3']
932
+ },
933
+ [
934
+ disabledSshPortInfo,
935
+ enabledHttpPortInfo_1,
936
+ enabledHttpPortInfo_2,
937
+ enabledHttpPortInfo_3
938
+ ],
939
+ 'HTTP1=5011,HTTP2=5012,HTTP3=5013'
940
+ ],
941
+ [
942
+ {
943
+ clientAccessToken: '006',
944
+ region: 'us-west-2',
945
+ services: ['SSH1', 'HTTP1']
946
+ },
947
+ [
948
+ enabledSshPortInfo,
949
+ disabledHttpPortInfo_1,
950
+ enabledHttpPortInfo_2,
951
+ disabledHttpPortInfo_3
952
+ ],
953
+ 'SSH1=22,HTTP1=5011'
954
+ ]
955
+ ];
956
+ test.each(testCasesMatchBetweenServicesAndConfig)('should start Secure Tunnel, given services and port config match', async (message, stConfig, expectedPortMapping) => {
957
+ // Arrange
958
+ // --------------------------------------------------------------------
959
+ const deltaShadowMsg = createDeltaShadowMsg(stConfig);
960
+ const updateReported = transformDeltaToUpdateReported(deltaShadowMsg);
961
+ stConfig
962
+ .filter((portInfo) => portInfo.type === 'HTTP')
963
+ .forEach((portInfo) => {
964
+ jest
965
+ .mocked(spawner_detached_1.runDetachedProcess)
966
+ .mockResolvedValueOnce({});
967
+ });
968
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
969
+ expect(actualReportedShadow).toEqual(updateReported);
970
+ jest.clearAllMocks(); // after setting up the shadow, we need to reset mocks again
971
+ jest.resetModules();
972
+ mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that localproxy already exists on file system
973
+ const expectedLocalproxyArgs = [
974
+ '--destination-app',
975
+ expectedPortMapping,
976
+ '--region',
977
+ message.region,
978
+ '--capath',
979
+ directories_1.AWS_ROOT_CERTIFICATE_FILE_PATH,
980
+ '--local-bind-address',
981
+ '0.0.0.0',
982
+ '-t',
983
+ message.clientAccessToken
984
+ ];
985
+ // Act
986
+ // --------------------------------------------------------------------
987
+ await testStHandlerSingleton.secureTunnelNotifyHandler(message);
988
+ // Assert
989
+ // --------------------------------------------------------------------
990
+ expect(mockJsSpawner.exists).toHaveBeenCalledTimes(1);
991
+ expect(system_info_1.getArch).not.toHaveBeenCalled();
992
+ expect(system_info_1.getOsVersion).not.toHaveBeenCalled();
993
+ expect(system_info_1.getDistribution).not.toHaveBeenCalled();
994
+ expect(mockJsSpawner.mkdirp).not.toHaveBeenCalled();
995
+ expect(download_file_1.downloadFile).not.toHaveBeenCalled();
996
+ expect(mockJsSpawner.run).not.toHaveBeenCalled();
997
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(1);
998
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledWith(directories_1.SECURE_TUNNEL_BIN_PATH, expectedLocalproxyArgs);
999
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(0);
1000
+ });
1001
+ it('should start Secure Tunnel, given 1 SSH and 1 HTTP ports enabled', async () => {
1002
+ // Arrange
1003
+ // --------------------------------------------------------------------
1004
+ const message = {
1005
+ clientAccessToken: '089',
1006
+ region: 'us-west-2',
1007
+ services: ['SSH1', 'HTTP1']
1008
+ };
1009
+ let stConfig = [enabledSshPortInfo, enabledHttpPortInfo_1];
1010
+ const expectedPortMapping = 'SSH1=22,HTTP1=5011';
1011
+ let deltaShadowMsg = createDeltaShadowMsg(stConfig);
1012
+ let updateReported = transformDeltaToUpdateReported(deltaShadowMsg);
1013
+ stConfig
1014
+ .filter((portInfo) => portInfo.type === 'HTTP')
1015
+ .forEach((portInfo) => {
1016
+ jest
1017
+ .mocked(spawner_detached_1.runDetachedProcess)
1018
+ .mockResolvedValueOnce({});
1019
+ });
1020
+ const socatArgs = [
1021
+ `tcp4-listen:${ST_START_PORT_NUMBER + 1},fork`,
1022
+ `tcp4:${enabledHttpPortInfo_1.ip}:${enabledHttpPortInfo_1.port}`
1023
+ ];
1024
+ const beforeReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
1025
+ expect(beforeReportedShadow).toEqual(updateReported);
1026
+ jest.clearAllMocks(); // after setting up the shadow, we need to reset mocks again
1027
+ jest.resetModules();
1028
+ mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that localproxy already exists on file system
1029
+ const expectedLocalproxyArgs = [
1030
+ '--destination-app',
1031
+ expectedPortMapping,
1032
+ '--region',
1033
+ message.region,
1034
+ '--capath',
1035
+ directories_1.AWS_ROOT_CERTIFICATE_FILE_PATH,
1036
+ '--local-bind-address',
1037
+ '0.0.0.0',
1038
+ '-t',
1039
+ message.clientAccessToken
1040
+ ];
1041
+ await testStHandlerSingleton.secureTunnelNotifyHandler(message);
1042
+ jest.clearAllMocks(); // after setting up the shadow, we need to reset mocks again
1043
+ jest.resetModules();
1044
+ stConfig = [disabledSshPortInfo, disabledHttpPortInfo_1];
1045
+ deltaShadowMsg = createDeltaShadowMsg(stConfig);
1046
+ updateReported = transformDeltaToUpdateReported(deltaShadowMsg);
1047
+ stConfig.forEach((portInfo) => {
1048
+ jest.mocked(spawner_detached_1.runDetachedProcess).mockResolvedValueOnce({});
1049
+ });
1050
+ // Act
1051
+ // --------------------------------------------------------------------
1052
+ const actualReportedShadow = await testStHandlerSingleton.syncShadowToDeviceState(deltaShadowMsg);
1053
+ // Assert
1054
+ // --------------------------------------------------------------------
1055
+ expect(actualReportedShadow).toEqual(updateReported);
1056
+ expect(mockJsSpawner.exists).not.toHaveBeenCalled();
1057
+ expect(system_info_1.getArch).not.toHaveBeenCalled();
1058
+ expect(system_info_1.getOsVersion).not.toHaveBeenCalled();
1059
+ expect(system_info_1.getDistribution).not.toHaveBeenCalled();
1060
+ expect(mockJsSpawner.mkdirp).not.toHaveBeenCalled();
1061
+ expect(download_file_1.downloadFile).not.toHaveBeenCalled();
1062
+ expect(mockJsSpawner.run).not.toHaveBeenCalled();
1063
+ expect(spawner_detached_1.runDetachedProcess).toHaveBeenCalledTimes(0);
1064
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledTimes(2);
1065
+ expect(spawner_detached_1.killDetachedProcess).toHaveBeenCalledWith({}, [
1066
+ ['socat', ...socatArgs].join(' ')
1067
+ ]);
1068
+ });
1069
+ });
1070
+ //# sourceMappingURL=secure-tunneling.test.js.map