@tamyla/clodo-framework 1.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 +564 -0
- package/LICENSE +21 -0
- package/README.md +1393 -0
- package/bin/README.md +71 -0
- package/bin/clodo-service.js +416 -0
- package/bin/security/security-cli.js +96 -0
- package/bin/service-management/README.md +74 -0
- package/bin/service-management/create-service.js +129 -0
- package/bin/service-management/init-service.js +102 -0
- package/bin/service-management/init-service.js.backup +889 -0
- package/bin/shared/config/customer-cli.js +293 -0
- package/dist/config/ConfigurationManager.js +159 -0
- package/dist/config/CustomerConfigCLI.js +220 -0
- package/dist/config/FeatureManager.js +426 -0
- package/dist/config/customers.js +441 -0
- package/dist/config/domains.js +180 -0
- package/dist/config/features.js +225 -0
- package/dist/config/index.js +6 -0
- package/dist/database/database-orchestrator.js +730 -0
- package/dist/database/index.js +4 -0
- package/dist/deployment/auditor.js +971 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/rollback-manager.js +523 -0
- package/dist/deployment/testers/api-tester.js +80 -0
- package/dist/deployment/testers/auth-tester.js +129 -0
- package/dist/deployment/testers/core.js +217 -0
- package/dist/deployment/testers/database-tester.js +105 -0
- package/dist/deployment/testers/index.js +74 -0
- package/dist/deployment/testers/load-tester.js +120 -0
- package/dist/deployment/testers/performance-tester.js +105 -0
- package/dist/deployment/validator.js +558 -0
- package/dist/deployment/wrangler-deployer.js +574 -0
- package/dist/handlers/GenericRouteHandler.js +532 -0
- package/dist/index.js +39 -0
- package/dist/migration/MigrationAdapters.js +562 -0
- package/dist/modules/ModuleManager.js +668 -0
- package/dist/modules/security.js +98 -0
- package/dist/orchestration/cross-domain-coordinator.js +1083 -0
- package/dist/orchestration/index.js +5 -0
- package/dist/orchestration/modules/DeploymentCoordinator.js +258 -0
- package/dist/orchestration/modules/DomainResolver.js +196 -0
- package/dist/orchestration/modules/StateManager.js +332 -0
- package/dist/orchestration/multi-domain-orchestrator.js +255 -0
- package/dist/routing/EnhancedRouter.js +158 -0
- package/dist/schema/SchemaManager.js +778 -0
- package/dist/security/ConfigurationValidator.js +490 -0
- package/dist/security/DeploymentManager.js +208 -0
- package/dist/security/SecretGenerator.js +142 -0
- package/dist/security/SecurityCLI.js +228 -0
- package/dist/security/index.js +51 -0
- package/dist/security/patterns/environment-rules.js +66 -0
- package/dist/security/patterns/insecure-patterns.js +21 -0
- package/dist/service-management/ConfirmationEngine.js +411 -0
- package/dist/service-management/ErrorTracker.js +294 -0
- package/dist/service-management/GenerationEngine.js +3109 -0
- package/dist/service-management/InputCollector.js +237 -0
- package/dist/service-management/ServiceCreator.js +229 -0
- package/dist/service-management/ServiceInitializer.js +448 -0
- package/dist/service-management/ServiceOrchestrator.js +638 -0
- package/dist/service-management/handlers/ConfigMutator.js +130 -0
- package/dist/service-management/handlers/ConfirmationHandler.js +71 -0
- package/dist/service-management/handlers/GenerationHandler.js +80 -0
- package/dist/service-management/handlers/InputHandler.js +59 -0
- package/dist/service-management/handlers/ValidationHandler.js +203 -0
- package/dist/service-management/index.js +7 -0
- package/dist/services/GenericDataService.js +488 -0
- package/dist/shared/cloudflare/domain-discovery.js +562 -0
- package/dist/shared/cloudflare/domain-manager.js +912 -0
- package/dist/shared/cloudflare/index.js +8 -0
- package/dist/shared/cloudflare/ops.js +387 -0
- package/dist/shared/config/cache.js +1167 -0
- package/dist/shared/config/command-config-manager.js +174 -0
- package/dist/shared/config/customer-cli.js +258 -0
- package/dist/shared/config/index.js +9 -0
- package/dist/shared/config/manager.js +289 -0
- package/dist/shared/database/connection-manager.js +338 -0
- package/dist/shared/database/index.js +7 -0
- package/dist/shared/database/orchestrator.js +632 -0
- package/dist/shared/deployment/auditor.js +971 -0
- package/dist/shared/deployment/index.js +10 -0
- package/dist/shared/deployment/rollback-manager.js +523 -0
- package/dist/shared/deployment/validator.js +558 -0
- package/dist/shared/index.js +32 -0
- package/dist/shared/monitoring/health-checker.js +250 -0
- package/dist/shared/monitoring/index.js +8 -0
- package/dist/shared/monitoring/memory-manager.js +382 -0
- package/dist/shared/monitoring/production-monitor.js +390 -0
- package/dist/shared/production-tester/api-tester.js +80 -0
- package/dist/shared/production-tester/auth-tester.js +129 -0
- package/dist/shared/production-tester/core.js +217 -0
- package/dist/shared/production-tester/database-tester.js +105 -0
- package/dist/shared/production-tester/index.js +74 -0
- package/dist/shared/production-tester/load-tester.js +120 -0
- package/dist/shared/production-tester/performance-tester.js +105 -0
- package/dist/shared/security/api-token-manager.js +296 -0
- package/dist/shared/security/index.js +8 -0
- package/dist/shared/security/secret-generator.js +918 -0
- package/dist/shared/security/secure-token-manager.js +379 -0
- package/dist/shared/utils/error-recovery.js +240 -0
- package/dist/shared/utils/graceful-shutdown-manager.js +380 -0
- package/dist/shared/utils/index.js +9 -0
- package/dist/shared/utils/interactive-prompts.js +134 -0
- package/dist/shared/utils/rate-limiter.js +249 -0
- package/dist/utils/ErrorHandler.js +173 -0
- package/dist/utils/deployment/config-cache.js +1160 -0
- package/dist/utils/deployment/index.js +6 -0
- package/dist/utils/deployment/interactive-prompts.js +97 -0
- package/dist/utils/deployment/secret-generator.js +896 -0
- package/dist/utils/dirname-helper.js +35 -0
- package/dist/utils/domain-config.js +159 -0
- package/dist/utils/error-recovery.js +240 -0
- package/dist/utils/esm-helper.js +52 -0
- package/dist/utils/framework-config.js +481 -0
- package/dist/utils/graceful-shutdown-manager.js +379 -0
- package/dist/utils/health-checker.js +114 -0
- package/dist/utils/index.js +36 -0
- package/dist/utils/prompt-handler.js +98 -0
- package/dist/utils/usage-tracker.js +252 -0
- package/dist/utils/validation.js +112 -0
- package/dist/version/VersionDetector.js +723 -0
- package/dist/worker/index.js +4 -0
- package/dist/worker/integration.js +332 -0
- package/docs/FRAMEWORK-ARCHITECTURE-OVERVIEW.md +206 -0
- package/docs/INTEGRATION_GUIDE.md +2045 -0
- package/docs/README.md +82 -0
- package/docs/SECURITY.md +242 -0
- package/docs/deployment/deployment-guide.md +540 -0
- package/docs/overview.md +280 -0
- package/package.json +176 -0
- package/types/index.d.ts +575 -0
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Secret Generation Module
|
|
3
|
+
* Enterprise-grade cryptographic secret management with multi-domain and cross-environment capabilities
|
|
4
|
+
*
|
|
5
|
+
* Enhanced from deprecated smart-deployment.js with advanced features:
|
|
6
|
+
* - Multi-domain secret coordination
|
|
7
|
+
* - Cross-environment secret management
|
|
8
|
+
* - Multiple output formats (env, JSON, wrangler)
|
|
9
|
+
* - Secret rotation planning
|
|
10
|
+
* - Audit logging and validation
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { randomBytes, createHash } from 'crypto';
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, appendFileSync } from 'fs';
|
|
15
|
+
import { join, dirname } from 'path';
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
const SECRET_CONFIGS = {
|
|
21
|
+
'AUTH_JWT_SECRET': {
|
|
22
|
+
length: 64,
|
|
23
|
+
description: 'JWT token signing secret',
|
|
24
|
+
scope: 'critical'
|
|
25
|
+
},
|
|
26
|
+
'X_SERVICE_KEY': {
|
|
27
|
+
length: 64,
|
|
28
|
+
description: 'Service authentication key',
|
|
29
|
+
scope: 'critical'
|
|
30
|
+
},
|
|
31
|
+
'AUTH_SERVICE_API_KEY': {
|
|
32
|
+
length: 48,
|
|
33
|
+
description: 'Auth service API key',
|
|
34
|
+
scope: 'standard'
|
|
35
|
+
},
|
|
36
|
+
'LOGGER_SERVICE_API_KEY': {
|
|
37
|
+
length: 48,
|
|
38
|
+
description: 'Logger service API key',
|
|
39
|
+
scope: 'standard'
|
|
40
|
+
},
|
|
41
|
+
'CONTENT_SKIMMER_API_KEY': {
|
|
42
|
+
length: 48,
|
|
43
|
+
description: 'Content service API key',
|
|
44
|
+
scope: 'standard'
|
|
45
|
+
},
|
|
46
|
+
'CROSS_DOMAIN_AUTH_KEY': {
|
|
47
|
+
length: 64,
|
|
48
|
+
description: 'Cross-domain authentication key',
|
|
49
|
+
scope: 'critical'
|
|
50
|
+
},
|
|
51
|
+
'WEBHOOK_SIGNATURE_KEY': {
|
|
52
|
+
length: 32,
|
|
53
|
+
description: 'Webhook signature verification key',
|
|
54
|
+
scope: 'standard'
|
|
55
|
+
},
|
|
56
|
+
'FILE_ENCRYPTION_KEY': {
|
|
57
|
+
length: 64,
|
|
58
|
+
description: 'File encryption key',
|
|
59
|
+
scope: 'critical'
|
|
60
|
+
},
|
|
61
|
+
'SESSION_ENCRYPTION_KEY': {
|
|
62
|
+
length: 48,
|
|
63
|
+
description: 'Session encryption key',
|
|
64
|
+
scope: 'standard'
|
|
65
|
+
},
|
|
66
|
+
'API_RATE_LIMIT_KEY': {
|
|
67
|
+
length: 32,
|
|
68
|
+
description: 'API rate limiting key',
|
|
69
|
+
scope: 'standard'
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Enhanced Secret Manager Class
|
|
75
|
+
* Provides enterprise-grade secret management with multi-domain coordination
|
|
76
|
+
*/
|
|
77
|
+
export class EnhancedSecretManager {
|
|
78
|
+
constructor(options = {}) {
|
|
79
|
+
this.projectRoot = options.projectRoot || join(__dirname, '..', '..');
|
|
80
|
+
this.dryRun = options.dryRun || false;
|
|
81
|
+
this.options = options;
|
|
82
|
+
this.config = null;
|
|
83
|
+
|
|
84
|
+
// Multi-domain and environment tracking
|
|
85
|
+
this.domainSecrets = new Map();
|
|
86
|
+
this.environmentConfigs = new Map();
|
|
87
|
+
this.rotationSchedule = new Map();
|
|
88
|
+
|
|
89
|
+
// Paths for secret management - will be updated with framework config in initialize()
|
|
90
|
+
this.secretPaths = {
|
|
91
|
+
root: join(this.projectRoot, 'secrets'),
|
|
92
|
+
distribution: join(this.projectRoot, 'secrets', 'distribution'),
|
|
93
|
+
backups: join(this.projectRoot, 'secrets', 'backups'),
|
|
94
|
+
audit: join(this.projectRoot, 'logs', 'secrets-audit.log'),
|
|
95
|
+
templates: join(this.projectRoot, 'secrets', 'templates')
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Initialize with framework configuration
|
|
101
|
+
*/
|
|
102
|
+
async initialize() {
|
|
103
|
+
// Import framework config for consistent timing and retry settings
|
|
104
|
+
const {
|
|
105
|
+
frameworkConfig
|
|
106
|
+
} = await import('../../../src/utils/framework-config.js');
|
|
107
|
+
const timing = frameworkConfig.getTiming();
|
|
108
|
+
const security = frameworkConfig.getSecurity();
|
|
109
|
+
const configPaths = frameworkConfig.getPaths();
|
|
110
|
+
|
|
111
|
+
// Update paths with framework configuration
|
|
112
|
+
if (this.projectRoot) {
|
|
113
|
+
this.secretPaths = {
|
|
114
|
+
root: join(this.projectRoot, configPaths.secureTokens.replace('generated/cache/tokens', 'secrets')),
|
|
115
|
+
distribution: join(this.projectRoot, configPaths.secureTokens.replace('generated/cache/tokens', 'secrets'), 'distribution'),
|
|
116
|
+
backups: join(this.projectRoot, configPaths.backups, 'secrets'),
|
|
117
|
+
audit: join(this.projectRoot, configPaths.logs, 'secrets-audit.log'),
|
|
118
|
+
templates: join(this.projectRoot, configPaths.secureTokens.replace('generated/cache/tokens', 'secrets'), 'templates')
|
|
119
|
+
};
|
|
120
|
+
console.log(`🔐 Secret paths updated with framework config`);
|
|
121
|
+
}
|
|
122
|
+
this.config = {
|
|
123
|
+
retryAttempts: this.options.retryAttempts || timing.retryAttempts,
|
|
124
|
+
retryDelay: this.options.retryDelay || timing.retryDelay,
|
|
125
|
+
secretRotationInterval: this.options.secretRotationInterval || security.secretRotationInterval,
|
|
126
|
+
...this.options
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Supported output formats
|
|
130
|
+
this.outputFormats = {
|
|
131
|
+
env: {
|
|
132
|
+
extension: '.env',
|
|
133
|
+
description: 'Environment variables'
|
|
134
|
+
},
|
|
135
|
+
json: {
|
|
136
|
+
extension: '.json',
|
|
137
|
+
description: 'JSON format'
|
|
138
|
+
},
|
|
139
|
+
wrangler: {
|
|
140
|
+
extension: '.sh',
|
|
141
|
+
description: 'Wrangler CLI commands'
|
|
142
|
+
},
|
|
143
|
+
powershell: {
|
|
144
|
+
extension: '.ps1',
|
|
145
|
+
description: 'PowerShell commands'
|
|
146
|
+
},
|
|
147
|
+
docker: {
|
|
148
|
+
extension: '.env',
|
|
149
|
+
description: 'Docker environment file'
|
|
150
|
+
},
|
|
151
|
+
kubernetes: {
|
|
152
|
+
extension: '.yaml',
|
|
153
|
+
description: 'Kubernetes secrets'
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
this.initializeSecretManager();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Initialize enhanced secret manager
|
|
161
|
+
*/
|
|
162
|
+
initializeSecretManager() {
|
|
163
|
+
console.log('🔐 Enhanced Secret Manager v2.0');
|
|
164
|
+
console.log('===============================');
|
|
165
|
+
console.log(`📁 Secret Root: ${this.secretPaths.root}`);
|
|
166
|
+
console.log(`🔍 Mode: ${this.dryRun ? 'DRY RUN' : 'LIVE OPERATIONS'}`);
|
|
167
|
+
console.log(`📊 Formats: ${Object.keys(this.outputFormats).join(', ')}`);
|
|
168
|
+
console.log('');
|
|
169
|
+
|
|
170
|
+
// Create directories
|
|
171
|
+
Object.values(this.secretPaths).forEach(path => {
|
|
172
|
+
if (!path.endsWith('.log')) {
|
|
173
|
+
this.ensureDirectory(path);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
this.logSecretEvent('MANAGER_INITIALIZED', 'SYSTEM', {
|
|
177
|
+
formats: Object.keys(this.outputFormats),
|
|
178
|
+
mode: this.dryRun ? 'DRY_RUN' : 'LIVE'
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Generate domain-specific secrets with environment coordination
|
|
184
|
+
* @param {string} domain - Domain name
|
|
185
|
+
* @param {string} environment - Environment (production, staging, development)
|
|
186
|
+
* @param {Object} options - Generation options
|
|
187
|
+
* @returns {Promise<Object>} Generated secrets and metadata
|
|
188
|
+
*/
|
|
189
|
+
async generateDomainSpecificSecrets(domain, environment = 'production', options = {}) {
|
|
190
|
+
const {
|
|
191
|
+
customConfigs = {},
|
|
192
|
+
reuseExisting = true,
|
|
193
|
+
rotateAll = false,
|
|
194
|
+
formats = ['env', 'json', 'wrangler']
|
|
195
|
+
} = options;
|
|
196
|
+
console.log(`🔑 Generating secrets for ${domain} (${environment})`);
|
|
197
|
+
console.log(` 🔄 Reuse Existing: ${reuseExisting}`);
|
|
198
|
+
console.log(` 🔁 Rotate All: ${rotateAll}`);
|
|
199
|
+
console.log(` 📋 Formats: ${formats.join(', ')}`);
|
|
200
|
+
try {
|
|
201
|
+
// Load existing secrets if requested
|
|
202
|
+
let existingSecrets = {};
|
|
203
|
+
if (reuseExisting && !rotateAll) {
|
|
204
|
+
const existing = await this.loadDomainSecrets(domain, environment);
|
|
205
|
+
if (existing) {
|
|
206
|
+
existingSecrets = existing.secrets;
|
|
207
|
+
console.log(` 📂 Loaded ${Object.keys(existingSecrets).length} existing secrets`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Merge configurations
|
|
212
|
+
const configs = {
|
|
213
|
+
...SECRET_CONFIGS,
|
|
214
|
+
...customConfigs
|
|
215
|
+
};
|
|
216
|
+
const secrets = {
|
|
217
|
+
...existingSecrets
|
|
218
|
+
};
|
|
219
|
+
let generated = 0;
|
|
220
|
+
|
|
221
|
+
// Generate missing or rotated secrets
|
|
222
|
+
for (const [key, config] of Object.entries(configs)) {
|
|
223
|
+
if (!secrets[key] || rotateAll) {
|
|
224
|
+
secrets[key] = this.generateSecretValue(config.length);
|
|
225
|
+
generated++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
console.log(` 🔑 Generated ${generated} new secrets, reused ${Object.keys(secrets).length - generated}`);
|
|
229
|
+
|
|
230
|
+
// Create secret metadata
|
|
231
|
+
const secretData = {
|
|
232
|
+
domain,
|
|
233
|
+
environment,
|
|
234
|
+
generated: new Date().toISOString(),
|
|
235
|
+
generatedBy: 'EnhancedSecretManager v2.0',
|
|
236
|
+
secretCount: Object.keys(secrets).length,
|
|
237
|
+
newSecrets: generated,
|
|
238
|
+
formats: formats,
|
|
239
|
+
...secrets
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Save secrets
|
|
243
|
+
const savedFile = await this.saveDomainSecrets(domain, environment, secretData);
|
|
244
|
+
|
|
245
|
+
// Generate distribution files
|
|
246
|
+
const distribution = await this.generateSecretDistribution(domain, environment, secrets, formats);
|
|
247
|
+
|
|
248
|
+
// Deploy secrets if not dry run
|
|
249
|
+
let deployment = null;
|
|
250
|
+
if (!this.dryRun && options.deploy) {
|
|
251
|
+
deployment = await this.deploySecretsToCloudflare(domain, environment, secrets);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Update domain tracking
|
|
255
|
+
this.domainSecrets.set(`${domain}-${environment}`, {
|
|
256
|
+
secrets,
|
|
257
|
+
generated: new Date(),
|
|
258
|
+
formats,
|
|
259
|
+
deployment
|
|
260
|
+
});
|
|
261
|
+
this.logSecretEvent('SECRETS_GENERATED', domain, {
|
|
262
|
+
environment,
|
|
263
|
+
secretCount: Object.keys(secrets).length,
|
|
264
|
+
newSecrets: generated,
|
|
265
|
+
formats
|
|
266
|
+
});
|
|
267
|
+
return {
|
|
268
|
+
domain,
|
|
269
|
+
environment,
|
|
270
|
+
secrets,
|
|
271
|
+
metadata: secretData,
|
|
272
|
+
savedFile,
|
|
273
|
+
distribution,
|
|
274
|
+
deployment,
|
|
275
|
+
summary: {
|
|
276
|
+
total: Object.keys(secrets).length,
|
|
277
|
+
generated,
|
|
278
|
+
reused: Object.keys(secrets).length - generated
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
} catch (error) {
|
|
282
|
+
this.logSecretEvent('SECRET_GENERATION_FAILED', domain, {
|
|
283
|
+
environment,
|
|
284
|
+
error: error.message
|
|
285
|
+
});
|
|
286
|
+
throw new Error(`Secret generation failed for ${domain}: ${error.message}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Coordinate secrets across multiple environments for a domain
|
|
292
|
+
* @param {string} domain - Domain name
|
|
293
|
+
* @param {Array} environments - Environments to coordinate
|
|
294
|
+
* @param {Object} options - Coordination options
|
|
295
|
+
* @returns {Promise<Object>} Coordination results
|
|
296
|
+
*/
|
|
297
|
+
async coordinateSecretsAcrossEnvironments(domain, environments = ['development', 'staging', 'production'], options = {}) {
|
|
298
|
+
console.log(`🌐 Cross-Environment Secret Coordination for ${domain}`);
|
|
299
|
+
console.log(` 🌍 Environments: ${environments.join(', ')}`);
|
|
300
|
+
const {
|
|
301
|
+
syncCriticalSecrets = true,
|
|
302
|
+
generateUniquePerEnv = true,
|
|
303
|
+
formats = ['env', 'json', 'wrangler']
|
|
304
|
+
} = options;
|
|
305
|
+
const results = {
|
|
306
|
+
domain,
|
|
307
|
+
environments: {},
|
|
308
|
+
sharedSecrets: {},
|
|
309
|
+
coordinationId: this.generateCoordinationId(domain),
|
|
310
|
+
startTime: new Date()
|
|
311
|
+
};
|
|
312
|
+
try {
|
|
313
|
+
// Generate base secrets for production first
|
|
314
|
+
const productionSecrets = await this.generateDomainSpecificSecrets(domain, 'production', {
|
|
315
|
+
...options,
|
|
316
|
+
formats
|
|
317
|
+
});
|
|
318
|
+
results.environments.production = productionSecrets;
|
|
319
|
+
|
|
320
|
+
// Extract critical secrets to share across environments
|
|
321
|
+
if (syncCriticalSecrets) {
|
|
322
|
+
const criticalConfigs = Object.entries(SECRET_CONFIGS).filter(([, config]) => config.scope === 'critical');
|
|
323
|
+
for (const [key] of criticalConfigs) {
|
|
324
|
+
if (productionSecrets.secrets[key]) {
|
|
325
|
+
results.sharedSecrets[key] = productionSecrets.secrets[key];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
console.log(` 🔗 Sharing ${Object.keys(results.sharedSecrets).length} critical secrets across environments`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Process other environments
|
|
332
|
+
for (const env of environments) {
|
|
333
|
+
if (env === 'production') continue;
|
|
334
|
+
console.log(`\n 🌍 Processing ${env} environment...`);
|
|
335
|
+
const envOptions = {
|
|
336
|
+
...options,
|
|
337
|
+
customConfigs: generateUniquePerEnv ? {} : results.sharedSecrets,
|
|
338
|
+
reuseExisting: !generateUniquePerEnv,
|
|
339
|
+
formats
|
|
340
|
+
};
|
|
341
|
+
const envSecrets = await this.generateDomainSpecificSecrets(domain, env, envOptions);
|
|
342
|
+
|
|
343
|
+
// Override with shared secrets if coordinating
|
|
344
|
+
if (syncCriticalSecrets) {
|
|
345
|
+
Object.assign(envSecrets.secrets, results.sharedSecrets);
|
|
346
|
+
|
|
347
|
+
// Re-save with coordinated secrets
|
|
348
|
+
await this.saveDomainSecrets(domain, env, {
|
|
349
|
+
...envSecrets.metadata,
|
|
350
|
+
...envSecrets.secrets,
|
|
351
|
+
coordinated: true,
|
|
352
|
+
coordinationId: results.coordinationId
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Re-generate distribution with coordinated secrets
|
|
356
|
+
envSecrets.distribution = await this.generateSecretDistribution(domain, env, envSecrets.secrets, formats);
|
|
357
|
+
}
|
|
358
|
+
results.environments[env] = envSecrets;
|
|
359
|
+
}
|
|
360
|
+
results.endTime = new Date();
|
|
361
|
+
results.duration = (results.endTime - results.startTime) / 1000;
|
|
362
|
+
this.logSecretEvent('CROSS_ENVIRONMENT_COORDINATION_COMPLETED', domain, {
|
|
363
|
+
coordinationId: results.coordinationId,
|
|
364
|
+
environments: environments,
|
|
365
|
+
sharedSecrets: Object.keys(results.sharedSecrets).length,
|
|
366
|
+
duration: results.duration
|
|
367
|
+
});
|
|
368
|
+
console.log(`\n✅ Cross-environment coordination completed (${results.duration.toFixed(1)}s)`);
|
|
369
|
+
console.log(` 🔗 Shared secrets: ${Object.keys(results.sharedSecrets).length}`);
|
|
370
|
+
console.log(` 🌍 Environments: ${Object.keys(results.environments).length}`);
|
|
371
|
+
return results;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
this.logSecretEvent('CROSS_ENVIRONMENT_COORDINATION_FAILED', domain, {
|
|
374
|
+
coordinationId: results.coordinationId,
|
|
375
|
+
error: error.message
|
|
376
|
+
});
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Deploy secrets to Cloudflare Workers across environments
|
|
383
|
+
* @param {string} domain - Domain name
|
|
384
|
+
* @param {string} environment - Environment
|
|
385
|
+
* @param {Object} secrets - Secrets to deploy
|
|
386
|
+
* @returns {Promise<Object>} Deployment results
|
|
387
|
+
*/
|
|
388
|
+
async deploySecretsToCloudflare(domain, environment, secrets) {
|
|
389
|
+
console.log(` ☁️ Deploying ${Object.keys(secrets).length} secrets to Cloudflare (${environment})...`);
|
|
390
|
+
if (this.dryRun) {
|
|
391
|
+
console.log(` 🔍 DRY RUN: Would deploy secrets to ${environment}`);
|
|
392
|
+
return {
|
|
393
|
+
status: 'dry-run',
|
|
394
|
+
secretCount: Object.keys(secrets).length
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const results = {
|
|
398
|
+
domain,
|
|
399
|
+
environment,
|
|
400
|
+
deployed: [],
|
|
401
|
+
failed: [],
|
|
402
|
+
startTime: new Date()
|
|
403
|
+
};
|
|
404
|
+
try {
|
|
405
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
406
|
+
try {
|
|
407
|
+
await this.deploySingleSecret(key, value, environment);
|
|
408
|
+
results.deployed.push(key);
|
|
409
|
+
console.log(` ✅ ${key} deployed`);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
results.failed.push({
|
|
412
|
+
key,
|
|
413
|
+
error: error.message
|
|
414
|
+
});
|
|
415
|
+
console.log(` ❌ ${key} failed: ${error.message}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
results.endTime = new Date();
|
|
419
|
+
results.duration = (results.endTime - results.startTime) / 1000;
|
|
420
|
+
this.logSecretEvent('SECRETS_DEPLOYED', domain, {
|
|
421
|
+
environment,
|
|
422
|
+
deployed: results.deployed.length,
|
|
423
|
+
failed: results.failed.length,
|
|
424
|
+
duration: results.duration
|
|
425
|
+
});
|
|
426
|
+
return results;
|
|
427
|
+
} catch (error) {
|
|
428
|
+
this.logSecretEvent('SECRET_DEPLOYMENT_FAILED', domain, {
|
|
429
|
+
environment,
|
|
430
|
+
error: error.message
|
|
431
|
+
});
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Deploy single secret with retry logic
|
|
438
|
+
* @param {string} key - Secret key
|
|
439
|
+
* @param {string} value - Secret value
|
|
440
|
+
* @param {string} environment - Environment
|
|
441
|
+
*/
|
|
442
|
+
async deploySingleSecret(key, value, environment) {
|
|
443
|
+
for (let attempt = 1; attempt <= (this.config ? this.config.retryAttempts : 3); attempt++) {
|
|
444
|
+
try {
|
|
445
|
+
const command = process.platform === 'win32' ? `powershell -Command "Write-Output '${value}' | npx wrangler secret put ${key} --env ${environment}"` : `echo "${value}" | npx wrangler secret put ${key} --env ${environment}`;
|
|
446
|
+
execSync(command, {
|
|
447
|
+
shell: process.platform === 'win32' ? 'powershell.exe' : '/bin/bash',
|
|
448
|
+
stdio: 'pipe',
|
|
449
|
+
encoding: 'utf8',
|
|
450
|
+
timeout: this.config ? this.config.deploymentTimeout || 30000 : 30000
|
|
451
|
+
});
|
|
452
|
+
return;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
if (attempt === this.retryAttempts) {
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
console.log(` ⚠️ Attempt ${attempt} failed for ${key}, retrying...`);
|
|
458
|
+
await new Promise(resolve => setTimeout(resolve, this.config ? this.config.retryDelay : 2000));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Generate enhanced secret distribution files
|
|
465
|
+
* @param {string} domain - Domain name
|
|
466
|
+
* @param {string} environment - Environment
|
|
467
|
+
* @param {Object} secrets - Secrets to distribute
|
|
468
|
+
* @param {Array} formats - Output formats
|
|
469
|
+
* @returns {Promise<Object>} Distribution results
|
|
470
|
+
*/
|
|
471
|
+
async generateSecretDistribution(domain, environment, secrets, formats) {
|
|
472
|
+
const distDir = join(this.secretPaths.distribution, domain, environment);
|
|
473
|
+
this.ensureDirectory(distDir);
|
|
474
|
+
const distribution = {
|
|
475
|
+
domain,
|
|
476
|
+
environment,
|
|
477
|
+
directory: distDir,
|
|
478
|
+
files: {},
|
|
479
|
+
formats: formats,
|
|
480
|
+
generated: new Date()
|
|
481
|
+
};
|
|
482
|
+
console.log(` 📤 Generating distribution files in ${formats.join(', ')} formats...`);
|
|
483
|
+
try {
|
|
484
|
+
for (const format of formats) {
|
|
485
|
+
if (this.outputFormats[format]) {
|
|
486
|
+
const file = await this.generateFormatFile(distDir, domain, environment, secrets, format);
|
|
487
|
+
distribution.files[format] = file;
|
|
488
|
+
console.log(` 📄 ${format}: ${file.filename}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Generate comprehensive README
|
|
493
|
+
const readme = await this.generateDistributionReadme(domain, environment, secrets, formats);
|
|
494
|
+
distribution.files.readme = readme;
|
|
495
|
+
this.logSecretEvent('DISTRIBUTION_GENERATED', domain, {
|
|
496
|
+
environment,
|
|
497
|
+
formats,
|
|
498
|
+
fileCount: Object.keys(distribution.files).length
|
|
499
|
+
});
|
|
500
|
+
return distribution;
|
|
501
|
+
} catch (error) {
|
|
502
|
+
this.logSecretEvent('DISTRIBUTION_GENERATION_FAILED', domain, {
|
|
503
|
+
environment,
|
|
504
|
+
error: error.message
|
|
505
|
+
});
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Generate specific format file
|
|
512
|
+
* @param {string} distDir - Distribution directory
|
|
513
|
+
* @param {string} domain - Domain name
|
|
514
|
+
* @param {string} environment - Environment
|
|
515
|
+
* @param {Object} secrets - Secrets
|
|
516
|
+
* @param {string} format - Format type
|
|
517
|
+
* @returns {Promise<Object>} File information
|
|
518
|
+
*/
|
|
519
|
+
async generateFormatFile(distDir, domain, environment, secrets, format) {
|
|
520
|
+
const formatConfig = this.outputFormats[format];
|
|
521
|
+
const filename = `secrets-${environment}${formatConfig.extension}`;
|
|
522
|
+
const filepath = join(distDir, filename);
|
|
523
|
+
let content = '';
|
|
524
|
+
switch (format) {
|
|
525
|
+
case 'env':
|
|
526
|
+
content = Object.entries(secrets).map(([key, value]) => `${key}=${value}`).join('\n');
|
|
527
|
+
break;
|
|
528
|
+
case 'json':
|
|
529
|
+
content = JSON.stringify(secrets, null, 2);
|
|
530
|
+
break;
|
|
531
|
+
case 'wrangler':
|
|
532
|
+
content = ['#!/bin/bash', `# Wrangler secret deployment for ${domain} (${environment})`, `# Generated: ${new Date().toISOString()}`, '', ...Object.entries(secrets).map(([key, value]) => `echo "${value}" | npx wrangler secret put ${key} --env ${environment}`)].join('\n');
|
|
533
|
+
break;
|
|
534
|
+
case 'powershell':
|
|
535
|
+
content = [`# PowerShell secret deployment for ${domain} (${environment})`, `# Generated: ${new Date().toISOString()}`, '', ...Object.entries(secrets).map(([key, value]) => `"${value}" | npx wrangler secret put ${key} --env ${environment}`)].join('\n');
|
|
536
|
+
break;
|
|
537
|
+
case 'docker':
|
|
538
|
+
content = Object.entries(secrets).map(([key, value]) => `${key}=${value}`).join('\n');
|
|
539
|
+
break;
|
|
540
|
+
case 'kubernetes':
|
|
541
|
+
{
|
|
542
|
+
const encodedSecrets = {};
|
|
543
|
+
Object.entries(secrets).forEach(([key, value]) => {
|
|
544
|
+
encodedSecrets[key] = Buffer.from(value).toString('base64');
|
|
545
|
+
});
|
|
546
|
+
content = ['apiVersion: v1', 'kind: Secret', 'metadata:', ` name: ${domain.replace(/\./g, '-')}-secrets-${environment}`, ` namespace: ${environment}`, 'type: Opaque', 'data:', ...Object.entries(encodedSecrets).map(([key, value]) => ` ${key}: ${value}`)].join('\n');
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
default:
|
|
550
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
551
|
+
}
|
|
552
|
+
writeFileSync(filepath, content);
|
|
553
|
+
return {
|
|
554
|
+
format,
|
|
555
|
+
filename,
|
|
556
|
+
filepath,
|
|
557
|
+
description: formatConfig.description,
|
|
558
|
+
size: content.length
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Generate comprehensive distribution README
|
|
564
|
+
* @param {string} domain - Domain name
|
|
565
|
+
* @param {string} environment - Environment
|
|
566
|
+
* @param {Object} secrets - Secrets
|
|
567
|
+
* @param {Array} formats - Formats
|
|
568
|
+
* @returns {Promise<Object>} README file info
|
|
569
|
+
*/
|
|
570
|
+
async generateDistributionReadme(domain, environment, secrets, formats) {
|
|
571
|
+
const readmeContent = `# Secret Distribution for ${domain} (${environment})
|
|
572
|
+
|
|
573
|
+
Generated: ${new Date().toISOString()}
|
|
574
|
+
Secret Count: ${Object.keys(secrets).length}
|
|
575
|
+
Formats: ${formats.join(', ')}
|
|
576
|
+
|
|
577
|
+
## 🔐 Generated Files
|
|
578
|
+
|
|
579
|
+
${formats.map(format => {
|
|
580
|
+
const config = this.outputFormats[format];
|
|
581
|
+
const filename = `secrets-${environment}${config.extension}`;
|
|
582
|
+
return `- **${filename}** - ${config.description}`;
|
|
583
|
+
}).join('\n')}
|
|
584
|
+
|
|
585
|
+
## 🚀 Usage Instructions
|
|
586
|
+
|
|
587
|
+
### Environment Variables (.env)
|
|
588
|
+
\`\`\`bash
|
|
589
|
+
# For Node.js applications
|
|
590
|
+
cp secrets-${environment}.env /path/to/your/app/.env
|
|
591
|
+
source secrets-${environment}.env
|
|
592
|
+
\`\`\`
|
|
593
|
+
|
|
594
|
+
### JSON Format
|
|
595
|
+
\`\`\`javascript
|
|
596
|
+
// For API consumption
|
|
597
|
+
const secrets = require('./secrets-${environment}.json');
|
|
598
|
+
console.log(secrets.AUTH_JWT_SECRET);
|
|
599
|
+
\`\`\`
|
|
600
|
+
|
|
601
|
+
### Cloudflare Workers Deployment
|
|
602
|
+
\`\`\`bash
|
|
603
|
+
# Linux/Mac
|
|
604
|
+
chmod +x secrets-${environment}.sh
|
|
605
|
+
./secrets-${environment}.sh
|
|
606
|
+
|
|
607
|
+
# Windows PowerShell
|
|
608
|
+
.\\secrets-${environment}.ps1
|
|
609
|
+
\`\`\`
|
|
610
|
+
|
|
611
|
+
### Docker Deployment
|
|
612
|
+
\`\`\`bash
|
|
613
|
+
# Use as environment file
|
|
614
|
+
docker run --env-file secrets-${environment}.env your-image
|
|
615
|
+
\`\`\`
|
|
616
|
+
|
|
617
|
+
### Kubernetes Deployment
|
|
618
|
+
\`\`\`bash
|
|
619
|
+
# Apply secret manifest
|
|
620
|
+
kubectl apply -f secrets-${environment}.yaml
|
|
621
|
+
\`\`\`
|
|
622
|
+
|
|
623
|
+
## 🛡️ Security Guidelines
|
|
624
|
+
|
|
625
|
+
- **Keep secrets secure** - Never commit to version control
|
|
626
|
+
- **Use secure channels** - Distribute via encrypted channels only
|
|
627
|
+
- **Rotate regularly** - Update secrets according to security policy
|
|
628
|
+
- **Monitor access** - Track secret usage and access patterns
|
|
629
|
+
- **Backup safely** - Store backups in encrypted, access-controlled locations
|
|
630
|
+
|
|
631
|
+
## 🔄 Secret Rotation
|
|
632
|
+
|
|
633
|
+
To rotate secrets for this domain and environment:
|
|
634
|
+
\`\`\`bash
|
|
635
|
+
# Rotate all secrets
|
|
636
|
+
node scripts/shared/secret-generator.js --domain ${domain} --environment ${environment} --rotate-all
|
|
637
|
+
|
|
638
|
+
# Rotate specific secret
|
|
639
|
+
node scripts/shared/secret-generator.js --domain ${domain} --environment ${environment} --rotate AUTH_JWT_SECRET
|
|
640
|
+
\`\`\`
|
|
641
|
+
|
|
642
|
+
## 📊 Secret Inventory
|
|
643
|
+
|
|
644
|
+
${Object.entries(SECRET_CONFIGS).map(([key, config]) => `- **${key}**: ${config.description} (${config.scope})`).join('\n')}
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
*Generated by Enhanced Secret Manager v2.0*
|
|
648
|
+
*For support: Check project documentation*
|
|
649
|
+
`;
|
|
650
|
+
const readmeFile = join(domain, environment, 'README.md');
|
|
651
|
+
const readmePath = join(this.secretPaths.distribution, readmeFile);
|
|
652
|
+
writeFileSync(readmePath, readmeContent);
|
|
653
|
+
return {
|
|
654
|
+
filename: 'README.md',
|
|
655
|
+
filepath: readmePath,
|
|
656
|
+
size: readmeContent.length
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Utility methods
|
|
661
|
+
|
|
662
|
+
generateSecretValue(length) {
|
|
663
|
+
return randomBytes(length / 2).toString('hex');
|
|
664
|
+
}
|
|
665
|
+
generateCoordinationId(domain) {
|
|
666
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
667
|
+
const hash = createHash('md5').update(domain).digest('hex').substring(0, 8);
|
|
668
|
+
return `coord-${domain}-${hash}-${timestamp}`;
|
|
669
|
+
}
|
|
670
|
+
async loadDomainSecrets(domain, environment) {
|
|
671
|
+
const filename = join(this.secretPaths.root, `${domain}-${environment}-secrets.json`);
|
|
672
|
+
if (!existsSync(filename)) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
const data = JSON.parse(readFileSync(filename, 'utf8'));
|
|
677
|
+
const {
|
|
678
|
+
domain: d,
|
|
679
|
+
environment: e,
|
|
680
|
+
generated,
|
|
681
|
+
generatedBy,
|
|
682
|
+
secretCount,
|
|
683
|
+
newSecrets,
|
|
684
|
+
formats,
|
|
685
|
+
...secrets
|
|
686
|
+
} = data;
|
|
687
|
+
return {
|
|
688
|
+
secrets,
|
|
689
|
+
metadata: {
|
|
690
|
+
domain: d,
|
|
691
|
+
environment: e,
|
|
692
|
+
generated,
|
|
693
|
+
generatedBy,
|
|
694
|
+
secretCount,
|
|
695
|
+
newSecrets,
|
|
696
|
+
formats
|
|
697
|
+
},
|
|
698
|
+
filename
|
|
699
|
+
};
|
|
700
|
+
} catch (error) {
|
|
701
|
+
throw new Error(`Failed to load secrets for ${domain} (${environment}): ${error.message}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async saveDomainSecrets(domain, environment, secretData) {
|
|
705
|
+
const filename = join(this.secretPaths.root, `${domain}-${environment}-secrets.json`);
|
|
706
|
+
if (this.dryRun) {
|
|
707
|
+
console.log(` 🔍 DRY RUN: Would save secrets to ${filename}`);
|
|
708
|
+
return filename;
|
|
709
|
+
}
|
|
710
|
+
writeFileSync(filename, JSON.stringify(secretData, null, 2));
|
|
711
|
+
console.log(` 💾 Secrets saved: ${filename}`);
|
|
712
|
+
return filename;
|
|
713
|
+
}
|
|
714
|
+
ensureDirectory(path) {
|
|
715
|
+
if (!existsSync(path)) {
|
|
716
|
+
mkdirSync(path, {
|
|
717
|
+
recursive: true
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
logSecretEvent(event, domain, details = {}) {
|
|
722
|
+
const logEntry = {
|
|
723
|
+
timestamp: new Date().toISOString(),
|
|
724
|
+
event,
|
|
725
|
+
domain,
|
|
726
|
+
details,
|
|
727
|
+
user: process.env.USER || process.env.USERNAME || 'system'
|
|
728
|
+
};
|
|
729
|
+
try {
|
|
730
|
+
const logLine = JSON.stringify(logEntry) + '\n';
|
|
731
|
+
if (existsSync(this.secretPaths.audit)) {
|
|
732
|
+
appendFileSync(this.secretPaths.audit, logLine);
|
|
733
|
+
} else {
|
|
734
|
+
writeFileSync(this.secretPaths.audit, logLine);
|
|
735
|
+
}
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.warn(`⚠️ Failed to log secret event: ${error.message}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// Legacy function exports for backward compatibility
|
|
743
|
+
export function generateSecrets(customConfigs = {}) {
|
|
744
|
+
const configs = {
|
|
745
|
+
...SECRET_CONFIGS,
|
|
746
|
+
...customConfigs
|
|
747
|
+
};
|
|
748
|
+
const secrets = {};
|
|
749
|
+
for (const [key, config] of Object.entries(configs)) {
|
|
750
|
+
secrets[key] = randomBytes(config.length / 2).toString('hex');
|
|
751
|
+
}
|
|
752
|
+
return secrets;
|
|
753
|
+
}
|
|
754
|
+
export function generateSingleSecret(length = 32) {
|
|
755
|
+
return randomBytes(length / 2).toString('hex');
|
|
756
|
+
}
|
|
757
|
+
export function saveSecrets(domain, environment, secrets, additionalData = {}) {
|
|
758
|
+
const secretsDir = 'secrets';
|
|
759
|
+
if (!existsSync(secretsDir)) {
|
|
760
|
+
mkdirSync(secretsDir, {
|
|
761
|
+
recursive: true
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
const data = {
|
|
765
|
+
domain,
|
|
766
|
+
environment,
|
|
767
|
+
generated: new Date().toISOString(),
|
|
768
|
+
note: 'Generated by modular secret system',
|
|
769
|
+
...additionalData,
|
|
770
|
+
...secrets
|
|
771
|
+
};
|
|
772
|
+
const filename = join(secretsDir, `${domain}-secrets.json`);
|
|
773
|
+
writeFileSync(filename, JSON.stringify(data, null, 2));
|
|
774
|
+
return filename;
|
|
775
|
+
}
|
|
776
|
+
export function loadSecrets(domain) {
|
|
777
|
+
const filename = join('secrets', `${domain}-secrets.json`);
|
|
778
|
+
if (!existsSync(filename)) {
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
const data = JSON.parse(readFileSync(filename, 'utf8'));
|
|
783
|
+
const {
|
|
784
|
+
domain: d,
|
|
785
|
+
environment,
|
|
786
|
+
generated,
|
|
787
|
+
note,
|
|
788
|
+
...secrets
|
|
789
|
+
} = data;
|
|
790
|
+
return {
|
|
791
|
+
secrets,
|
|
792
|
+
metadata: {
|
|
793
|
+
domain: d,
|
|
794
|
+
environment,
|
|
795
|
+
generated,
|
|
796
|
+
note
|
|
797
|
+
},
|
|
798
|
+
filename
|
|
799
|
+
};
|
|
800
|
+
} catch (error) {
|
|
801
|
+
throw new Error(`Failed to load secrets: ${error.message}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
export function distributeSecrets(domain, secrets) {
|
|
805
|
+
const distDir = join('secrets', 'distribution', domain);
|
|
806
|
+
mkdirSync(distDir, {
|
|
807
|
+
recursive: true
|
|
808
|
+
});
|
|
809
|
+
const files = {};
|
|
810
|
+
|
|
811
|
+
// .env format
|
|
812
|
+
const envContent = Object.entries(secrets).map(([key, value]) => `${key}=${value}`).join('\n');
|
|
813
|
+
const envFile = join(distDir, '.env');
|
|
814
|
+
writeFileSync(envFile, envContent);
|
|
815
|
+
files.env = envFile;
|
|
816
|
+
|
|
817
|
+
// JSON format
|
|
818
|
+
const jsonFile = join(distDir, 'secrets.json');
|
|
819
|
+
writeFileSync(jsonFile, JSON.stringify(secrets, null, 2));
|
|
820
|
+
files.json = jsonFile;
|
|
821
|
+
|
|
822
|
+
// Shell script format (cross-platform)
|
|
823
|
+
const shellContent = Object.entries(secrets).map(([key, value]) => `echo "${value}" | npx wrangler secret put ${key} --env production`).join('\n');
|
|
824
|
+
const shellFile = join(distDir, 'deploy-secrets.sh');
|
|
825
|
+
writeFileSync(shellFile, shellContent);
|
|
826
|
+
files.shell = shellFile;
|
|
827
|
+
|
|
828
|
+
// PowerShell script format
|
|
829
|
+
const psContent = Object.entries(secrets).map(([key, value]) => `"${value}" | npx wrangler secret put ${key} --env production`).join('\n');
|
|
830
|
+
const psFile = join(distDir, 'deploy-secrets.ps1');
|
|
831
|
+
writeFileSync(psFile, psContent);
|
|
832
|
+
files.powershell = psFile;
|
|
833
|
+
|
|
834
|
+
// README
|
|
835
|
+
const readme = `# Secret Distribution for ${domain}
|
|
836
|
+
|
|
837
|
+
Generated: ${new Date().toISOString()}
|
|
838
|
+
|
|
839
|
+
## Files
|
|
840
|
+
- \`.env\` - Environment variables for Node.js applications
|
|
841
|
+
- \`secrets.json\` - JSON format for API consumption
|
|
842
|
+
- \`deploy-secrets.sh\` - Bash commands for Cloudflare Workers
|
|
843
|
+
- \`deploy-secrets.ps1\` - PowerShell commands for Cloudflare Workers
|
|
844
|
+
|
|
845
|
+
## Usage
|
|
846
|
+
|
|
847
|
+
### For downstream Node.js applications:
|
|
848
|
+
\`\`\`bash
|
|
849
|
+
cp .env /path/to/your/app/
|
|
850
|
+
\`\`\`
|
|
851
|
+
|
|
852
|
+
### For upstream Cloudflare Workers (Linux/Mac):
|
|
853
|
+
\`\`\`bash
|
|
854
|
+
chmod +x deploy-secrets.sh
|
|
855
|
+
./deploy-secrets.sh
|
|
856
|
+
\`\`\`
|
|
857
|
+
|
|
858
|
+
### For upstream Cloudflare Workers (Windows):
|
|
859
|
+
\`\`\`powershell
|
|
860
|
+
.\\deploy-secrets.ps1
|
|
861
|
+
\`\`\`
|
|
862
|
+
|
|
863
|
+
## Security Notice
|
|
864
|
+
- Keep these files secure
|
|
865
|
+
- Never commit to version control
|
|
866
|
+
- Distribute via secure channels only
|
|
867
|
+
`;
|
|
868
|
+
const readmeFile = join(distDir, 'README.md');
|
|
869
|
+
writeFileSync(readmeFile, readme);
|
|
870
|
+
files.readme = readmeFile;
|
|
871
|
+
return {
|
|
872
|
+
directory: distDir,
|
|
873
|
+
files
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
export function validateSecrets(secrets) {
|
|
877
|
+
const issues = [];
|
|
878
|
+
for (const [key, value] of Object.entries(secrets)) {
|
|
879
|
+
if (!value || typeof value !== 'string') {
|
|
880
|
+
issues.push(`${key}: Invalid value`);
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (value.length < 16) {
|
|
884
|
+
issues.push(`${key}: Too short (minimum 16 characters)`);
|
|
885
|
+
}
|
|
886
|
+
if (!/^[a-f0-9]+$/i.test(value)) {
|
|
887
|
+
issues.push(`${key}: Should be hexadecimal`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return issues;
|
|
891
|
+
}
|
|
892
|
+
export function listSecretsFiles() {
|
|
893
|
+
const secretsDir = 'secrets';
|
|
894
|
+
if (!existsSync(secretsDir)) {
|
|
895
|
+
return [];
|
|
896
|
+
}
|
|
897
|
+
try {
|
|
898
|
+
const files = [];
|
|
899
|
+
const items = readdirSync(secretsDir);
|
|
900
|
+
for (const item of items) {
|
|
901
|
+
if (item.endsWith('-secrets.json')) {
|
|
902
|
+
const domain = item.replace('-secrets.json', '');
|
|
903
|
+
const filepath = join(secretsDir, item);
|
|
904
|
+
const stat = statSync(filepath);
|
|
905
|
+
files.push({
|
|
906
|
+
domain,
|
|
907
|
+
filepath,
|
|
908
|
+
modified: stat.mtime,
|
|
909
|
+
size: stat.size
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return files.sort((a, b) => b.modified - a.modified);
|
|
914
|
+
} catch (error) {
|
|
915
|
+
return [];
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
export { SECRET_CONFIGS };
|