timonel 2.8.1 → 2.8.3
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 +6 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +290 -226
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -6
- package/dist/lib/helm.d.ts +0 -472
- package/dist/lib/helm.js +0 -481
- package/dist/lib/helmChartWriter.d.ts +0 -178
- package/dist/lib/helmChartWriter.js +0 -180
- package/dist/lib/resources/baseResourceProvider.d.ts +0 -46
- package/dist/lib/resources/baseResourceProvider.js +1 -47
- package/dist/lib/resources/cloud/aws/awsResources.d.ts +0 -144
- package/dist/lib/resources/cloud/aws/awsResources.js +1 -163
- package/dist/lib/resources/cloud/aws/karpenterResources.d.ts +0 -132
- package/dist/lib/resources/cloud/aws/karpenterResources.js +0 -75
- package/dist/lib/rutter.d.ts +0 -324
- package/dist/lib/rutter.js +8 -352
- package/dist/lib/security.d.ts +0 -119
- package/dist/lib/security.js +4 -160
- package/dist/lib/umbrella.d.ts +0 -24
- package/dist/lib/umbrella.js +0 -24
- package/dist/lib/umbrellaRutter.d.ts +0 -71
- package/dist/lib/umbrellaRutter.js +2 -82
- package/dist/lib/utils/helmHelpers.d.ts +0 -39
- package/dist/lib/utils/helmHelpers.js +0 -32
- package/package.json +1 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/helm.d.ts.map +0 -1
- package/dist/lib/helm.js.map +0 -1
- package/dist/lib/helmChartWriter.d.ts.map +0 -1
- package/dist/lib/helmChartWriter.js.map +0 -1
- package/dist/lib/resources/baseResourceProvider.d.ts.map +0 -1
- package/dist/lib/resources/baseResourceProvider.js.map +0 -1
- package/dist/lib/resources/cloud/aws/awsResources.d.ts.map +0 -1
- package/dist/lib/resources/cloud/aws/awsResources.js.map +0 -1
- package/dist/lib/resources/cloud/aws/karpenterResources.d.ts.map +0 -1
- package/dist/lib/resources/cloud/aws/karpenterResources.js.map +0 -1
- package/dist/lib/rutter.d.ts.map +0 -1
- package/dist/lib/rutter.js.map +0 -1
- package/dist/lib/security.d.ts.map +0 -1
- package/dist/lib/security.js.map +0 -1
- package/dist/lib/umbrella.d.ts.map +0 -1
- package/dist/lib/umbrella.js.map +0 -1
- package/dist/lib/umbrellaRutter.d.ts.map +0 -1
- package/dist/lib/umbrellaRutter.js.map +0 -1
- package/dist/lib/utils/helmHelpers.d.ts.map +0 -1
- package/dist/lib/utils/helmHelpers.js.map +0 -1
package/dist/lib/security.js
CHANGED
|
@@ -1,77 +1,36 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Security utilities for input validation and sanitization
|
|
3
|
-
* Focused on CLI tool security concerns: path traversal, injection prevention
|
|
4
|
-
* @since 2.2.0
|
|
5
|
-
*/
|
|
6
1
|
import * as path from 'path';
|
|
7
|
-
/**
|
|
8
|
-
* Security utilities for input validation and sanitization
|
|
9
|
-
* Focused on CLI tool security concerns: path traversal, injection prevention
|
|
10
|
-
*
|
|
11
|
-
* @since 2.2.0
|
|
12
|
-
*/
|
|
13
2
|
export class SecurityUtils {
|
|
14
|
-
/**
|
|
15
|
-
* Validates and sanitizes file paths to prevent path traversal attacks
|
|
16
|
-
* @param inputPath - The path to validate
|
|
17
|
-
* @param allowedBasePath - The base path that the input should be within
|
|
18
|
-
* @returns Sanitized path if valid
|
|
19
|
-
* @throws Error if path is invalid or contains traversal sequences
|
|
20
|
-
*
|
|
21
|
-
* @since 2.2.0
|
|
22
|
-
*/
|
|
23
3
|
static validatePath(inputPath, allowedBasePath) {
|
|
24
4
|
if (!inputPath || typeof inputPath !== 'string') {
|
|
25
5
|
throw new Error('Invalid path: path must be a non-empty string');
|
|
26
6
|
}
|
|
27
|
-
// Check for path traversal sequences
|
|
28
7
|
if (inputPath.includes('../') ||
|
|
29
8
|
inputPath.includes('..\\') ||
|
|
30
9
|
inputPath.includes('%2e%2e%2f') ||
|
|
31
10
|
inputPath.includes('%2e%2e%5c')) {
|
|
32
11
|
throw new Error('Invalid path: path traversal sequences detected');
|
|
33
12
|
}
|
|
34
|
-
// Resolve and normalize paths
|
|
35
13
|
const resolvedInput = path.resolve(inputPath);
|
|
36
14
|
const resolvedBase = path.resolve(allowedBasePath);
|
|
37
|
-
// Ensure the resolved path is within the allowed base path
|
|
38
15
|
if (!resolvedInput.startsWith(resolvedBase + path.sep) && resolvedInput !== resolvedBase) {
|
|
39
16
|
throw new Error(`Invalid path: path must be within ${resolvedBase}`);
|
|
40
17
|
}
|
|
41
18
|
return resolvedInput;
|
|
42
19
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Sanitizes log messages to prevent log injection attacks
|
|
45
|
-
* @param message - The message to sanitize
|
|
46
|
-
* @returns Sanitized message with control characters removed
|
|
47
|
-
*
|
|
48
|
-
* @since 2.2.0
|
|
49
|
-
*/
|
|
50
20
|
static sanitizeLogMessage(message) {
|
|
51
21
|
if (typeof message !== 'string') {
|
|
52
22
|
return String(message);
|
|
53
23
|
}
|
|
54
|
-
// Remove control characters (ASCII 0-31 and 127) and normalize line endings
|
|
55
24
|
return (message
|
|
56
|
-
|
|
57
|
-
.replace(
|
|
58
|
-
.replace(
|
|
59
|
-
.replace(/[\r\n]/g, ' ') // Replace remaining CR/LF with space
|
|
25
|
+
.replace(/[\x00-\x1F\x7F]/g, '')
|
|
26
|
+
.replace(/\r\n/g, ' ')
|
|
27
|
+
.replace(/[\r\n]/g, ' ')
|
|
60
28
|
.trim());
|
|
61
29
|
}
|
|
62
|
-
/**
|
|
63
|
-
* Sanitizes environment names to prevent path traversal (CWE-22)
|
|
64
|
-
* @param env - Environment name to sanitize
|
|
65
|
-
* @returns Sanitized environment name
|
|
66
|
-
* @throws Error if environment name is invalid
|
|
67
|
-
*
|
|
68
|
-
* @since 2.2.0
|
|
69
|
-
*/
|
|
70
30
|
static sanitizeEnvironmentName(env) {
|
|
71
31
|
if (!env || typeof env !== 'string') {
|
|
72
32
|
throw new Error('Environment name must be a non-empty string');
|
|
73
33
|
}
|
|
74
|
-
// Allow only alphanumeric, hyphens, and underscores
|
|
75
34
|
const sanitized = env.replace(/[^a-zA-Z0-9-_]/g, '');
|
|
76
35
|
if (sanitized !== env) {
|
|
77
36
|
throw new Error(`Invalid environment name: ${this.sanitizeLogMessage(env)}`);
|
|
@@ -81,98 +40,49 @@ export class SecurityUtils {
|
|
|
81
40
|
}
|
|
82
41
|
return sanitized;
|
|
83
42
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Validates TypeScript file extensions for dynamic imports
|
|
86
|
-
* @param filePath - The file path to validate
|
|
87
|
-
* @returns True if the file has a valid TypeScript extension
|
|
88
|
-
*
|
|
89
|
-
* @since 2.2.0
|
|
90
|
-
*/
|
|
91
43
|
static isValidTypeScriptFile(filePath) {
|
|
92
44
|
const allowedExtensions = ['.ts', '.tsx'];
|
|
93
45
|
const ext = path.extname(filePath);
|
|
94
46
|
return allowedExtensions.includes(ext);
|
|
95
47
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Validates if a path is safe for use in Helm templates
|
|
98
|
-
* Enhanced validation with better security checks and Helm best practices
|
|
99
|
-
* @param templatePath - The template path to validate
|
|
100
|
-
* @returns True if the path is valid for Helm templates
|
|
101
|
-
*
|
|
102
|
-
* @since 2.2.0
|
|
103
|
-
*/
|
|
104
48
|
static isValidHelmTemplatePath(templatePath) {
|
|
105
49
|
if (!templatePath || typeof templatePath !== 'string') {
|
|
106
50
|
return false;
|
|
107
51
|
}
|
|
108
|
-
// Check length limits (reasonable for Helm paths)
|
|
109
52
|
if (templatePath.length > 253) {
|
|
110
53
|
return false;
|
|
111
54
|
}
|
|
112
|
-
// Enhanced validation following Kubernetes naming conventions
|
|
113
|
-
// Check if starts and ends with alphanumeric
|
|
114
55
|
if (!/^[a-zA-Z0-9]/.test(templatePath) || !/[a-zA-Z0-9]$/.test(templatePath)) {
|
|
115
56
|
return false;
|
|
116
57
|
}
|
|
117
|
-
// Check for valid characters only (safe regex)
|
|
118
58
|
if (!/^[a-zA-Z0-9._-]+$/.test(templatePath)) {
|
|
119
59
|
return false;
|
|
120
60
|
}
|
|
121
|
-
// Additional security checks
|
|
122
|
-
// Prevent path traversal attempts
|
|
123
61
|
if (templatePath.includes('..') || templatePath.includes('//')) {
|
|
124
62
|
return false;
|
|
125
63
|
}
|
|
126
|
-
// Prevent reserved words that could cause issues
|
|
127
64
|
const reservedWords = ['nil', 'null', 'undefined', 'true', 'false'];
|
|
128
65
|
const pathSegments = templatePath.split('.');
|
|
129
66
|
for (const segment of pathSegments) {
|
|
130
67
|
if (reservedWords.includes(segment.toLowerCase())) {
|
|
131
68
|
return false;
|
|
132
69
|
}
|
|
133
|
-
// Each segment should not be empty
|
|
134
70
|
if (segment.length === 0) {
|
|
135
71
|
return false;
|
|
136
72
|
}
|
|
137
73
|
}
|
|
138
74
|
return true;
|
|
139
75
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Validates chart names according to Helm conventions
|
|
142
|
-
* @param chartName - Chart name to validate
|
|
143
|
-
* @returns True if chart name follows Helm naming rules
|
|
144
|
-
*
|
|
145
|
-
* @since 2.2.0
|
|
146
|
-
*/
|
|
147
76
|
static isValidChartName(chartName) {
|
|
148
77
|
if (!chartName || typeof chartName !== 'string') {
|
|
149
78
|
return false;
|
|
150
79
|
}
|
|
151
|
-
|
|
152
|
-
// eslint-disable-next-line security/detect-unsafe-regex -- Simple character class regex is safe
|
|
153
|
-
const chartNameRegex = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;
|
|
80
|
+
const chartNameRegex = /^[a-z]([-a-z0-9]*[a-z0-9])?$/;
|
|
154
81
|
return chartNameRegex.test(chartName) && chartName.length <= 63;
|
|
155
82
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Validates subchart names according to Helm conventions
|
|
158
|
-
* @param subchartName - Subchart name to validate
|
|
159
|
-
* @returns True if subchart name is valid
|
|
160
|
-
*
|
|
161
|
-
* @since 2.2.0
|
|
162
|
-
*/
|
|
163
83
|
static isValidSubchartName(subchartName) {
|
|
164
84
|
return this.isValidChartName(subchartName);
|
|
165
85
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Sanitizes environment variable names and values for Kubernetes security
|
|
168
|
-
* Prevents injection attacks and ensures compliance with Kubernetes naming rules
|
|
169
|
-
* @param name - Environment variable name to sanitize
|
|
170
|
-
* @param value - Environment variable value to sanitize
|
|
171
|
-
* @returns Object with sanitized name and value
|
|
172
|
-
* @throws Error if name or value contains dangerous patterns
|
|
173
|
-
*
|
|
174
|
-
* @since 2.6.0
|
|
175
|
-
*/
|
|
176
86
|
static sanitizeEnvVar(name, value) {
|
|
177
87
|
if (!name || typeof name !== 'string') {
|
|
178
88
|
throw new Error('Environment variable name must be a non-empty string');
|
|
@@ -180,9 +90,7 @@ export class SecurityUtils {
|
|
|
180
90
|
if (typeof value !== 'string') {
|
|
181
91
|
throw new Error('Environment variable value must be a string');
|
|
182
92
|
}
|
|
183
|
-
// Kubernetes env var name validation (RFC 1123 compatible)
|
|
184
93
|
let sanitizedName = name.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
|
185
|
-
// Ensure name starts with letter or underscore
|
|
186
94
|
if (/^[0-9]/.test(sanitizedName)) {
|
|
187
95
|
sanitizedName = `_${sanitizedName}`;
|
|
188
96
|
}
|
|
@@ -190,7 +98,6 @@ export class SecurityUtils {
|
|
|
190
98
|
if (!nameRegex.test(sanitizedName)) {
|
|
191
99
|
throw new Error(`Invalid environment variable name: ${this.sanitizeLogMessage(name)}`);
|
|
192
100
|
}
|
|
193
|
-
// Check for dangerous patterns in value using safer string methods
|
|
194
101
|
const hasDangerousPattern = (val) => {
|
|
195
102
|
if (val.includes('$(') && val.includes(')'))
|
|
196
103
|
return 'command substitution';
|
|
@@ -198,7 +105,6 @@ export class SecurityUtils {
|
|
|
198
105
|
return 'backtick execution';
|
|
199
106
|
if (val.includes('${') && val.includes('}'))
|
|
200
107
|
return 'variable expansion';
|
|
201
|
-
// Check for control characters
|
|
202
108
|
for (let i = 0; i < val.length; i++) {
|
|
203
109
|
const code = val.charCodeAt(i);
|
|
204
110
|
if ((code >= 0x00 && code <= 0x08) ||
|
|
@@ -215,7 +121,6 @@ export class SecurityUtils {
|
|
|
215
121
|
if (dangerousPattern) {
|
|
216
122
|
throw new Error(`Environment variable value contains dangerous pattern (${dangerousPattern}): ${this.sanitizeLogMessage(value)}`);
|
|
217
123
|
}
|
|
218
|
-
// Limit value length to prevent DoS
|
|
219
124
|
if (value.length > 32768) {
|
|
220
125
|
throw new Error('Environment variable value exceeds maximum length (32KB)');
|
|
221
126
|
}
|
|
@@ -224,58 +129,31 @@ export class SecurityUtils {
|
|
|
224
129
|
value: value.trim(),
|
|
225
130
|
};
|
|
226
131
|
}
|
|
227
|
-
/**
|
|
228
|
-
* Validates container image tags for security best practices
|
|
229
|
-
* Prevents use of dangerous tags and ensures proper versioning
|
|
230
|
-
* @param tag - Image tag to validate
|
|
231
|
-
* @returns True if tag is valid and secure
|
|
232
|
-
* @throws Error if tag violates security policies
|
|
233
|
-
*
|
|
234
|
-
* @since 2.6.0
|
|
235
|
-
*/
|
|
236
132
|
static validateImageTag(tag) {
|
|
237
133
|
if (!tag || typeof tag !== 'string') {
|
|
238
134
|
throw new Error('Image tag must be a non-empty string');
|
|
239
135
|
}
|
|
240
136
|
const trimmedTag = tag.trim();
|
|
241
|
-
// Reject dangerous or non-specific tags
|
|
242
137
|
const dangerousTags = ['latest', 'master', 'main', 'dev', 'development', 'test', 'staging'];
|
|
243
138
|
if (dangerousTags.includes(trimmedTag.toLowerCase())) {
|
|
244
139
|
throw new Error(`Insecure image tag detected: '${trimmedTag}'. Use specific version tags for security.`);
|
|
245
140
|
}
|
|
246
|
-
// Validate tag format (Docker tag rules)
|
|
247
141
|
const tagRegex = /^[a-zA-Z0-9._-]+$/;
|
|
248
142
|
if (!tagRegex.test(trimmedTag)) {
|
|
249
143
|
throw new Error(`Invalid image tag format: ${this.sanitizeLogMessage(trimmedTag)}`);
|
|
250
144
|
}
|
|
251
|
-
// Check length limits
|
|
252
145
|
if (trimmedTag.length > 128) {
|
|
253
146
|
throw new Error('Image tag exceeds maximum length (128 characters)');
|
|
254
147
|
}
|
|
255
|
-
// Prefer semantic versioning patterns
|
|
256
|
-
// eslint-disable-next-line security/detect-unsafe-regex -- Simple regex patterns are safe for validation
|
|
257
148
|
const semverPattern = /^v?\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/;
|
|
258
149
|
const hashPattern = /^[a-f0-9]{7,64}$/;
|
|
259
|
-
// eslint-disable-next-line security/detect-unsafe-regex -- Simple date pattern is safe for validation
|
|
260
150
|
const datePattern = /^\d{4}-\d{2}-\d{2}(-[a-zA-Z0-9.-]+)?$/;
|
|
261
151
|
if (!semverPattern.test(trimmedTag) &&
|
|
262
152
|
!hashPattern.test(trimmedTag) &&
|
|
263
153
|
!datePattern.test(trimmedTag)) {
|
|
264
|
-
// Warning for non-standard tags but don't fail
|
|
265
|
-
console.warn(`Warning: Image tag '${trimmedTag}' doesn't follow recommended patterns (semver, hash, or date)`);
|
|
266
154
|
}
|
|
267
155
|
return true;
|
|
268
156
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Generates secure names for Kubernetes secrets
|
|
271
|
-
* Ensures names follow RFC 1123 and are safe for Kubernetes
|
|
272
|
-
* @param baseName - Base name for the secret
|
|
273
|
-
* @param suffix - Optional suffix to append
|
|
274
|
-
* @returns Secure secret name
|
|
275
|
-
* @throws Error if generated name is invalid
|
|
276
|
-
*
|
|
277
|
-
* @since 2.6.0
|
|
278
|
-
*/
|
|
279
157
|
static generateSecretName(baseName, suffix) {
|
|
280
158
|
if (!baseName || typeof baseName !== 'string') {
|
|
281
159
|
throw new Error('Base name must be a non-empty string');
|
|
@@ -284,27 +162,16 @@ export class SecurityUtils {
|
|
|
284
162
|
if (suffix) {
|
|
285
163
|
sanitizedBase = this.appendSuffix(sanitizedBase, suffix);
|
|
286
164
|
}
|
|
287
|
-
// Ensure name starts with letter (not number)
|
|
288
165
|
if (/^[0-9]/.test(sanitizedBase)) {
|
|
289
166
|
sanitizedBase = `s${sanitizedBase}`;
|
|
290
167
|
}
|
|
291
168
|
sanitizedBase = this.truncateIfNeeded(sanitizedBase, suffix);
|
|
292
|
-
// Final validation
|
|
293
169
|
if (!this.isValidChartName(sanitizedBase)) {
|
|
294
170
|
throw new Error(`Generated secret name is invalid: ${sanitizedBase}`);
|
|
295
171
|
}
|
|
296
172
|
return sanitizedBase;
|
|
297
173
|
}
|
|
298
|
-
/**
|
|
299
|
-
* Sanitizes the base name for secret generation using safe string methods
|
|
300
|
-
* @param baseName - Base name to sanitize
|
|
301
|
-
* @returns Sanitized base name
|
|
302
|
-
* @throws Error if no valid characters remain
|
|
303
|
-
*
|
|
304
|
-
* @since 2.6.0
|
|
305
|
-
*/
|
|
306
174
|
static sanitizeBaseName(baseName) {
|
|
307
|
-
// Use character-by-character processing instead of regex
|
|
308
175
|
let sanitized = '';
|
|
309
176
|
for (const char of baseName.toLowerCase()) {
|
|
310
177
|
if ((char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char === '-') {
|
|
@@ -314,7 +181,6 @@ export class SecurityUtils {
|
|
|
314
181
|
sanitized += '-';
|
|
315
182
|
}
|
|
316
183
|
}
|
|
317
|
-
// Collapse multiple hyphens using split/filter/join
|
|
318
184
|
const parts = sanitized.split('-').filter((part) => part.length > 0);
|
|
319
185
|
sanitized = parts.join('-');
|
|
320
186
|
if (!sanitized) {
|
|
@@ -322,20 +188,10 @@ export class SecurityUtils {
|
|
|
322
188
|
}
|
|
323
189
|
return sanitized;
|
|
324
190
|
}
|
|
325
|
-
/**
|
|
326
|
-
* Appends suffix to sanitized base name using safe string methods
|
|
327
|
-
* @param baseName - Sanitized base name
|
|
328
|
-
* @param suffix - Suffix to append
|
|
329
|
-
* @returns Base name with suffix
|
|
330
|
-
* @throws Error if suffix is invalid
|
|
331
|
-
*
|
|
332
|
-
* @since 2.6.0
|
|
333
|
-
*/
|
|
334
191
|
static appendSuffix(baseName, suffix) {
|
|
335
192
|
if (typeof suffix !== 'string') {
|
|
336
193
|
throw new Error('Suffix must be a string');
|
|
337
194
|
}
|
|
338
|
-
// Use character-by-character processing instead of regex
|
|
339
195
|
let sanitizedSuffix = '';
|
|
340
196
|
for (const char of suffix.toLowerCase()) {
|
|
341
197
|
if ((char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char === '-') {
|
|
@@ -345,7 +201,6 @@ export class SecurityUtils {
|
|
|
345
201
|
sanitizedSuffix += '-';
|
|
346
202
|
}
|
|
347
203
|
}
|
|
348
|
-
// Collapse multiple hyphens using split/filter/join
|
|
349
204
|
const parts = sanitizedSuffix.split('-').filter((part) => part.length > 0);
|
|
350
205
|
sanitizedSuffix = parts.join('-');
|
|
351
206
|
if (sanitizedSuffix) {
|
|
@@ -353,22 +208,12 @@ export class SecurityUtils {
|
|
|
353
208
|
}
|
|
354
209
|
return baseName;
|
|
355
210
|
}
|
|
356
|
-
/**
|
|
357
|
-
* Truncates name if it exceeds length limits
|
|
358
|
-
* @param name - Name to potentially truncate
|
|
359
|
-
* @param suffix - Original suffix for preservation
|
|
360
|
-
* @returns Truncated name if needed
|
|
361
|
-
*
|
|
362
|
-
* @since 2.6.0
|
|
363
|
-
*/
|
|
364
211
|
static truncateIfNeeded(name, suffix) {
|
|
365
212
|
const maxLength = 63;
|
|
366
213
|
if (name.length <= maxLength) {
|
|
367
214
|
return name;
|
|
368
215
|
}
|
|
369
|
-
// Truncate but preserve suffix if possible
|
|
370
216
|
if (suffix) {
|
|
371
|
-
// Use safe character processing instead of regex
|
|
372
217
|
let safeSuffix = '';
|
|
373
218
|
for (const char of suffix.toLowerCase()) {
|
|
374
219
|
if ((char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char === '-') {
|
|
@@ -387,4 +232,3 @@ export class SecurityUtils {
|
|
|
387
232
|
return name.substring(0, maxLength);
|
|
388
233
|
}
|
|
389
234
|
}
|
|
390
|
-
//# sourceMappingURL=security.js.map
|
package/dist/lib/umbrella.d.ts
CHANGED
|
@@ -1,27 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Umbrella chart utilities for managing multiple subcharts as a single unit
|
|
3
|
-
* @since 0.2.0
|
|
4
|
-
*/
|
|
5
1
|
import type { UmbrellaRutter, UmbrellaRutterProps } from './umbrellaRutter.js';
|
|
6
|
-
/**
|
|
7
|
-
* Create an umbrella chart from multiple Rutter instances
|
|
8
|
-
*
|
|
9
|
-
* @param props - Umbrella chart configuration
|
|
10
|
-
* @returns UmbrellaRutter instance for managing subcharts
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* const umbrella = createUmbrella({
|
|
15
|
-
* meta: { name: 'my-umbrella', version: '1.0.0' },
|
|
16
|
-
* subcharts: [
|
|
17
|
-
* { name: 'frontend', rutter: frontendChart },
|
|
18
|
-
* { name: 'backend', rutter: backendChart }
|
|
19
|
-
* ]
|
|
20
|
-
* });
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* @since 0.2.0
|
|
24
|
-
*/
|
|
25
2
|
export declare function createUmbrella(props: UmbrellaRutterProps): UmbrellaRutter;
|
|
26
3
|
export type { UmbrellaRutterProps, SubchartSpec } from './umbrellaRutter.js';
|
|
27
|
-
//# sourceMappingURL=umbrella.d.ts.map
|
package/dist/lib/umbrella.js
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Umbrella chart utilities for managing multiple subcharts as a single unit
|
|
3
|
-
* @since 0.2.0
|
|
4
|
-
*/
|
|
5
1
|
import { UmbrellaRutter as UmbrellaRutterClass } from './umbrellaRutter.js';
|
|
6
|
-
/**
|
|
7
|
-
* Create an umbrella chart from multiple Rutter instances
|
|
8
|
-
*
|
|
9
|
-
* @param props - Umbrella chart configuration
|
|
10
|
-
* @returns UmbrellaRutter instance for managing subcharts
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* const umbrella = createUmbrella({
|
|
15
|
-
* meta: { name: 'my-umbrella', version: '1.0.0' },
|
|
16
|
-
* subcharts: [
|
|
17
|
-
* { name: 'frontend', rutter: frontendChart },
|
|
18
|
-
* { name: 'backend', rutter: backendChart }
|
|
19
|
-
* ]
|
|
20
|
-
* });
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* @since 0.2.0
|
|
24
|
-
*/
|
|
25
2
|
export function createUmbrella(props) {
|
|
26
3
|
return new UmbrellaRutterClass(props);
|
|
27
4
|
}
|
|
28
|
-
//# sourceMappingURL=umbrella.js.map
|
|
@@ -1,97 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview UmbrellaRutter class for managing Helm umbrella charts with multiple subcharts
|
|
3
|
-
* @since 0.2.0
|
|
4
|
-
*/
|
|
5
1
|
import type { HelmChartMeta } from './helmChartWriter.js';
|
|
6
2
|
import type { Rutter } from './rutter.js';
|
|
7
|
-
/**
|
|
8
|
-
* Configuration for a subchart within an umbrella chart
|
|
9
|
-
*
|
|
10
|
-
* @since 0.2.0
|
|
11
|
-
*/
|
|
12
3
|
export interface SubchartSpec {
|
|
13
|
-
/** Name of the subchart */
|
|
14
4
|
name: string;
|
|
15
|
-
/** Rutter instance that generates the subchart */
|
|
16
5
|
rutter: Rutter;
|
|
17
|
-
/** Version of the subchart (optional) */
|
|
18
6
|
version?: string;
|
|
19
|
-
/** Helm condition for enabling/disabling the subchart */
|
|
20
7
|
condition?: string;
|
|
21
|
-
/** Tags for grouping subcharts */
|
|
22
8
|
tags?: string[];
|
|
23
|
-
/** Repository URL for the subchart */
|
|
24
9
|
repository?: string;
|
|
25
10
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Configuration properties for UmbrellaRutter
|
|
28
|
-
*
|
|
29
|
-
* @since 0.2.0
|
|
30
|
-
*/
|
|
31
11
|
export interface UmbrellaRutterProps {
|
|
32
|
-
/** Metadata for the umbrella chart */
|
|
33
12
|
meta: HelmChartMeta;
|
|
34
|
-
/** Array of subchart specifications */
|
|
35
13
|
subcharts: SubchartSpec[];
|
|
36
|
-
/** Default values for the umbrella chart */
|
|
37
14
|
defaultValues?: Record<string, unknown>;
|
|
38
|
-
/** Environment-specific values */
|
|
39
15
|
envValues?: Record<string, Record<string, unknown>>;
|
|
40
16
|
}
|
|
41
|
-
/**
|
|
42
|
-
* UmbrellaRutter manages multiple Rutter instances as subcharts
|
|
43
|
-
* to create Helm umbrella charts with dependencies.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```typescript
|
|
47
|
-
* const umbrella = new UmbrellaRutter({
|
|
48
|
-
* meta: { name: 'my-app', version: '1.0.0' },
|
|
49
|
-
* subcharts: [
|
|
50
|
-
* { name: 'frontend', rutter: frontendChart },
|
|
51
|
-
* { name: 'backend', rutter: backendChart }
|
|
52
|
-
* ]
|
|
53
|
-
* });
|
|
54
|
-
*
|
|
55
|
-
* umbrella.write('./charts/my-app');
|
|
56
|
-
* ```
|
|
57
|
-
*
|
|
58
|
-
* @since 0.2.0
|
|
59
|
-
*/
|
|
60
17
|
export declare class UmbrellaRutter {
|
|
61
18
|
private readonly props;
|
|
62
|
-
/**
|
|
63
|
-
* Creates a new UmbrellaRutter instance
|
|
64
|
-
*
|
|
65
|
-
* @param props - Configuration properties for the umbrella chart
|
|
66
|
-
* @throws {Error} If chart metadata is invalid
|
|
67
|
-
*
|
|
68
|
-
* @since 0.2.0
|
|
69
|
-
*/
|
|
70
19
|
constructor(props: UmbrellaRutterProps);
|
|
71
20
|
private validateMetadata;
|
|
72
|
-
/**
|
|
73
|
-
* Write the umbrella chart with all subcharts to the output directory
|
|
74
|
-
*
|
|
75
|
-
* Creates the complete umbrella chart structure including:
|
|
76
|
-
* - Parent Chart.yaml with dependencies
|
|
77
|
-
* - Parent values.yaml with global and subchart values
|
|
78
|
-
* - Individual subchart directories under charts/
|
|
79
|
-
* - NOTES.txt template
|
|
80
|
-
*
|
|
81
|
-
* @param outDir - Output directory path for the umbrella chart
|
|
82
|
-
* @throws {Error} If output directory path is invalid
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```typescript
|
|
86
|
-
* umbrella.write('./dist/my-umbrella-chart');
|
|
87
|
-
* ```
|
|
88
|
-
*
|
|
89
|
-
* @since 0.2.0
|
|
90
|
-
*/
|
|
91
21
|
write(outDir: string): void;
|
|
92
22
|
private writeParentChart;
|
|
93
23
|
private writeParentValues;
|
|
94
24
|
private deepMerge;
|
|
95
25
|
private generateNotesTemplate;
|
|
96
26
|
}
|
|
97
|
-
//# sourceMappingURL=umbrellaRutter.d.ts.map
|
|
@@ -1,101 +1,35 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview UmbrellaRutter class for managing Helm umbrella charts with multiple subcharts
|
|
3
|
-
* @since 0.2.0
|
|
4
|
-
*/
|
|
5
1
|
import { writeFileSync, mkdirSync } from 'fs';
|
|
6
2
|
import { join } from 'path';
|
|
7
3
|
import YAML from 'yaml';
|
|
8
4
|
import { SecurityUtils } from './security.js';
|
|
9
|
-
/**
|
|
10
|
-
* UmbrellaRutter manages multiple Rutter instances as subcharts
|
|
11
|
-
* to create Helm umbrella charts with dependencies.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```typescript
|
|
15
|
-
* const umbrella = new UmbrellaRutter({
|
|
16
|
-
* meta: { name: 'my-app', version: '1.0.0' },
|
|
17
|
-
* subcharts: [
|
|
18
|
-
* { name: 'frontend', rutter: frontendChart },
|
|
19
|
-
* { name: 'backend', rutter: backendChart }
|
|
20
|
-
* ]
|
|
21
|
-
* });
|
|
22
|
-
*
|
|
23
|
-
* umbrella.write('./charts/my-app');
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* @since 0.2.0
|
|
27
|
-
*/
|
|
28
5
|
export class UmbrellaRutter {
|
|
29
|
-
/**
|
|
30
|
-
* Creates a new UmbrellaRutter instance
|
|
31
|
-
*
|
|
32
|
-
* @param props - Configuration properties for the umbrella chart
|
|
33
|
-
* @throws {Error} If chart metadata is invalid
|
|
34
|
-
*
|
|
35
|
-
* @since 0.2.0
|
|
36
|
-
*/
|
|
37
6
|
constructor(props) {
|
|
38
|
-
// Validate chart metadata
|
|
39
7
|
this.validateMetadata(props.meta);
|
|
40
8
|
this.props = props;
|
|
41
9
|
}
|
|
42
10
|
validateMetadata(meta) {
|
|
43
|
-
// Validate chart name follows Helm conventions
|
|
44
|
-
// eslint-disable-next-line security/detect-unsafe-regex -- Safe regex for chart name validation
|
|
45
11
|
if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(meta.name)) {
|
|
46
12
|
throw new Error('Chart name must contain only lowercase letters, numbers, and dashes');
|
|
47
13
|
}
|
|
48
|
-
// Validate semantic versioning
|
|
49
|
-
// eslint-disable-next-line security/detect-unsafe-regex -- Safe regex for semver validation
|
|
50
14
|
if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/.test(meta.version)) {
|
|
51
15
|
throw new Error('Chart version must follow semantic versioning (e.g., 1.0.0)');
|
|
52
16
|
}
|
|
53
17
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Write the umbrella chart with all subcharts to the output directory
|
|
56
|
-
*
|
|
57
|
-
* Creates the complete umbrella chart structure including:
|
|
58
|
-
* - Parent Chart.yaml with dependencies
|
|
59
|
-
* - Parent values.yaml with global and subchart values
|
|
60
|
-
* - Individual subchart directories under charts/
|
|
61
|
-
* - NOTES.txt template
|
|
62
|
-
*
|
|
63
|
-
* @param outDir - Output directory path for the umbrella chart
|
|
64
|
-
* @throws {Error} If output directory path is invalid
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```typescript
|
|
68
|
-
* umbrella.write('./dist/my-umbrella-chart');
|
|
69
|
-
* ```
|
|
70
|
-
*
|
|
71
|
-
* @since 0.2.0
|
|
72
|
-
*/
|
|
73
18
|
write(outDir) {
|
|
74
|
-
// Validate output directory path
|
|
75
19
|
const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd());
|
|
76
|
-
// Create output directory structure
|
|
77
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
78
20
|
mkdirSync(validatedOutDir, { recursive: true });
|
|
79
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
80
21
|
mkdirSync(join(validatedOutDir, 'charts'), { recursive: true });
|
|
81
|
-
// Write each subchart to charts/ directory
|
|
82
22
|
for (const subchart of this.props.subcharts) {
|
|
83
|
-
// Validate subchart name using centralized validation
|
|
84
23
|
if (!SecurityUtils.isValidSubchartName(subchart.name)) {
|
|
85
24
|
throw new Error(`Invalid subchart name: ${SecurityUtils.sanitizeLogMessage(subchart.name)}`);
|
|
86
25
|
}
|
|
87
|
-
const sanitizedName = subchart.name;
|
|
26
|
+
const sanitizedName = subchart.name;
|
|
88
27
|
const subchartDir = join(validatedOutDir, 'charts', sanitizedName);
|
|
89
28
|
subchart.rutter.write(subchartDir);
|
|
90
29
|
}
|
|
91
|
-
// Generate parent Chart.yaml with dependencies
|
|
92
30
|
this.writeParentChart(validatedOutDir);
|
|
93
|
-
// Generate parent values.yaml
|
|
94
31
|
this.writeParentValues(validatedOutDir);
|
|
95
|
-
// Generate empty templates directory (umbrella charts typically don't have templates)
|
|
96
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
97
32
|
mkdirSync(join(validatedOutDir, 'templates'), { recursive: true });
|
|
98
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
99
33
|
writeFileSync(join(validatedOutDir, 'templates', 'NOTES.txt'), this.generateNotesTemplate());
|
|
100
34
|
}
|
|
101
35
|
writeParentChart(outDir) {
|
|
@@ -111,7 +45,6 @@ export class UmbrellaRutter {
|
|
|
111
45
|
sources: this.props.meta.sources,
|
|
112
46
|
maintainers: this.props.meta.maintainers,
|
|
113
47
|
dependencies: this.props.subcharts.map((subchart) => {
|
|
114
|
-
// Subchart name already validated in write() method
|
|
115
48
|
return {
|
|
116
49
|
name: subchart.name,
|
|
117
50
|
version: subchart.version || '0.1.0',
|
|
@@ -121,17 +54,13 @@ export class UmbrellaRutter {
|
|
|
121
54
|
};
|
|
122
55
|
}),
|
|
123
56
|
};
|
|
124
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
125
57
|
writeFileSync(join(outDir, 'Chart.yaml'), YAML.stringify(chartYaml));
|
|
126
58
|
}
|
|
127
59
|
writeParentValues(outDir) {
|
|
128
60
|
const values = {
|
|
129
|
-
// Global values that can be shared across subcharts
|
|
130
61
|
global: {},
|
|
131
|
-
// Individual subchart values
|
|
132
62
|
...this.props.defaultValues,
|
|
133
63
|
};
|
|
134
|
-
// Add subchart-specific value sections
|
|
135
64
|
for (const subchart of this.props.subcharts) {
|
|
136
65
|
if (!(subchart.name in values)) {
|
|
137
66
|
values[subchart.name] = {
|
|
@@ -139,15 +68,11 @@ export class UmbrellaRutter {
|
|
|
139
68
|
};
|
|
140
69
|
}
|
|
141
70
|
}
|
|
142
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
143
71
|
writeFileSync(join(outDir, 'values.yaml'), YAML.stringify(values));
|
|
144
|
-
// Write environment-specific values files
|
|
145
72
|
if (this.props.envValues) {
|
|
146
73
|
for (const [env, envVals] of Object.entries(this.props.envValues)) {
|
|
147
|
-
// Use centralized environment name sanitization
|
|
148
74
|
const sanitizedEnv = SecurityUtils.sanitizeEnvironmentName(env);
|
|
149
75
|
const envValues = this.deepMerge(values, envVals);
|
|
150
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
151
76
|
writeFileSync(join(outDir, `values-${sanitizedEnv}.yaml`), YAML.stringify(envValues));
|
|
152
77
|
}
|
|
153
78
|
}
|
|
@@ -156,13 +81,9 @@ export class UmbrellaRutter {
|
|
|
156
81
|
const result = { ...target };
|
|
157
82
|
for (const [key, value] of Object.entries(source)) {
|
|
158
83
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
159
|
-
|
|
160
|
-
result[key] = this.deepMerge(
|
|
161
|
-
// eslint-disable-next-line security/detect-object-injection -- Safe object property access in deep merge
|
|
162
|
-
result[key] || {}, value);
|
|
84
|
+
result[key] = this.deepMerge(result[key] || {}, value);
|
|
163
85
|
}
|
|
164
86
|
else {
|
|
165
|
-
// eslint-disable-next-line security/detect-object-injection -- Safe object property access in deep merge
|
|
166
87
|
result[key] = value;
|
|
167
88
|
}
|
|
168
89
|
}
|
|
@@ -184,4 +105,3 @@ ${this.props.subcharts.map((s) => ` - charts/${s.name}/README.md`).join('\n')}
|
|
|
184
105
|
`;
|
|
185
106
|
}
|
|
186
107
|
}
|
|
187
|
-
//# sourceMappingURL=umbrellaRutter.js.map
|