@kapeta/local-cluster-service 0.43.3 → 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 CHANGED
@@ -1,3 +1,16 @@
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
+
1
14
  ## [0.43.3](https://github.com/kapetacom/local-cluster-service/compare/v0.43.2...v0.43.3) (2024-05-08)
2
15
 
3
16
 
@@ -49,19 +49,19 @@ router.all('/:systemId/:consumerInstanceId/:consumerResourceName/:type/*', async
49
49
  });
50
50
  return;
51
51
  }
52
- const toBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
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 (!toBlockInstance) {
55
+ if (!consumerBlockInstance) {
56
56
  res.status(401).send({ error: `Block instance not found "${req.params.consumerInstanceId}` });
57
57
  return;
58
58
  }
59
- const toBlockAsset = await assetManager_1.assetManager.getAsset(toBlockInstance.block.ref);
60
- if (!toBlockAsset) {
61
- res.status(401).send({ error: `Block asset not found "${toBlockInstance.block.ref}` });
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(toBlockAsset.data.spec.consumers, req.params.consumerResourceName);
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 fromBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
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 (!fromBlockInstance) {
75
+ if (!providerBlockInstance) {
76
76
  res.status(401).send({ error: `Block instance not found "${connection.provider.blockId}` });
77
77
  return;
78
78
  }
79
- const fromBlockAsset = await assetManager_1.assetManager.getAsset(fromBlockInstance.block.ref);
80
- if (!fromBlockAsset) {
81
- res.status(401).send({ error: `Block asset not found "${fromBlockInstance.block.ref}` });
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(fromBlockAsset.data.spec.providers, connection.provider.resourceName);
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
- Get the path the consumer requested.
98
- Note that this might not match the path the destination is expecting so we need to identify the method
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
- connection,
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 { Response } from 'express';
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 function proxyRestRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo): void;
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 proxyRestRequest(req, res, opts) {
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
- let { consumerMethod, providerMethod } = resolveMethods(req, opts);
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
- traffic.withResponse({
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 { Response } from 'express';
6
- import { ProxyRequestInfo } from '../../types';
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
- function proxyHttpRequest(req, res, opts) {
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;
@@ -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 toBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
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 (!toBlockInstance) {
55
+ if (!consumerBlockInstance) {
56
56
  res.status(401).send({ error: `Block instance not found "${req.params.consumerInstanceId}` });
57
57
  return;
58
58
  }
59
- const toBlockAsset = await assetManager_1.assetManager.getAsset(toBlockInstance.block.ref);
60
- if (!toBlockAsset) {
61
- res.status(401).send({ error: `Block asset not found "${toBlockInstance.block.ref}` });
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(toBlockAsset.data.spec.consumers, req.params.consumerResourceName);
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 fromBlockInstance = lodash_1.default.find(plan.spec.blocks, (blockInstance) => {
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 (!fromBlockInstance) {
75
+ if (!providerBlockInstance) {
76
76
  res.status(401).send({ error: `Block instance not found "${connection.provider.blockId}` });
77
77
  return;
78
78
  }
79
- const fromBlockAsset = await assetManager_1.assetManager.getAsset(fromBlockInstance.block.ref);
80
- if (!fromBlockAsset) {
81
- res.status(401).send({ error: `Block asset not found "${fromBlockInstance.block.ref}` });
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(fromBlockAsset.data.spec.providers, connection.provider.resourceName);
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
- Get the path the consumer requested.
98
- Note that this might not match the path the destination is expecting so we need to identify the method
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
- connection,
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 { Response } from 'express';
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 function proxyRestRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo): void;
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 proxyRestRequest(req, res, opts) {
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
- let { consumerMethod, providerMethod } = resolveMethods(req, opts);
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
- traffic.withResponse({
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 { Response } from 'express';
6
- import { ProxyRequestInfo } from '../../types';
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
- function proxyHttpRequest(req, res, opts) {
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;
@@ -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.43.3",
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 --noEmit && eslint src/**/*.ts",
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",
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import Router from 'express-promise-router';
7
- import { Request, Response } from 'express';
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 toBlockInstance = _.find(plan.spec.blocks, (blockInstance) => {
64
+ const consumerBlockInstance = _.find(plan.spec.blocks, (blockInstance) => {
65
65
  return blockInstance.id.toLowerCase() === connection.consumer.blockId.toLowerCase();
66
66
  });
67
67
 
68
- if (!toBlockInstance) {
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 toBlockAsset = await assetManager.getAsset(toBlockInstance.block.ref);
73
+ const consumerBlockAsset = await assetManager.getAsset(consumerBlockInstance.block.ref);
74
74
 
75
- if (!toBlockAsset) {
76
- res.status(401).send({ error: `Block asset not found "${toBlockInstance.block.ref}` });
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(toBlockAsset.data.spec.consumers, req.params.consumerResourceName);
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 fromBlockInstance = _.find(plan.spec.blocks, (blockInstance) => {
99
+ const providerBlockInstance = _.find(plan.spec.blocks, (blockInstance) => {
97
100
  return blockInstance.id.toLowerCase() === connection.provider.blockId.toLowerCase();
98
101
  });
99
102
 
100
- if (!fromBlockInstance) {
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 fromBlockAsset = await assetManager.getAsset(fromBlockInstance.block.ref);
108
+ const providerBlockAsset = await assetManager.getAsset(providerBlockInstance.block.ref);
106
109
 
107
- if (!fromBlockAsset) {
108
- res.status(401).send({ error: `Block asset not found "${fromBlockInstance.block.ref}` });
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(fromBlockAsset.data.spec.providers, connection.provider.resourceName);
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
- Get the path the consumer requested.
134
- Note that this might not match the path the destination is expecting so we need to identify the method
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
- connection,
150
+ providerBlockAsset,
145
151
  });
146
152
  } catch (err: any) {
147
153
  console.warn('Failed to process proxy request', err);
@@ -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, Response } from 'express';
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
- export function proxyRestRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo) {
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
- let { consumerMethod, providerMethod } = resolveMethods(req, opts);
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
- traffic.withResponse({
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
+ };
@@ -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 { Response } from 'express';
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 function proxyHttpRequest(req: StringBodyRequest, res: Response, opts: ProxyRequestInfo) {
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 {