@kapeta/local-cluster-service 0.43.2 → 0.44.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/CHANGELOG.md +20 -0
- package/dist/cjs/src/proxy/routes.js +19 -19
- package/dist/cjs/src/proxy/types/rest.d.ts +2 -4
- package/dist/cjs/src/proxy/types/rest.js +45 -6
- package/dist/cjs/src/proxy/types/web.d.ts +2 -4
- package/dist/cjs/src/proxy/types/web.js +2 -2
- package/dist/cjs/src/types.d.ts +5 -2
- package/dist/esm/src/proxy/routes.js +19 -19
- package/dist/esm/src/proxy/types/rest.d.ts +2 -4
- package/dist/esm/src/proxy/types/rest.js +45 -6
- package/dist/esm/src/proxy/types/web.d.ts +2 -4
- package/dist/esm/src/proxy/types/web.js +2 -2
- package/dist/esm/src/types.d.ts +5 -2
- package/package.json +7 -4
- package/src/proxy/routes.ts +26 -20
- package/src/proxy/types/rest.ts +73 -11
- package/src/proxy/types/web.ts +3 -5
- package/src/types.ts +5 -2
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
# [0.44.0](https://github.com/kapetacom/local-cluster-service/compare/v0.43.3...v0.44.0) (2024-05-08)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* Remove wrong todo comment ([c300f3c](https://github.com/kapetacom/local-cluster-service/commit/c300f3c8bd2542517317aca0d54825332f6424bc))
|
7
|
+
* Safeguard with optional chaining ([ff8af98](https://github.com/kapetacom/local-cluster-service/commit/ff8af98845d3cc6431a919a7d251c5a90e5b88ef))
|
8
|
+
|
9
|
+
|
10
|
+
### Features
|
11
|
+
|
12
|
+
* Mask sensitive data in req and res body ([39c0c46](https://github.com/kapetacom/local-cluster-service/commit/39c0c46a9f9c58938b70c4736d1fb8ec651d7d9c))
|
13
|
+
|
14
|
+
## [0.43.3](https://github.com/kapetacom/local-cluster-service/compare/v0.43.2...v0.43.3) (2024-05-08)
|
15
|
+
|
16
|
+
|
17
|
+
### Bug Fixes
|
18
|
+
|
19
|
+
* bump microfrontend w/ node 20.13 sentry fix ([#145](https://github.com/kapetacom/local-cluster-service/issues/145)) ([30cf291](https://github.com/kapetacom/local-cluster-service/commit/30cf2917985217eb6b9a172dade6e8cadea73d7e))
|
20
|
+
|
1
21
|
## [0.43.2](https://github.com/kapetacom/local-cluster-service/compare/v0.43.1...v0.43.2) (2024-05-03)
|
2
22
|
|
3
23
|
|
@@ -49,19 +49,19 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
|
|
49
49
|
});
|
50
50
|
return;
|
51
51
|
}
|
52
|
-
const
|
52
|
+
const consumerBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
|
53
53
|
return blockInstance.id.toLowerCase() === connection.consumer.blockId.toLowerCase();
|
54
54
|
});
|
55
|
-
if (!
|
55
|
+
if (!consumerBlockInstance) {
|
56
56
|
res.status(401).send({ error: `Block instance not found "${req.params.consumerInstanceId}` });
|
57
57
|
return;
|
58
58
|
}
|
59
|
-
const
|
60
|
-
if (!
|
61
|
-
res.status(401).send({ error: `Block asset not found "${
|
59
|
+
const consumerBlockAsset = await assetManager_1.assetManager.getAsset(consumerBlockInstance.block.ref);
|
60
|
+
if (!consumerBlockAsset) {
|
61
|
+
res.status(401).send({ error: `Block asset not found "${consumerBlockInstance.block.ref}` });
|
62
62
|
return;
|
63
63
|
}
|
64
|
-
const consumerResource = getResource(
|
64
|
+
const consumerResource = getResource(consumerBlockAsset.data.spec.consumers, req.params.consumerResourceName);
|
65
65
|
if (!consumerResource) {
|
66
66
|
res.status(401).send({
|
67
67
|
error: `Block resource not found "${req.params.consumerInstanceId}::${req.params.consumerResourceName}`,
|
@@ -69,19 +69,19 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
|
|
69
69
|
return;
|
70
70
|
}
|
71
71
|
const basePath = clusterService_1.clusterService.getProxyPath(req.params.systemId, req.params.consumerInstanceId, req.params.consumerResourceName, req.params.type);
|
72
|
-
const
|
72
|
+
const providerBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
|
73
73
|
return blockInstance.id.toLowerCase() === connection.provider.blockId.toLowerCase();
|
74
74
|
});
|
75
|
-
if (!
|
75
|
+
if (!providerBlockInstance) {
|
76
76
|
res.status(401).send({ error: `Block instance not found "${connection.provider.blockId}` });
|
77
77
|
return;
|
78
78
|
}
|
79
|
-
const
|
80
|
-
if (!
|
81
|
-
res.status(401).send({ error: `Block asset not found "${
|
79
|
+
const providerBlockAsset = await assetManager_1.assetManager.getAsset(providerBlockInstance.block.ref);
|
80
|
+
if (!providerBlockAsset) {
|
81
|
+
res.status(401).send({ error: `Block asset not found "${providerBlockInstance.block.ref}` });
|
82
82
|
return;
|
83
83
|
}
|
84
|
-
const providerResource = getResource(
|
84
|
+
const providerResource = getResource(providerBlockAsset.data.spec.providers, connection.provider.resourceName);
|
85
85
|
if (!providerResource) {
|
86
86
|
res.status(401).send({
|
87
87
|
error: `Block resource not found "${connection.provider.blockId}::${connection.provider.resourceName}`,
|
@@ -93,18 +93,18 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
|
|
93
93
|
while (address.endsWith('/')) {
|
94
94
|
address = address.substring(0, address.length - 1);
|
95
95
|
}
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
that is being called and identify the destination path from the connection.
|
100
|
-
*/
|
96
|
+
// Get the path the consumer requested. Note that this might not match the path the
|
97
|
+
// destination is expecting so we need to identify the method that is being called and
|
98
|
+
// identify the destination path from the connection.
|
101
99
|
const consumerPath = req.originalUrl.substring(basePath.length - 1);
|
102
100
|
typeHandler(req, res, {
|
103
|
-
consumerPath,
|
104
101
|
address,
|
102
|
+
connection,
|
103
|
+
consumerPath,
|
105
104
|
consumerResource,
|
105
|
+
consumerBlockAsset,
|
106
106
|
providerResource,
|
107
|
-
|
107
|
+
providerBlockAsset,
|
108
108
|
});
|
109
109
|
}
|
110
110
|
catch (err) {
|
@@ -2,9 +2,7 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import {
|
6
|
-
import { ProxyRequestInfo } from '../../types';
|
7
|
-
import { StringBodyRequest } from '../../middleware/stringBody';
|
5
|
+
import { ProxyRequestHandler } from '../../types';
|
8
6
|
import { Resource } from '@kapeta/schemas';
|
9
7
|
export declare function getRestMethodId(restResource: Resource, httpMethod: string, httpPath: string): string | undefined;
|
10
|
-
export declare
|
8
|
+
export declare const proxyRestRequest: ProxyRequestHandler;
|
@@ -16,6 +16,7 @@ const networkManager_1 = require("../../networkManager");
|
|
16
16
|
const socketManager_1 = require("../../socketManager");
|
17
17
|
const qs_1 = require("qs");
|
18
18
|
const web_1 = require("./web");
|
19
|
+
const kaplang_core_1 = require("@kapeta/kaplang-core");
|
19
20
|
function getRestMethodId(restResource, httpMethod, httpPath) {
|
20
21
|
return lodash_1.default.findKey(restResource.spec.methods, (method) => {
|
21
22
|
let methodType = method.method ? method.method.toUpperCase() : 'GET';
|
@@ -63,12 +64,49 @@ function resolveMethods(req, opts) {
|
|
63
64
|
providerMethod,
|
64
65
|
};
|
65
66
|
}
|
66
|
-
function
|
67
|
+
function resolveEntitiesSource(blockAsset) {
|
68
|
+
return blockAsset.data.spec?.entities?.source?.value || '';
|
69
|
+
}
|
70
|
+
const MASK_STRING = '*******';
|
71
|
+
function maskSimpleRequest(req, consumerMethod, entitiesSource) {
|
72
|
+
const maskedRequest = lodash_1.default.cloneDeep(req);
|
73
|
+
Object.entries(consumerMethod.arguments || {}).forEach(([key, value]) => {
|
74
|
+
if (value.transport === kaplang_core_1.HTTPTransport.BODY && typeof maskedRequest.body === 'string') {
|
75
|
+
try {
|
76
|
+
maskedRequest.body = (0, kaplang_core_1.maskSensitiveData)(maskedRequest.body, kaplang_core_1.DSLConverters.fromSchemaType(value), entitiesSource, MASK_STRING);
|
77
|
+
}
|
78
|
+
catch (error) {
|
79
|
+
// Ignore errors masking the request body
|
80
|
+
}
|
81
|
+
}
|
82
|
+
// TODO: Mask HEADER
|
83
|
+
// TODO: Mask QUERY
|
84
|
+
});
|
85
|
+
return maskedRequest;
|
86
|
+
}
|
87
|
+
function maskSimpleResponse(res, consumerMethod, entitiesSource) {
|
88
|
+
const maskedResponse = lodash_1.default.cloneDeep(res);
|
89
|
+
Object.entries(consumerMethod.arguments || {}).forEach(([key, value]) => {
|
90
|
+
// TODO: Mask HEADER
|
91
|
+
});
|
92
|
+
try {
|
93
|
+
if (typeof maskedResponse.body === 'string') {
|
94
|
+
maskedResponse.body = (0, kaplang_core_1.maskSensitiveData)(maskedResponse.body, kaplang_core_1.DSLConverters.fromSchemaType(consumerMethod.responseType), entitiesSource, MASK_STRING);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
catch (error) {
|
98
|
+
// Ignore errors masking the response body
|
99
|
+
}
|
100
|
+
return maskedResponse;
|
101
|
+
}
|
102
|
+
const proxyRestRequest = (req, res, opts) => {
|
67
103
|
if (lodash_1.default.isEmpty(opts.consumerResource.spec.methods) && lodash_1.default.isEmpty(opts.providerResource.spec.methods)) {
|
68
104
|
// If there are no methods defined, we assume the user controls the path and we just proxy the raw request
|
69
105
|
return (0, web_1.proxyHttpRequest)(req, res, opts);
|
70
106
|
}
|
71
|
-
|
107
|
+
const { consumerMethod, providerMethod } = resolveMethods(req, opts);
|
108
|
+
const consumerEntitiesSource = resolveEntitiesSource(opts.consumerBlockAsset);
|
109
|
+
const providerEntitiesSource = resolveEntitiesSource(opts.providerBlockAsset);
|
72
110
|
const consumerPathTemplate = (0, pathTemplateParser_1.pathTemplateParser)(consumerMethod.path);
|
73
111
|
const providerPathTemplate = (0, pathTemplateParser_1.pathTemplateParser)(providerMethod.path);
|
74
112
|
const pathVariables = consumerPathTemplate.parse(opts.consumerPath);
|
@@ -98,7 +136,7 @@ function proxyRestRequest(req, res, opts) {
|
|
98
136
|
body: req.stringBody,
|
99
137
|
headers: requestHeaders,
|
100
138
|
};
|
101
|
-
const traffic = networkManager_1.networkManager.addRequest(req.params.systemId, opts.connection, reqOpts, consumerMethod.id, providerMethod.id);
|
139
|
+
const traffic = networkManager_1.networkManager.addRequest(req.params.systemId, opts.connection, maskSimpleRequest(reqOpts, consumerMethod, consumerEntitiesSource), consumerMethod.id, providerMethod.id);
|
102
140
|
socketManager_1.socketManager.emit(traffic.connectionId, 'traffic_start', traffic);
|
103
141
|
(0, request_1.default)(reqOpts, function (err, response, responseBody) {
|
104
142
|
if (err) {
|
@@ -113,16 +151,17 @@ function proxyRestRequest(req, res, opts) {
|
|
113
151
|
delete responseHeaders['connection'];
|
114
152
|
res.set(responseHeaders);
|
115
153
|
res.status(response.statusCode);
|
116
|
-
|
154
|
+
const simpleResponse = {
|
117
155
|
code: response.statusCode,
|
118
156
|
headers: response.headers,
|
119
157
|
body: responseBody,
|
120
|
-
}
|
158
|
+
};
|
159
|
+
traffic.withResponse(maskSimpleResponse(simpleResponse, consumerMethod, consumerEntitiesSource));
|
121
160
|
socketManager_1.socketManager.emit(traffic.connectionId, 'traffic_end', traffic);
|
122
161
|
if (responseBody) {
|
123
162
|
res.write(responseBody);
|
124
163
|
}
|
125
164
|
res.end();
|
126
165
|
});
|
127
|
-
}
|
166
|
+
};
|
128
167
|
exports.proxyRestRequest = proxyRestRequest;
|
@@ -2,7 +2,5 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import {
|
6
|
-
|
7
|
-
import { StringBodyRequest } from '../../middleware/stringBody';
|
8
|
-
export declare function proxyHttpRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo): void;
|
5
|
+
import { ProxyRequestHandler } from '../../types';
|
6
|
+
export declare const proxyHttpRequest: ProxyRequestHandler;
|
@@ -13,7 +13,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
13
13
|
const networkManager_1 = require("../../networkManager");
|
14
14
|
const socketManager_1 = require("../../socketManager");
|
15
15
|
const qs_1 = require("qs");
|
16
|
-
|
16
|
+
const proxyHttpRequest = (req, res, opts) => {
|
17
17
|
const requestHeaders = lodash_1.default.clone(req.headers);
|
18
18
|
delete requestHeaders['content-length'];
|
19
19
|
delete requestHeaders['content-encoding'];
|
@@ -57,5 +57,5 @@ function proxyHttpRequest(req, res, opts) {
|
|
57
57
|
});
|
58
58
|
//We need to pipe the proxy response to the client response to handle sockets and event streams
|
59
59
|
proxyReq.pipe(res);
|
60
|
-
}
|
60
|
+
};
|
61
61
|
exports.proxyHttpRequest = proxyHttpRequest;
|
package/dist/cjs/src/types.d.ts
CHANGED
@@ -6,6 +6,7 @@ import express from 'express';
|
|
6
6
|
import { Connection, Resource } from '@kapeta/schemas';
|
7
7
|
import { StringBodyRequest } from './middleware/stringBody';
|
8
8
|
import { KapetaRequest } from './middleware/kapeta';
|
9
|
+
import { EnrichedAsset } from './assetManager';
|
9
10
|
export declare const KIND_RESOURCE_OPERATOR = "core/resource-type-operator";
|
10
11
|
export declare const KIND_BLOCK_TYPE = "core/block-type";
|
11
12
|
export declare const KIND_BLOCK_TYPE_OPERATOR = "core/block-type-operator";
|
@@ -80,9 +81,11 @@ export type ProxyRequestHandler = (req: StringBodyRequest, res: express.Response
|
|
80
81
|
export interface ProxyRequestInfo {
|
81
82
|
address: string;
|
82
83
|
connection: Connection;
|
83
|
-
providerResource: Resource;
|
84
|
-
consumerResource: Resource;
|
85
84
|
consumerPath: string;
|
85
|
+
consumerResource: Resource;
|
86
|
+
consumerBlockAsset: EnrichedAsset;
|
87
|
+
providerResource: Resource;
|
88
|
+
providerBlockAsset: EnrichedAsset;
|
86
89
|
}
|
87
90
|
export interface SimpleResponse {
|
88
91
|
code: number;
|
@@ -49,19 +49,19 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
|
|
49
49
|
});
|
50
50
|
return;
|
51
51
|
}
|
52
|
-
const
|
52
|
+
const consumerBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
|
53
53
|
return blockInstance.id.toLowerCase() === connection.consumer.blockId.toLowerCase();
|
54
54
|
});
|
55
|
-
if (!
|
55
|
+
if (!consumerBlockInstance) {
|
56
56
|
res.status(401).send({ error: `Block instance not found "${req.params.consumerInstanceId}` });
|
57
57
|
return;
|
58
58
|
}
|
59
|
-
const
|
60
|
-
if (!
|
61
|
-
res.status(401).send({ error: `Block asset not found "${
|
59
|
+
const consumerBlockAsset = await assetManager_1.assetManager.getAsset(consumerBlockInstance.block.ref);
|
60
|
+
if (!consumerBlockAsset) {
|
61
|
+
res.status(401).send({ error: `Block asset not found "${consumerBlockInstance.block.ref}` });
|
62
62
|
return;
|
63
63
|
}
|
64
|
-
const consumerResource = getResource(
|
64
|
+
const consumerResource = getResource(consumerBlockAsset.data.spec.consumers, req.params.consumerResourceName);
|
65
65
|
if (!consumerResource) {
|
66
66
|
res.status(401).send({
|
67
67
|
error: `Block resource not found "${req.params.consumerInstanceId}::${req.params.consumerResourceName}`,
|
@@ -69,19 +69,19 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
|
|
69
69
|
return;
|
70
70
|
}
|
71
71
|
const basePath = clusterService_1.clusterService.getProxyPath(req.params.systemId, req.params.consumerInstanceId, req.params.consumerResourceName, req.params.type);
|
72
|
-
const
|
72
|
+
const providerBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
|
73
73
|
return blockInstance.id.toLowerCase() === connection.provider.blockId.toLowerCase();
|
74
74
|
});
|
75
|
-
if (!
|
75
|
+
if (!providerBlockInstance) {
|
76
76
|
res.status(401).send({ error: `Block instance not found "${connection.provider.blockId}` });
|
77
77
|
return;
|
78
78
|
}
|
79
|
-
const
|
80
|
-
if (!
|
81
|
-
res.status(401).send({ error: `Block asset not found "${
|
79
|
+
const providerBlockAsset = await assetManager_1.assetManager.getAsset(providerBlockInstance.block.ref);
|
80
|
+
if (!providerBlockAsset) {
|
81
|
+
res.status(401).send({ error: `Block asset not found "${providerBlockInstance.block.ref}` });
|
82
82
|
return;
|
83
83
|
}
|
84
|
-
const providerResource = getResource(
|
84
|
+
const providerResource = getResource(providerBlockAsset.data.spec.providers, connection.provider.resourceName);
|
85
85
|
if (!providerResource) {
|
86
86
|
res.status(401).send({
|
87
87
|
error: `Block resource not found "${connection.provider.blockId}::${connection.provider.resourceName}`,
|
@@ -93,18 +93,18 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
|
|
93
93
|
while (address.endsWith('/')) {
|
94
94
|
address = address.substring(0, address.length - 1);
|
95
95
|
}
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
that is being called and identify the destination path from the connection.
|
100
|
-
*/
|
96
|
+
// Get the path the consumer requested. Note that this might not match the path the
|
97
|
+
// destination is expecting so we need to identify the method that is being called and
|
98
|
+
// identify the destination path from the connection.
|
101
99
|
const consumerPath = req.originalUrl.substring(basePath.length - 1);
|
102
100
|
typeHandler(req, res, {
|
103
|
-
consumerPath,
|
104
101
|
address,
|
102
|
+
connection,
|
103
|
+
consumerPath,
|
105
104
|
consumerResource,
|
105
|
+
consumerBlockAsset,
|
106
106
|
providerResource,
|
107
|
-
|
107
|
+
providerBlockAsset,
|
108
108
|
});
|
109
109
|
}
|
110
110
|
catch (err) {
|
@@ -2,9 +2,7 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import {
|
6
|
-
import { ProxyRequestInfo } from '../../types';
|
7
|
-
import { StringBodyRequest } from '../../middleware/stringBody';
|
5
|
+
import { ProxyRequestHandler } from '../../types';
|
8
6
|
import { Resource } from '@kapeta/schemas';
|
9
7
|
export declare function getRestMethodId(restResource: Resource, httpMethod: string, httpPath: string): string | undefined;
|
10
|
-
export declare
|
8
|
+
export declare const proxyRestRequest: ProxyRequestHandler;
|
@@ -16,6 +16,7 @@ const networkManager_1 = require("../../networkManager");
|
|
16
16
|
const socketManager_1 = require("../../socketManager");
|
17
17
|
const qs_1 = require("qs");
|
18
18
|
const web_1 = require("./web");
|
19
|
+
const kaplang_core_1 = require("@kapeta/kaplang-core");
|
19
20
|
function getRestMethodId(restResource, httpMethod, httpPath) {
|
20
21
|
return lodash_1.default.findKey(restResource.spec.methods, (method) => {
|
21
22
|
let methodType = method.method ? method.method.toUpperCase() : 'GET';
|
@@ -63,12 +64,49 @@ function resolveMethods(req, opts) {
|
|
63
64
|
providerMethod,
|
64
65
|
};
|
65
66
|
}
|
66
|
-
function
|
67
|
+
function resolveEntitiesSource(blockAsset) {
|
68
|
+
return blockAsset.data.spec?.entities?.source?.value || '';
|
69
|
+
}
|
70
|
+
const MASK_STRING = '*******';
|
71
|
+
function maskSimpleRequest(req, consumerMethod, entitiesSource) {
|
72
|
+
const maskedRequest = lodash_1.default.cloneDeep(req);
|
73
|
+
Object.entries(consumerMethod.arguments || {}).forEach(([key, value]) => {
|
74
|
+
if (value.transport === kaplang_core_1.HTTPTransport.BODY && typeof maskedRequest.body === 'string') {
|
75
|
+
try {
|
76
|
+
maskedRequest.body = (0, kaplang_core_1.maskSensitiveData)(maskedRequest.body, kaplang_core_1.DSLConverters.fromSchemaType(value), entitiesSource, MASK_STRING);
|
77
|
+
}
|
78
|
+
catch (error) {
|
79
|
+
// Ignore errors masking the request body
|
80
|
+
}
|
81
|
+
}
|
82
|
+
// TODO: Mask HEADER
|
83
|
+
// TODO: Mask QUERY
|
84
|
+
});
|
85
|
+
return maskedRequest;
|
86
|
+
}
|
87
|
+
function maskSimpleResponse(res, consumerMethod, entitiesSource) {
|
88
|
+
const maskedResponse = lodash_1.default.cloneDeep(res);
|
89
|
+
Object.entries(consumerMethod.arguments || {}).forEach(([key, value]) => {
|
90
|
+
// TODO: Mask HEADER
|
91
|
+
});
|
92
|
+
try {
|
93
|
+
if (typeof maskedResponse.body === 'string') {
|
94
|
+
maskedResponse.body = (0, kaplang_core_1.maskSensitiveData)(maskedResponse.body, kaplang_core_1.DSLConverters.fromSchemaType(consumerMethod.responseType), entitiesSource, MASK_STRING);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
catch (error) {
|
98
|
+
// Ignore errors masking the response body
|
99
|
+
}
|
100
|
+
return maskedResponse;
|
101
|
+
}
|
102
|
+
const proxyRestRequest = (req, res, opts) => {
|
67
103
|
if (lodash_1.default.isEmpty(opts.consumerResource.spec.methods) && lodash_1.default.isEmpty(opts.providerResource.spec.methods)) {
|
68
104
|
// If there are no methods defined, we assume the user controls the path and we just proxy the raw request
|
69
105
|
return (0, web_1.proxyHttpRequest)(req, res, opts);
|
70
106
|
}
|
71
|
-
|
107
|
+
const { consumerMethod, providerMethod } = resolveMethods(req, opts);
|
108
|
+
const consumerEntitiesSource = resolveEntitiesSource(opts.consumerBlockAsset);
|
109
|
+
const providerEntitiesSource = resolveEntitiesSource(opts.providerBlockAsset);
|
72
110
|
const consumerPathTemplate = (0, pathTemplateParser_1.pathTemplateParser)(consumerMethod.path);
|
73
111
|
const providerPathTemplate = (0, pathTemplateParser_1.pathTemplateParser)(providerMethod.path);
|
74
112
|
const pathVariables = consumerPathTemplate.parse(opts.consumerPath);
|
@@ -98,7 +136,7 @@ function proxyRestRequest(req, res, opts) {
|
|
98
136
|
body: req.stringBody,
|
99
137
|
headers: requestHeaders,
|
100
138
|
};
|
101
|
-
const traffic = networkManager_1.networkManager.addRequest(req.params.systemId, opts.connection, reqOpts, consumerMethod.id, providerMethod.id);
|
139
|
+
const traffic = networkManager_1.networkManager.addRequest(req.params.systemId, opts.connection, maskSimpleRequest(reqOpts, consumerMethod, consumerEntitiesSource), consumerMethod.id, providerMethod.id);
|
102
140
|
socketManager_1.socketManager.emit(traffic.connectionId, 'traffic_start', traffic);
|
103
141
|
(0, request_1.default)(reqOpts, function (err, response, responseBody) {
|
104
142
|
if (err) {
|
@@ -113,16 +151,17 @@ function proxyRestRequest(req, res, opts) {
|
|
113
151
|
delete responseHeaders['connection'];
|
114
152
|
res.set(responseHeaders);
|
115
153
|
res.status(response.statusCode);
|
116
|
-
|
154
|
+
const simpleResponse = {
|
117
155
|
code: response.statusCode,
|
118
156
|
headers: response.headers,
|
119
157
|
body: responseBody,
|
120
|
-
}
|
158
|
+
};
|
159
|
+
traffic.withResponse(maskSimpleResponse(simpleResponse, consumerMethod, consumerEntitiesSource));
|
121
160
|
socketManager_1.socketManager.emit(traffic.connectionId, 'traffic_end', traffic);
|
122
161
|
if (responseBody) {
|
123
162
|
res.write(responseBody);
|
124
163
|
}
|
125
164
|
res.end();
|
126
165
|
});
|
127
|
-
}
|
166
|
+
};
|
128
167
|
exports.proxyRestRequest = proxyRestRequest;
|
@@ -2,7 +2,5 @@
|
|
2
2
|
* Copyright 2023 Kapeta Inc.
|
3
3
|
* SPDX-License-Identifier: BUSL-1.1
|
4
4
|
*/
|
5
|
-
import {
|
6
|
-
|
7
|
-
import { StringBodyRequest } from '../../middleware/stringBody';
|
8
|
-
export declare function proxyHttpRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo): void;
|
5
|
+
import { ProxyRequestHandler } from '../../types';
|
6
|
+
export declare const proxyHttpRequest: ProxyRequestHandler;
|
@@ -13,7 +13,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
13
13
|
const networkManager_1 = require("../../networkManager");
|
14
14
|
const socketManager_1 = require("../../socketManager");
|
15
15
|
const qs_1 = require("qs");
|
16
|
-
|
16
|
+
const proxyHttpRequest = (req, res, opts) => {
|
17
17
|
const requestHeaders = lodash_1.default.clone(req.headers);
|
18
18
|
delete requestHeaders['content-length'];
|
19
19
|
delete requestHeaders['content-encoding'];
|
@@ -57,5 +57,5 @@ function proxyHttpRequest(req, res, opts) {
|
|
57
57
|
});
|
58
58
|
//We need to pipe the proxy response to the client response to handle sockets and event streams
|
59
59
|
proxyReq.pipe(res);
|
60
|
-
}
|
60
|
+
};
|
61
61
|
exports.proxyHttpRequest = proxyHttpRequest;
|
package/dist/esm/src/types.d.ts
CHANGED
@@ -6,6 +6,7 @@ import express from 'express';
|
|
6
6
|
import { Connection, Resource } from '@kapeta/schemas';
|
7
7
|
import { StringBodyRequest } from './middleware/stringBody';
|
8
8
|
import { KapetaRequest } from './middleware/kapeta';
|
9
|
+
import { EnrichedAsset } from './assetManager';
|
9
10
|
export declare const KIND_RESOURCE_OPERATOR = "core/resource-type-operator";
|
10
11
|
export declare const KIND_BLOCK_TYPE = "core/block-type";
|
11
12
|
export declare const KIND_BLOCK_TYPE_OPERATOR = "core/block-type-operator";
|
@@ -80,9 +81,11 @@ export type ProxyRequestHandler = (req: StringBodyRequest, res: express.Response
|
|
80
81
|
export interface ProxyRequestInfo {
|
81
82
|
address: string;
|
82
83
|
connection: Connection;
|
83
|
-
providerResource: Resource;
|
84
|
-
consumerResource: Resource;
|
85
84
|
consumerPath: string;
|
85
|
+
consumerResource: Resource;
|
86
|
+
consumerBlockAsset: EnrichedAsset;
|
87
|
+
providerResource: Resource;
|
88
|
+
providerBlockAsset: EnrichedAsset;
|
86
89
|
}
|
87
90
|
export interface SimpleResponse {
|
88
91
|
code: number;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@kapeta/local-cluster-service",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.44.0",
|
4
4
|
"description": "Manages configuration, ports and service discovery for locally running Kapeta systems",
|
5
5
|
"type": "commonjs",
|
6
6
|
"exports": {
|
@@ -43,13 +43,16 @@
|
|
43
43
|
"build:cjs": "tsc --outDir ./dist/cjs && echo '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json",
|
44
44
|
"build": "npm run clean && npm run build:esm && npm run build:cjs",
|
45
45
|
"format": "prettier --write .",
|
46
|
-
"lint": "tsc
|
46
|
+
"lint": "npm run lint:tsc && npm run lint:eslint",
|
47
|
+
"lint:tsc": "tsc --noEmit",
|
48
|
+
"lint:eslint": "eslint src/**/*.ts",
|
47
49
|
"prepublishOnly": "npm run build"
|
48
50
|
},
|
49
51
|
"homepage": "https://github.com/kapetacom/local-cluster-service#readme",
|
50
52
|
"dependencies": {
|
51
53
|
"@kapeta/codegen": "^1.3.0",
|
52
54
|
"@kapeta/config-mapper": "^1.2.1",
|
55
|
+
"@kapeta/kaplang-core": "^1.14.2",
|
53
56
|
"@kapeta/local-cluster-config": "^0.4.2",
|
54
57
|
"@kapeta/nodejs-api-client": ">=0.2.0 <2",
|
55
58
|
"@kapeta/nodejs-process": "^1.2.0",
|
@@ -57,8 +60,8 @@
|
|
57
60
|
"@kapeta/nodejs-utils": "<2",
|
58
61
|
"@kapeta/schemas": "^3.6.0",
|
59
62
|
"@kapeta/sdk-config": "^2.1.1",
|
60
|
-
"@kapeta/web-microfrontend": "^1.2.
|
61
|
-
"@sentry/node": "^7.
|
63
|
+
"@kapeta/web-microfrontend": "^1.2.11",
|
64
|
+
"@sentry/node": "^7.114.0",
|
62
65
|
"@types/dockerode": "^3.3.19",
|
63
66
|
"@types/stream-json": "^1.7.3",
|
64
67
|
"async-lock": "^1.4.0",
|
package/src/proxy/routes.ts
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
*/
|
5
5
|
|
6
6
|
import Router from 'express-promise-router';
|
7
|
-
import {
|
7
|
+
import { Response } from 'express';
|
8
8
|
import { Resource } from '@kapeta/schemas';
|
9
9
|
import { proxyRestRequest } from './types/rest';
|
10
10
|
import { proxyHttpRequest } from './types/web';
|
@@ -61,23 +61,26 @@ router.all(
|
|
61
61
|
return;
|
62
62
|
}
|
63
63
|
|
64
|
-
const
|
64
|
+
const consumerBlockInstance = _.find(plan.spec.blocks, (blockInstance) => {
|
65
65
|
return blockInstance.id.toLowerCase() === connection.consumer.blockId.toLowerCase();
|
66
66
|
});
|
67
67
|
|
68
|
-
if (!
|
68
|
+
if (!consumerBlockInstance) {
|
69
69
|
res.status(401).send({ error: `Block instance not found "${req.params.consumerInstanceId}` });
|
70
70
|
return;
|
71
71
|
}
|
72
72
|
|
73
|
-
const
|
73
|
+
const consumerBlockAsset = await assetManager.getAsset(consumerBlockInstance.block.ref);
|
74
74
|
|
75
|
-
if (!
|
76
|
-
res.status(401).send({ error: `Block asset not found "${
|
75
|
+
if (!consumerBlockAsset) {
|
76
|
+
res.status(401).send({ error: `Block asset not found "${consumerBlockInstance.block.ref}` });
|
77
77
|
return;
|
78
78
|
}
|
79
79
|
|
80
|
-
const consumerResource = getResource(
|
80
|
+
const consumerResource = getResource(
|
81
|
+
consumerBlockAsset.data.spec.consumers,
|
82
|
+
req.params.consumerResourceName
|
83
|
+
);
|
81
84
|
|
82
85
|
if (!consumerResource) {
|
83
86
|
res.status(401).send({
|
@@ -93,23 +96,26 @@ router.all(
|
|
93
96
|
req.params.type
|
94
97
|
);
|
95
98
|
|
96
|
-
const
|
99
|
+
const providerBlockInstance = _.find(plan.spec.blocks, (blockInstance) => {
|
97
100
|
return blockInstance.id.toLowerCase() === connection.provider.blockId.toLowerCase();
|
98
101
|
});
|
99
102
|
|
100
|
-
if (!
|
103
|
+
if (!providerBlockInstance) {
|
101
104
|
res.status(401).send({ error: `Block instance not found "${connection.provider.blockId}` });
|
102
105
|
return;
|
103
106
|
}
|
104
107
|
|
105
|
-
const
|
108
|
+
const providerBlockAsset = await assetManager.getAsset(providerBlockInstance.block.ref);
|
106
109
|
|
107
|
-
if (!
|
108
|
-
res.status(401).send({ error: `Block asset not found "${
|
110
|
+
if (!providerBlockAsset) {
|
111
|
+
res.status(401).send({ error: `Block asset not found "${providerBlockInstance.block.ref}` });
|
109
112
|
return;
|
110
113
|
}
|
111
114
|
|
112
|
-
const providerResource = getResource(
|
115
|
+
const providerResource = getResource(
|
116
|
+
providerBlockAsset.data.spec.providers,
|
117
|
+
connection.provider.resourceName
|
118
|
+
);
|
113
119
|
|
114
120
|
if (!providerResource) {
|
115
121
|
res.status(401).send({
|
@@ -129,19 +135,19 @@ router.all(
|
|
129
135
|
address = address.substring(0, address.length - 1);
|
130
136
|
}
|
131
137
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
that is being called and identify the destination path from the connection.
|
136
|
-
*/
|
138
|
+
// Get the path the consumer requested. Note that this might not match the path the
|
139
|
+
// destination is expecting so we need to identify the method that is being called and
|
140
|
+
// identify the destination path from the connection.
|
137
141
|
const consumerPath = req.originalUrl.substring(basePath.length - 1);
|
138
142
|
|
139
143
|
typeHandler(req, res, {
|
140
|
-
consumerPath,
|
141
144
|
address,
|
145
|
+
connection,
|
146
|
+
consumerPath,
|
142
147
|
consumerResource,
|
148
|
+
consumerBlockAsset,
|
143
149
|
providerResource,
|
144
|
-
|
150
|
+
providerBlockAsset,
|
145
151
|
});
|
146
152
|
} catch (err: any) {
|
147
153
|
console.warn('Failed to process proxy request', err);
|
package/src/proxy/types/rest.ts
CHANGED
@@ -10,12 +10,13 @@ import { pathTemplateParser } from '../../utils/pathTemplateParser';
|
|
10
10
|
import { networkManager } from '../../networkManager';
|
11
11
|
|
12
12
|
import { socketManager } from '../../socketManager';
|
13
|
-
import { Request
|
14
|
-
import { ProxyRequestInfo, SimpleRequest, StringMap } from '../../types';
|
15
|
-
import { StringBodyRequest } from '../../middleware/stringBody';
|
13
|
+
import { Request } from 'express';
|
14
|
+
import { ProxyRequestHandler, ProxyRequestInfo, SimpleRequest, SimpleResponse, StringMap } from '../../types';
|
16
15
|
import { Resource } from '@kapeta/schemas';
|
17
16
|
import { stringify } from 'qs';
|
18
17
|
import { proxyHttpRequest } from './web';
|
18
|
+
import { DSLConverters, HTTPTransport, RESTMethod, maskSensitiveData } from '@kapeta/kaplang-core';
|
19
|
+
import { EnrichedAsset } from '../../assetManager';
|
19
20
|
|
20
21
|
export function getRestMethodId(restResource: Resource, httpMethod: string, httpPath: string) {
|
21
22
|
return _.findKey(restResource.spec.methods, (method) => {
|
@@ -37,6 +38,10 @@ export function getRestMethodId(restResource: Resource, httpMethod: string, http
|
|
37
38
|
});
|
38
39
|
}
|
39
40
|
|
41
|
+
interface RESTMethodWithId extends RESTMethod {
|
42
|
+
id: string;
|
43
|
+
}
|
44
|
+
|
40
45
|
/**
|
41
46
|
*
|
42
47
|
* @param req {Request}
|
@@ -52,7 +57,7 @@ function resolveMethods(req: Request, opts: ProxyRequestInfo) {
|
|
52
57
|
);
|
53
58
|
}
|
54
59
|
|
55
|
-
const consumerMethod = _.cloneDeep(opts.consumerResource.spec.methods[consumerMethodId]);
|
60
|
+
const consumerMethod = _.cloneDeep(opts.consumerResource.spec.methods[consumerMethodId] as RESTMethodWithId);
|
56
61
|
|
57
62
|
if (!consumerMethod) {
|
58
63
|
throw new Error(
|
@@ -70,7 +75,7 @@ function resolveMethods(req: Request, opts: ProxyRequestInfo) {
|
|
70
75
|
throw new Error(`Connection contained no mapping for consumer method "${consumerMethodId}`);
|
71
76
|
}
|
72
77
|
|
73
|
-
const providerMethod = _.cloneDeep(opts.providerResource.spec.methods[providerMethodId]);
|
78
|
+
const providerMethod = _.cloneDeep(opts.providerResource.spec.methods[providerMethodId] as RESTMethodWithId);
|
74
79
|
|
75
80
|
if (!providerMethod) {
|
76
81
|
throw new Error(
|
@@ -86,12 +91,67 @@ function resolveMethods(req: Request, opts: ProxyRequestInfo) {
|
|
86
91
|
};
|
87
92
|
}
|
88
93
|
|
89
|
-
|
94
|
+
function resolveEntitiesSource(blockAsset: EnrichedAsset) {
|
95
|
+
return (blockAsset.data.spec?.entities?.source?.value as string) || '';
|
96
|
+
}
|
97
|
+
|
98
|
+
const MASK_STRING = '*******';
|
99
|
+
|
100
|
+
function maskSimpleRequest(req: SimpleRequest, consumerMethod: RESTMethod, entitiesSource: string) {
|
101
|
+
const maskedRequest = _.cloneDeep(req);
|
102
|
+
|
103
|
+
Object.entries(consumerMethod.arguments || {}).forEach(([key, value]) => {
|
104
|
+
if (value.transport === HTTPTransport.BODY && typeof maskedRequest.body === 'string') {
|
105
|
+
try {
|
106
|
+
maskedRequest.body = maskSensitiveData(
|
107
|
+
maskedRequest.body,
|
108
|
+
DSLConverters.fromSchemaType(value),
|
109
|
+
entitiesSource,
|
110
|
+
MASK_STRING
|
111
|
+
);
|
112
|
+
} catch (error) {
|
113
|
+
// Ignore errors masking the request body
|
114
|
+
}
|
115
|
+
}
|
116
|
+
// TODO: Mask HEADER
|
117
|
+
// TODO: Mask QUERY
|
118
|
+
});
|
119
|
+
|
120
|
+
return maskedRequest;
|
121
|
+
}
|
122
|
+
|
123
|
+
function maskSimpleResponse(res: SimpleResponse, consumerMethod: RESTMethod, entitiesSource: string) {
|
124
|
+
const maskedResponse = _.cloneDeep(res);
|
125
|
+
|
126
|
+
Object.entries(consumerMethod.arguments || {}).forEach(([key, value]) => {
|
127
|
+
// TODO: Mask HEADER
|
128
|
+
});
|
129
|
+
|
130
|
+
try {
|
131
|
+
if (typeof maskedResponse.body === 'string') {
|
132
|
+
maskedResponse.body = maskSensitiveData(
|
133
|
+
maskedResponse.body,
|
134
|
+
DSLConverters.fromSchemaType(consumerMethod.responseType),
|
135
|
+
entitiesSource,
|
136
|
+
MASK_STRING
|
137
|
+
);
|
138
|
+
}
|
139
|
+
} catch (error) {
|
140
|
+
// Ignore errors masking the response body
|
141
|
+
}
|
142
|
+
|
143
|
+
return maskedResponse;
|
144
|
+
}
|
145
|
+
|
146
|
+
export const proxyRestRequest: ProxyRequestHandler = (req, res, opts) => {
|
90
147
|
if (_.isEmpty(opts.consumerResource.spec.methods) && _.isEmpty(opts.providerResource.spec.methods)) {
|
91
148
|
// If there are no methods defined, we assume the user controls the path and we just proxy the raw request
|
92
149
|
return proxyHttpRequest(req, res, opts);
|
93
150
|
}
|
94
|
-
|
151
|
+
const { consumerMethod, providerMethod } = resolveMethods(req, opts);
|
152
|
+
|
153
|
+
const consumerEntitiesSource = resolveEntitiesSource(opts.consumerBlockAsset);
|
154
|
+
const providerEntitiesSource = resolveEntitiesSource(opts.providerBlockAsset);
|
95
155
|
|
96
156
|
const consumerPathTemplate = pathTemplateParser(consumerMethod.path);
|
97
157
|
const providerPathTemplate = pathTemplateParser(providerMethod.path);
|
@@ -134,7 +194,7 @@ export function proxyRestRequest(req: StringBodyRequest, res: Response, opts: Pr
|
|
134
194
|
const traffic = networkManager.addRequest(
|
135
195
|
req.params.systemId,
|
136
196
|
opts.connection,
|
137
|
-
reqOpts,
|
197
|
+
maskSimpleRequest(reqOpts, consumerMethod, consumerEntitiesSource),
|
138
198
|
consumerMethod.id,
|
139
199
|
providerMethod.id
|
140
200
|
);
|
@@ -160,11 +220,13 @@ export function proxyRestRequest(req: StringBodyRequest, res: Response, opts: Pr
|
|
160
220
|
|
161
221
|
res.status(response.statusCode);
|
162
222
|
|
163
|
-
|
223
|
+
const simpleResponse = {
|
164
224
|
code: response.statusCode,
|
165
225
|
headers: response.headers as StringMap,
|
166
226
|
body: responseBody,
|
167
|
-
}
|
227
|
+
};
|
228
|
+
|
229
|
+
traffic.withResponse(maskSimpleResponse(simpleResponse, consumerMethod, consumerEntitiesSource));
|
168
230
|
|
169
231
|
socketManager.emit(traffic.connectionId, 'traffic_end', traffic);
|
170
232
|
|
@@ -174,4 +236,4 @@ export function proxyRestRequest(req: StringBodyRequest, res: Response, opts: Pr
|
|
174
236
|
|
175
237
|
res.end();
|
176
238
|
});
|
177
|
-
}
|
239
|
+
};
|
package/src/proxy/types/web.ts
CHANGED
@@ -7,12 +7,10 @@ import request from 'request';
|
|
7
7
|
import _ from 'lodash';
|
8
8
|
import { networkManager } from '../../networkManager';
|
9
9
|
import { socketManager } from '../../socketManager';
|
10
|
-
import {
|
11
|
-
import { ProxyRequestInfo, SimpleRequest, StringMap } from '../../types';
|
12
|
-
import { StringBodyRequest } from '../../middleware/stringBody';
|
10
|
+
import { ProxyRequestHandler, SimpleRequest, StringMap } from '../../types';
|
13
11
|
import { stringify } from 'qs';
|
14
12
|
|
15
|
-
export
|
13
|
+
export const proxyHttpRequest: ProxyRequestHandler = (req, res, opts) => {
|
16
14
|
const requestHeaders = _.clone(req.headers);
|
17
15
|
|
18
16
|
delete requestHeaders['content-length'];
|
@@ -67,4 +65,4 @@ export function proxyHttpRequest(req: StringBodyRequest, res: Response, opts: Pr
|
|
67
65
|
|
68
66
|
//We need to pipe the proxy response to the client response to handle sockets and event streams
|
69
67
|
proxyReq.pipe(res);
|
70
|
-
}
|
68
|
+
};
|
package/src/types.ts
CHANGED
@@ -7,6 +7,7 @@ import express from 'express';
|
|
7
7
|
import { Connection, Resource } from '@kapeta/schemas';
|
8
8
|
import { StringBodyRequest } from './middleware/stringBody';
|
9
9
|
import { KapetaRequest } from './middleware/kapeta';
|
10
|
+
import { EnrichedAsset } from './assetManager';
|
10
11
|
|
11
12
|
export const KIND_RESOURCE_OPERATOR = 'core/resource-type-operator';
|
12
13
|
export const KIND_BLOCK_TYPE = 'core/block-type';
|
@@ -90,9 +91,11 @@ export type ProxyRequestHandler = (req: StringBodyRequest, res: express.Response
|
|
90
91
|
export interface ProxyRequestInfo {
|
91
92
|
address: string;
|
92
93
|
connection: Connection;
|
93
|
-
providerResource: Resource;
|
94
|
-
consumerResource: Resource;
|
95
94
|
consumerPath: string;
|
95
|
+
consumerResource: Resource;
|
96
|
+
consumerBlockAsset: EnrichedAsset;
|
97
|
+
providerResource: Resource;
|
98
|
+
providerBlockAsset: EnrichedAsset;
|
96
99
|
}
|
97
100
|
|
98
101
|
export interface SimpleResponse {
|