timonel 3.0.0-beta.1 → 3.0.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 +186 -0
- package/README.md +182 -126
- package/SECURITY.md +25 -11
- package/dist/cli.js +54 -15
- package/dist/lib/helm.js +28 -1
- package/dist/lib/helmChartWriter.js +27 -8
- package/dist/lib/resources/baseResourceProvider.js +5 -0
- package/dist/lib/resources/cloud/aws/awsResources.js +2 -1
- package/dist/lib/resources/cloud/aws/karpenterResources.js +16 -2
- package/dist/lib/security.js +4 -4
- package/dist/lib/templates/flexible-subchart.js +14 -5
- package/dist/lib/templates/umbrella-chart.js +21 -10
- package/dist/lib/umbrellaRutter.js +19 -7
- package/dist/lib/utils/envVarsLoader.js +13 -7
- package/dist/lib/utils/helmHelpers.js +10 -2
- package/dist/lib/utils/helmYamlSerializer.js +19 -9
- package/dist/lib/utils/logger.js +54 -39
- package/dist/lib/utils/valuesRef.js +9 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Document, Scalar, isMap, isScalar, visit, Pair } from 'yaml';
|
|
2
|
+
import { SecurityUtils } from '../security.js';
|
|
2
3
|
import { isHelmConstruct, isHelmExpression, createHelmExpression, } from './helmControlStructures.js';
|
|
3
4
|
import { isHelmValue, isHelmFieldConditional, isHelmRange, isHelmWith, } from './valuesRef.js';
|
|
4
5
|
const FIELD_CONDITIONAL = '__FIELD_CONDITIONAL__:';
|
|
@@ -542,7 +543,8 @@ export function dumpHelmAwareYaml(obj, options = {}) {
|
|
|
542
543
|
const contentLines = content.split('\n');
|
|
543
544
|
const firstContentLine = contentLines[0]?.trim() || '';
|
|
544
545
|
if (firstContentLine.startsWith(`${fieldKey}:`)) {
|
|
545
|
-
|
|
546
|
+
const escapedFieldKey = fieldKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
547
|
+
contentLines[0] = contentLines[0].replace(new RegExp(`^\\s*${escapedFieldKey}:\\s*`), '');
|
|
546
548
|
content = contentLines.join('\n');
|
|
547
549
|
}
|
|
548
550
|
const contentFormatted = content
|
|
@@ -640,7 +642,7 @@ export function postProcessFieldConditionals(yaml) {
|
|
|
640
642
|
.join('\n');
|
|
641
643
|
result = result.substring(0, secondLastNewline + 1) + processed + result.substring(markerEnd);
|
|
642
644
|
}
|
|
643
|
-
result = result.replace(/:\s*\|[-+]?\s*\n(\
|
|
645
|
+
result = result.replace(/:\s*\|[-+]?\s*\n([ \t]*\{\{)/g, ':\n$1');
|
|
644
646
|
result = result.replace(/"(\{\{[\s\S]*?\}\})"/g, '$1');
|
|
645
647
|
return result;
|
|
646
648
|
}
|
|
@@ -718,13 +720,14 @@ export function parseHelmExpressions(content) {
|
|
|
718
720
|
let match;
|
|
719
721
|
regex.lastIndex = 0;
|
|
720
722
|
while ((match = regex.exec(line)) !== null) {
|
|
723
|
+
const sanitizedExpression = SecurityUtils.sanitizeLogMessage(match[0] || '');
|
|
721
724
|
expressions.push({
|
|
722
725
|
type,
|
|
723
|
-
expression:
|
|
726
|
+
expression: sanitizedExpression,
|
|
724
727
|
startLine: i + 1,
|
|
725
728
|
startCol: match.index + 1,
|
|
726
729
|
endLine: i + 1,
|
|
727
|
-
endCol: match.index + match[0]
|
|
730
|
+
endCol: match.index + (match[0]?.length || 0) + 1,
|
|
728
731
|
});
|
|
729
732
|
if (match.index === regex.lastIndex)
|
|
730
733
|
regex.lastIndex++;
|
|
@@ -780,14 +783,19 @@ function validateFunctionCalls(_content) {
|
|
|
780
783
|
function checkCommonIssues(yaml, warnings) {
|
|
781
784
|
const deprecatedFunctions = ['template'];
|
|
782
785
|
for (const func of deprecatedFunctions) {
|
|
783
|
-
|
|
786
|
+
if (!/^[a-zA-Z0-9_]+$/.test(func)) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const escapedFunc = func.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
790
|
+
const pattern = new RegExp(`\\{\\{[^}]*\\b${escapedFunc}\\b[^}]*\\}\\}`, 'g');
|
|
784
791
|
let match;
|
|
785
792
|
while ((match = pattern.exec(yaml)) !== null) {
|
|
793
|
+
const sanitizedFunc = SecurityUtils.sanitizeLogMessage(func);
|
|
786
794
|
warnings.push({
|
|
787
795
|
type: 'semantic',
|
|
788
|
-
message: `Function '${
|
|
796
|
+
message: `Function '${sanitizedFunc}' is deprecated`,
|
|
789
797
|
expression: match[0],
|
|
790
|
-
suggestion: `Consider avoiding deprecated function '${
|
|
798
|
+
suggestion: `Consider avoiding deprecated function '${sanitizedFunc}'`,
|
|
791
799
|
});
|
|
792
800
|
}
|
|
793
801
|
}
|
|
@@ -798,11 +806,13 @@ function checkQuotedExpressions(yaml, warnings) {
|
|
|
798
806
|
let match;
|
|
799
807
|
pattern.lastIndex = 0;
|
|
800
808
|
while ((match = pattern.exec(yaml)) !== null) {
|
|
809
|
+
const sanitizedMatch = SecurityUtils.sanitizeLogMessage(match[1] || '');
|
|
810
|
+
const sanitizedExpression = SecurityUtils.sanitizeLogMessage(match[0] || '');
|
|
801
811
|
warnings.push({
|
|
802
812
|
type: 'semantic',
|
|
803
813
|
message: 'Helm expression should not be quoted',
|
|
804
|
-
expression:
|
|
805
|
-
suggestion: `Remove quotes around: ${
|
|
814
|
+
expression: sanitizedExpression,
|
|
815
|
+
suggestion: `Remove quotes around: ${sanitizedMatch}`,
|
|
806
816
|
});
|
|
807
817
|
}
|
|
808
818
|
}
|
package/dist/lib/utils/logger.js
CHANGED
|
@@ -180,19 +180,24 @@ export class TimonelLogger {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
requestSerializer(req) {
|
|
183
|
-
|
|
183
|
+
try {
|
|
184
|
+
if (!req || typeof req !== 'object')
|
|
185
|
+
return {};
|
|
186
|
+
const request = req;
|
|
187
|
+
return {
|
|
188
|
+
method: request.method,
|
|
189
|
+
url: SecurityUtils.sanitizeLogMessage(request.url || ''),
|
|
190
|
+
headers: {
|
|
191
|
+
'user-agent': request.headers?.['user-agent'],
|
|
192
|
+
'content-type': request.headers?.['content-type'],
|
|
193
|
+
},
|
|
194
|
+
remoteAddress: request.remoteAddress,
|
|
195
|
+
remotePort: request.remotePort,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
184
199
|
return {};
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
method: request.method,
|
|
188
|
-
url: SecurityUtils.sanitizeLogMessage(request.url || ''),
|
|
189
|
-
headers: {
|
|
190
|
-
'user-agent': request.headers?.['user-agent'],
|
|
191
|
-
'content-type': request.headers?.['content-type'],
|
|
192
|
-
},
|
|
193
|
-
remoteAddress: request.remoteAddress,
|
|
194
|
-
remotePort: request.remotePort,
|
|
195
|
-
};
|
|
200
|
+
}
|
|
196
201
|
}
|
|
197
202
|
responseSerializer(res) {
|
|
198
203
|
if (!res || typeof res !== 'object')
|
|
@@ -256,40 +261,50 @@ export class TimonelLogger {
|
|
|
256
261
|
}
|
|
257
262
|
}
|
|
258
263
|
sanitizeContext(context) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
264
|
+
try {
|
|
265
|
+
const sanitized = {};
|
|
266
|
+
for (const [key, value] of Object.entries(context)) {
|
|
267
|
+
if (TimonelLogger.SENSITIVE_FIELDS.has(key.toLowerCase())) {
|
|
268
|
+
sanitized[key] = '[REDACTED]';
|
|
269
|
+
}
|
|
270
|
+
else if (typeof value === 'string') {
|
|
271
|
+
sanitized[key] = SecurityUtils.sanitizeLogMessage(value);
|
|
272
|
+
}
|
|
273
|
+
else if (value && typeof value === 'object') {
|
|
274
|
+
sanitized[key] = this.sanitizeNestedObject(value, 2);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
sanitized[key] = value;
|
|
278
|
+
}
|
|
272
279
|
}
|
|
280
|
+
return sanitized;
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return {};
|
|
273
284
|
}
|
|
274
|
-
return sanitized;
|
|
275
285
|
}
|
|
276
286
|
sanitizeNestedObject(obj, depth) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const sanitized = {};
|
|
281
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
282
|
-
if (typeof value === 'string') {
|
|
283
|
-
sanitized[key] = SecurityUtils.sanitizeLogMessage(value);
|
|
287
|
+
try {
|
|
288
|
+
if (depth <= 0 || !obj || typeof obj !== 'object') {
|
|
289
|
+
return obj;
|
|
284
290
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
291
|
+
const sanitized = {};
|
|
292
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
293
|
+
if (typeof value === 'string') {
|
|
294
|
+
sanitized[key] = SecurityUtils.sanitizeLogMessage(value);
|
|
295
|
+
}
|
|
296
|
+
else if (value && typeof value === 'object') {
|
|
297
|
+
sanitized[key] = this.sanitizeNestedObject(value, depth - 1);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
sanitized[key] = value;
|
|
301
|
+
}
|
|
290
302
|
}
|
|
303
|
+
return sanitized;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return obj;
|
|
291
307
|
}
|
|
292
|
-
return sanitized;
|
|
293
308
|
}
|
|
294
309
|
child(context) {
|
|
295
310
|
const sanitizedContext = this.sanitizeContext(context);
|
|
@@ -10,6 +10,9 @@ function serializeValue(value) {
|
|
|
10
10
|
return String(value);
|
|
11
11
|
}
|
|
12
12
|
function createCondition(condition) {
|
|
13
|
+
if (!condition || typeof condition !== 'string') {
|
|
14
|
+
throw new Error('Condition must be a non-empty string');
|
|
15
|
+
}
|
|
13
16
|
return {
|
|
14
17
|
[HELM_VALUE_SYMBOL]: true,
|
|
15
18
|
__condition: condition,
|
|
@@ -17,9 +20,15 @@ function createCondition(condition) {
|
|
|
17
20
|
return createCondition(`not (${this.__condition})`);
|
|
18
21
|
},
|
|
19
22
|
and(other) {
|
|
23
|
+
if (!other || typeof other.__condition !== 'string') {
|
|
24
|
+
throw new Error('Invalid HelmCondition provided to and()');
|
|
25
|
+
}
|
|
20
26
|
return createCondition(`and (${this.__condition}) (${other.__condition})`);
|
|
21
27
|
},
|
|
22
28
|
or(other) {
|
|
29
|
+
if (!other || typeof other.__condition !== 'string') {
|
|
30
|
+
throw new Error('Invalid HelmCondition provided to or()');
|
|
31
|
+
}
|
|
23
32
|
return createCondition(`or (${this.__condition}) (${other.__condition})`);
|
|
24
33
|
},
|
|
25
34
|
toString() {
|