@cumulus/es-client 20.1.3-alpha.2 → 20.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +8 -8
  2. package/search.js +108 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cumulus/es-client",
3
- "version": "20.1.3-alpha.2",
3
+ "version": "20.2.1",
4
4
  "description": "Utilities for working with Elasticsearch",
5
5
  "keywords": [
6
6
  "CUMULUS",
@@ -34,10 +34,10 @@
34
34
  "license": "Apache-2.0",
35
35
  "dependencies": {
36
36
  "@aws-sdk/credential-providers": "^3.621.0",
37
- "@cumulus/common": "20.1.3-alpha.2",
38
- "@cumulus/errors": "20.1.3-alpha.2",
39
- "@cumulus/logger": "20.1.3-alpha.2",
40
- "@cumulus/message": "20.1.3-alpha.2",
37
+ "@cumulus/common": "20.2.1",
38
+ "@cumulus/errors": "20.2.1",
39
+ "@cumulus/logger": "20.2.1",
40
+ "@cumulus/message": "20.2.1",
41
41
  "@elastic/elasticsearch": "^5.6.20",
42
42
  "aws4": "^1.12.0",
43
43
  "lodash": "~4.17.21",
@@ -45,9 +45,9 @@
45
45
  "p-limit": "^1.2.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@cumulus/aws-client": "20.1.3-alpha.2",
49
- "@cumulus/test-data": "20.1.3-alpha.2",
48
+ "@cumulus/aws-client": "20.2.1",
49
+ "@cumulus/test-data": "20.2.1",
50
50
  "p-each-series": "^2.1.0"
51
51
  },
52
- "gitHead": "c859f53331e38f38652e5ef124d4c79ceda32757"
52
+ "gitHead": "8aeab83b5fd9f7e1818d4631e1e36535e443ae90"
53
53
  }
package/search.js CHANGED
@@ -9,6 +9,8 @@
9
9
 
10
10
  const has = require('lodash/has');
11
11
  const omit = require('lodash/omit');
12
+ const isString = require('lodash/isString');
13
+ const isError = require('lodash/isError');
12
14
  const { fromNodeProviderChain } = require('@aws-sdk/credential-providers');
13
15
  const elasticsearch = require('@elastic/elasticsearch');
14
16
 
@@ -29,6 +31,68 @@ const multipleRecordFoundString = 'More than one record was found!';
29
31
  const recordNotFoundString = 'Record not found';
30
32
  const logger = new Logger({ sender: '@cumulus/es-client/search' });
31
33
 
34
+ class HttpError extends Error {
35
+ constructor(statusCode, message) {
36
+ super(message);
37
+ this.statusCode = statusCode;
38
+ this.name = 'HttpError';
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Sanitizes sensitive data in error messages or logs
44
+ *
45
+ * @param {Error|string} input - The error object or message to sanitize
46
+ * @returns {Error|string} - Sanitized error or message
47
+ */
48
+ const sanitizeSensitive = (input) => {
49
+ const sensitiveFields = [
50
+ `${process.env.METRICS_ES_USER}:${process.env.METRICS_ES_PASS}`,
51
+ ].filter(Boolean);
52
+
53
+ let message = isString(input) ? input : input.message || input.toString();
54
+
55
+ const escapeRegExp = (string) => string.replace(/[$()*+.?[\\\]^{|}-]/g, '\\$&');
56
+
57
+ sensitiveFields.forEach((field) => {
58
+ if (field) {
59
+ const pattern = `(^|\\s|[^a-zA-Z0-9_])(${escapeRegExp(field)})($|\\s|[^a-zA-Z0-9_])`;
60
+ message = message.replace(new RegExp(pattern, 'g'), '$1*****$3');
61
+ }
62
+ });
63
+
64
+ if (isString(input)) {
65
+ return message;
66
+ }
67
+
68
+ const sanitizedError = new Error(message);
69
+ sanitizedError.stack = input.stack;
70
+ return sanitizedError;
71
+ };
72
+
73
+ /**
74
+ * Custom logger for elasticsearch client to sanitize sensitive data
75
+ */
76
+ class EsCustomLogger {
77
+ constructor() {
78
+ this.levels = ['error', 'warning']; // Log only errors and warnings
79
+ }
80
+
81
+ error(message) {
82
+ logger.error(sanitizeSensitive(message));
83
+ }
84
+
85
+ warning(message) {
86
+ logger.warn(sanitizeSensitive(message));
87
+ }
88
+
89
+ info() {}
90
+
91
+ debug() {}
92
+
93
+ trace() {}
94
+ }
95
+
32
96
  /**
33
97
  * Returns the local address of elasticsearch based on
34
98
  * environment variables
@@ -102,12 +166,14 @@ const esMetricsConfig = () => {
102
166
  throw new Error('ELK Metrics stack not configured');
103
167
  }
104
168
 
105
- const node = `https://${process.env.METRICS_ES_USER}:${
106
- process.env.METRICS_ES_PASS}@${process.env.METRICS_ES_HOST}`;
169
+ const encodedUser = encodeURIComponent(process.env.METRICS_ES_USER);
170
+ const encodedPass = encodeURIComponent(process.env.METRICS_ES_PASS);
171
+ const node = `https://${encodedUser}:${encodedPass}@${process.env.METRICS_ES_HOST}`;
107
172
 
108
173
  return {
109
174
  node,
110
175
  requestTimeout: 50000,
176
+ log: EsCustomLogger,
111
177
  };
112
178
  };
113
179
 
@@ -329,6 +395,7 @@ class BaseSearch {
329
395
  }
330
396
 
331
397
  async get(id, parentId) {
398
+ const esCustomLogger = new EsCustomLogger();
332
399
  const body = {
333
400
  query: {
334
401
  bool: {
@@ -358,23 +425,31 @@ class BaseSearch {
358
425
  await this.initializeEsClient();
359
426
  }
360
427
 
361
- const result = await this.client.search({
362
- index: this.index,
363
- type: this.type,
364
- body,
365
- })
366
- .then((response) => response.body);
428
+ try {
429
+ const result = await this.client.search({
430
+ index: this.index,
431
+ type: this.type,
432
+ body,
433
+ })
434
+ .then((response) => response.body);
435
+
436
+ if (result.hits.total > 1) {
437
+ return { detail: multipleRecordFoundString };
438
+ }
439
+ if (result.hits.total === 0) {
440
+ return { detail: recordNotFoundString };
441
+ }
367
442
 
368
- if (result.hits.total > 1) {
369
- return { detail: multipleRecordFoundString };
370
- }
371
- if (result.hits.total === 0) {
372
- return { detail: recordNotFoundString };
443
+ const resp = result.hits.hits[0]._source;
444
+ resp._id = result.hits.hits[0]._id;
445
+ return resp;
446
+ } catch (error) {
447
+ esCustomLogger.error(sanitizeSensitive(error));
448
+ if (error.meta?.statusCode === 401) {
449
+ throw new HttpError(401, 'Invalid credentials');
450
+ }
451
+ throw sanitizeSensitive(error);
373
452
  }
374
-
375
- const resp = result.hits.hits[0]._source;
376
- resp._id = result.hits.hits[0]._id;
377
- return resp;
378
453
  }
379
454
 
380
455
  async exists(id, parentId) {
@@ -408,7 +483,13 @@ class BaseSearch {
408
483
  results: hits.map((s) => s._source),
409
484
  };
410
485
  } catch (error) {
411
- return error;
486
+ const esCustomLogger = new EsCustomLogger();
487
+ esCustomLogger.error(sanitizeSensitive(error));
488
+
489
+ if (error.meta?.statusCode === 401) {
490
+ throw new HttpError(401, 'Invalid credentials');
491
+ }
492
+ throw sanitizeSensitive(error);
412
493
  }
413
494
  }
414
495
 
@@ -431,7 +512,13 @@ class BaseSearch {
431
512
  counts: result.body.aggregations,
432
513
  };
433
514
  } catch (error) {
434
- return error;
515
+ const esCustomLogger = new EsCustomLogger();
516
+ esCustomLogger.error(sanitizeSensitive(error));
517
+
518
+ if (error.meta?.statusCode === 401) {
519
+ throw new HttpError(401, 'Invalid credentials');
520
+ }
521
+ throw sanitizeSensitive(error);
435
522
  }
436
523
  }
437
524
  }
@@ -461,4 +548,6 @@ module.exports = {
461
548
  multipleRecordFoundString,
462
549
  recordNotFoundString,
463
550
  getLocalEsHost,
551
+ sanitizeSensitive,
552
+ isError,
464
553
  };