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