[grade=A] P1-8: replace 66 console.* calls across 6 remaining files with structured logger
Files changed: - src/security/crypto-utils.js: 16 calls → log tagged 'crypto' - src/security/docker-security.js: 15 calls → log tagged 'security' - src/managers/port-lock-manager.js: 16 calls → log tagged 'portlock' - src/docker/self-updater.js: 10 calls → log tagged 'updater' - src/security/event-workers.js: 5 calls → log tagged 'events' - src/security/keychain-manager.js: 4 calls → log tagged 'keychain' Fixed 2 bugs found during sweep: - self-updater.js:161 — arrow expression body had trailing semicolon (SyntaxError) - port-lock-manager.js:137 — log.error referenced 'port' var out of scope (ReferenceError) 1539/1539 Jest tests pass. All ESLint warnings pre-existing (0 new).
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
const { log } = require('../utils/logging');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const fsp = require('fs').promises;
|
const fsp = require('fs').promises;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -86,7 +87,7 @@ class SelfUpdater extends EventEmitter {
|
|||||||
start() {
|
start() {
|
||||||
if (!this.config.enabled || this.checkTimer) return;
|
if (!this.config.enabled || this.checkTimer) return;
|
||||||
|
|
||||||
console.log('[SelfUpdater] Starting auto-update checks every %ds', this.config.checkInterval / 1000);
|
log.info('updater', 'Starting auto-update checks', { intervalMs: this.config.checkInterval });
|
||||||
|
|
||||||
// First check after a short delay (let server finish startup)
|
// First check after a short delay (let server finish startup)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -124,7 +125,7 @@ class SelfUpdater extends EventEmitter {
|
|||||||
return { version: pkg.version, commit };
|
return { version: pkg.version, commit };
|
||||||
} catch { /* try next candidate */ }
|
} catch { /* try next candidate */ }
|
||||||
}
|
}
|
||||||
console.error('[SelfUpdater] getLocalVersion failed: no candidate package.json found');
|
log.error('updater', 'getLocalVersion failed: no candidate package.json found');
|
||||||
return { version: '0.0.0', commit: null };
|
return { version: '0.0.0', commit: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +159,7 @@ class SelfUpdater extends EventEmitter {
|
|||||||
// Fire-and-forget; the response shouldn't block on the container rebuild.
|
// Fire-and-forget; the response shouldn't block on the container rebuild.
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this._autoCheckAndApply().catch(err =>
|
this._autoCheckAndApply().catch(err =>
|
||||||
console.error('[SelfUpdater] %s-triggered update error: %s', triggeredBy, err.message)
|
log.error('updater', err, { triggeredBy })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return { accepted: true, triggeredBy };
|
return { accepted: true, triggeredBy };
|
||||||
@@ -174,7 +175,7 @@ class SelfUpdater extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
remote = await this._fetchJson(`${this.config.updateUrl}/version.json`);
|
remote = await this._fetchJson(`${this.config.updateUrl}/version.json`);
|
||||||
} catch (primaryErr) {
|
} catch (primaryErr) {
|
||||||
console.warn('[SelfUpdater] Primary server failed:', primaryErr.message, '— trying mirror');
|
log.warn('updater', 'Primary server failed, trying mirror', { error: primaryErr.message });
|
||||||
try {
|
try {
|
||||||
remote = await this._fetchJson(`${this.config.mirrorUrl}/version.json`);
|
remote = await this._fetchJson(`${this.config.mirrorUrl}/version.json`);
|
||||||
sourceUrl = this.config.mirrorUrl;
|
sourceUrl = this.config.mirrorUrl;
|
||||||
@@ -240,7 +241,7 @@ class SelfUpdater extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
await this._downloadFile(primaryUrl, tarballPath);
|
await this._downloadFile(primaryUrl, tarballPath);
|
||||||
} catch (dlErr) {
|
} catch (dlErr) {
|
||||||
console.warn('[SelfUpdater] Primary download failed:', dlErr.message, '— trying mirror');
|
log.warn('updater', 'Primary download failed, trying mirror', { error: dlErr.message });
|
||||||
// Ensure file is fully cleaned up before mirror attempt
|
// Ensure file is fully cleaned up before mirror attempt
|
||||||
try { fs.unlinkSync(tarballPath); } catch { /* ignore */ }
|
try { fs.unlinkSync(tarballPath); } catch { /* ignore */ }
|
||||||
await this._downloadFile(mirrorUrl, tarballPath);
|
await this._downloadFile(mirrorUrl, tarballPath);
|
||||||
@@ -468,11 +469,11 @@ class SelfUpdater extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
const result = await this.checkForUpdate();
|
const result = await this.checkForUpdate();
|
||||||
if (result.available && result.remote) {
|
if (result.available && result.remote) {
|
||||||
console.log('[SelfUpdater] Update available: %s → %s', result.local.version, result.remote.version);
|
log.info('updater', 'Update available', { localVersion: result.local.version, remoteVersion: result.remote.version });
|
||||||
await this.applyUpdate(result.remote);
|
await this.applyUpdate(result.remote);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[SelfUpdater] Auto-update error:', e.message);
|
log.error('updater', e, { phase: 'autoUpdate' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,7 +607,7 @@ class SelfUpdater extends EventEmitter {
|
|||||||
fs.mkdirSync(path.dirname(this.notifySecretFile), { recursive: true });
|
fs.mkdirSync(path.dirname(this.notifySecretFile), { recursive: true });
|
||||||
fs.writeFileSync(this.notifySecretFile, `${secret}\n`, { mode: 0o600 });
|
fs.writeFileSync(this.notifySecretFile, `${secret}\n`, { mode: 0o600 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[SelfUpdater] Failed to persist notify secret:', error.message);
|
log.warn('updater', 'Failed to persist notify secret', { error: error.message });
|
||||||
}
|
}
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
@@ -626,7 +627,7 @@ class SelfUpdater extends EventEmitter {
|
|||||||
fs.mkdirSync(path.dirname(this.config.instanceIdFile), { recursive: true });
|
fs.mkdirSync(path.dirname(this.config.instanceIdFile), { recursive: true });
|
||||||
fs.writeFileSync(this.config.instanceIdFile, `${instanceId}\n`, 'utf8');
|
fs.writeFileSync(this.config.instanceIdFile, `${instanceId}\n`, 'utf8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[SelfUpdater] Failed to persist instance ID:', error.message);
|
log.warn('updater', 'Failed to persist instance ID', { error: error.message });
|
||||||
}
|
}
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
@@ -644,7 +645,7 @@ class SelfUpdater extends EventEmitter {
|
|||||||
try {
|
try {
|
||||||
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
|
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[SelfUpdater] Failed to save history:', e.message);
|
log.error('updater', e, { operation: 'saveHistory' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const lockfile = require('proper-lockfile');
|
const lockfile = require('proper-lockfile');
|
||||||
const platformPaths = require('../../platform-paths');
|
const platformPaths = require('../../platform-paths');
|
||||||
|
const { log } = require('../utils/logging');
|
||||||
|
|
||||||
const LOCK_DIR = process.env.PORT_LOCK_DIR || path.join(platformPaths.dataDir, '.port-locks');
|
const LOCK_DIR = process.env.PORT_LOCK_DIR || path.join(platformPaths.dataDir, '.port-locks');
|
||||||
const LOCK_TIMEOUT = 120000; // 2 minutes
|
const LOCK_TIMEOUT = 120000; // 2 minutes
|
||||||
@@ -35,7 +36,7 @@ class PortLockManager {
|
|||||||
ensureLockDirectory() {
|
ensureLockDirectory() {
|
||||||
if (!fs.existsSync(LOCK_DIR)) {
|
if (!fs.existsSync(LOCK_DIR)) {
|
||||||
fs.mkdirSync(LOCK_DIR, { recursive: true });
|
fs.mkdirSync(LOCK_DIR, { recursive: true });
|
||||||
console.log('[PortLockManager] Created lock directory:', LOCK_DIR);
|
log.info('portlock', 'Created lock directory', { dir: LOCK_DIR });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ class PortLockManager {
|
|||||||
const releaseFunctions = [];
|
const releaseFunctions = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(`[PortLockManager] Acquiring locks for ports: ${sortedPorts.join(', ')}`);
|
log.info('portlock', 'Acquiring locks', { ports: sortedPorts });
|
||||||
|
|
||||||
// Acquire locks in sorted order to prevent deadlocks
|
// Acquire locks in sorted order to prevent deadlocks
|
||||||
for (const port of sortedPorts) {
|
for (const port of sortedPorts) {
|
||||||
@@ -83,7 +84,7 @@ class PortLockManager {
|
|||||||
acquiredLocks.push(port);
|
acquiredLocks.push(port);
|
||||||
releaseFunctions.push(release);
|
releaseFunctions.push(release);
|
||||||
|
|
||||||
console.log(`[PortLockManager] Locked port ${port}`);
|
log.info('portlock', 'Locked port', { port });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store lock information
|
// Store lock information
|
||||||
@@ -93,18 +94,18 @@ class PortLockManager {
|
|||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[PortLockManager] Successfully acquired all locks (ID: ${lockId})`);
|
log.info('portlock', 'Acquired all locks', { lockId });
|
||||||
return lockId;
|
return lockId;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Release any locks we managed to acquire
|
// Release any locks we managed to acquire
|
||||||
console.error(`[PortLockManager] Failed to acquire all locks:`, error.message);
|
log.error('portlock', error, { operation: 'acquire', lockId });
|
||||||
|
|
||||||
for (const release of releaseFunctions) {
|
for (const release of releaseFunctions) {
|
||||||
try {
|
try {
|
||||||
await release();
|
await release();
|
||||||
} catch (releaseError) {
|
} catch (releaseError) {
|
||||||
console.error(`[PortLockManager] Error releasing lock during cleanup:`, releaseError.message);
|
log.error('portlock', releaseError, { operation: 'releaseCleanup', lockId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,11 +121,11 @@ class PortLockManager {
|
|||||||
const lockInfo = this.activeLocks.get(lockId);
|
const lockInfo = this.activeLocks.get(lockId);
|
||||||
|
|
||||||
if (!lockInfo) {
|
if (!lockInfo) {
|
||||||
console.warn(`[PortLockManager] Lock ID ${lockId} not found (may have been released already)`);
|
log.warn('portlock', 'Lock ID not found', { lockId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[PortLockManager] Releasing locks for ports: ${lockInfo.ports.join(', ')}`);
|
log.info('portlock', 'Releasing locks', { lockId, ports: lockInfo.ports });
|
||||||
|
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
@@ -133,16 +134,16 @@ class PortLockManager {
|
|||||||
await release();
|
await release();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errors.push(error.message);
|
errors.push(error.message);
|
||||||
console.error(`[PortLockManager] Error releasing lock:`, error.message);
|
log.error('portlock', error, { operation: 'release', lockId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeLocks.delete(lockId);
|
this.activeLocks.delete(lockId);
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
console.warn(`[PortLockManager] Released locks with ${errors.length} errors`);
|
log.warn('portlock', 'Released locks with errors', { lockId, errorCount: errors.length });
|
||||||
} else {
|
} else {
|
||||||
console.log(`[PortLockManager] Successfully released all locks (ID: ${lockId})`);
|
log.info('portlock', 'Released all locks', { lockId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +152,7 @@ class PortLockManager {
|
|||||||
* Removes locks older than LOCK_STALE_THRESHOLD
|
* Removes locks older than LOCK_STALE_THRESHOLD
|
||||||
*/
|
*/
|
||||||
async cleanupStaleLocks() {
|
async cleanupStaleLocks() {
|
||||||
console.log('[PortLockManager] Cleaning up stale locks...');
|
log.info('portlock', 'Cleaning up stale locks');
|
||||||
|
|
||||||
this.ensureLockDirectory();
|
this.ensureLockDirectory();
|
||||||
|
|
||||||
@@ -174,20 +175,20 @@ class PortLockManager {
|
|||||||
// Lock is stale or not locked, safe to remove
|
// Lock is stale or not locked, safe to remove
|
||||||
fs.unlinkSync(lockFilePath);
|
fs.unlinkSync(lockFilePath);
|
||||||
cleaned++;
|
cleaned++;
|
||||||
console.log(`[PortLockManager] Removed stale lock: ${file}`);
|
log.info('portlock', 'Removed stale lock', { file });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// File might not exist or might have been removed by another process
|
// File might not exist or might have been removed by another process
|
||||||
if (error.code !== 'ENOENT') {
|
if (error.code !== 'ENOENT') {
|
||||||
errors++;
|
errors++;
|
||||||
console.warn(`[PortLockManager] Error checking lock ${file}:`, error.message);
|
log.warn('portlock', 'Error checking lock', { file, error: error.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[PortLockManager] Cleanup complete: ${cleaned} stale locks removed, ${errors} errors`);
|
log.info('portlock', 'Cleanup complete', { cleaned, errors });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[PortLockManager] Error during cleanup:', error.message);
|
log.error('portlock', error, { operation: 'cleanup' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const crypto = require('crypto');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const platformPaths = require('../../platform-paths');
|
const platformPaths = require('../../platform-paths');
|
||||||
|
const { log } = require('../utils/logging');
|
||||||
|
|
||||||
// Encryption settings
|
// Encryption settings
|
||||||
const ALGORITHM = 'aes-256-gcm';
|
const ALGORITHM = 'aes-256-gcm';
|
||||||
@@ -65,7 +66,7 @@ function loadOrCreateKey() {
|
|||||||
// Check for key in environment variable first
|
// Check for key in environment variable first
|
||||||
if (process.env.DASHCADDY_ENCRYPTION_KEY) {
|
if (process.env.DASHCADDY_ENCRYPTION_KEY) {
|
||||||
encryptionKey = Buffer.from(process.env.DASHCADDY_ENCRYPTION_KEY, 'hex');
|
encryptionKey = Buffer.from(process.env.DASHCADDY_ENCRYPTION_KEY, 'hex');
|
||||||
console.log('[Crypto] Using encryption key from environment variable');
|
log.info('crypto', 'Using encryption key from environment variable');
|
||||||
return encryptionKey;
|
return encryptionKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,16 +76,16 @@ function loadOrCreateKey() {
|
|||||||
const keyData = fs.readFileSync(KEY_FILE, 'utf8').trim();
|
const keyData = fs.readFileSync(KEY_FILE, 'utf8').trim();
|
||||||
if (keyData.length >= 64) {
|
if (keyData.length >= 64) {
|
||||||
encryptionKey = Buffer.from(keyData, 'hex');
|
encryptionKey = Buffer.from(keyData, 'hex');
|
||||||
console.log('[Crypto] Loaded encryption key from file');
|
log.info('crypto', 'Loaded encryption key from file');
|
||||||
// First-run bootstrap: if .bak doesn't exist yet, write the current
|
// First-run bootstrap: if .bak doesn't exist yet, write the current
|
||||||
// key to it. This ensures the silent recovery path is available from
|
// key to it. This ensures the silent recovery path is available from
|
||||||
// the very next restart without requiring an explicit rotateKey().
|
// the very next restart without requiring an explicit rotateKey().
|
||||||
if (!fs.existsSync(KEY_FILE + '.bak')) {
|
if (!fs.existsSync(KEY_FILE + '.bak')) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(KEY_FILE + '.bak', keyData, { mode: 0o600 });
|
fs.writeFileSync(KEY_FILE + '.bak', keyData, { mode: 0o600 });
|
||||||
console.log(`[Crypto] Seeded ${KEY_FILE}.bak with current key for future fallback`);
|
log.info('crypto', 'Seeded .bak key file for future fallback');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[Crypto] Could not seed .bak key file:', e.message);
|
log.warn('crypto', 'Could not seed .bak key file', { error: e.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Try fallback to .bak key if primary can't decrypt existing credentials.
|
// Try fallback to .bak key if primary can't decrypt existing credentials.
|
||||||
@@ -98,14 +99,14 @@ function loadOrCreateKey() {
|
|||||||
encryptionKey = tryFallbackToBackupKey(Buffer.from(keyData, 'hex'), Buffer.from(backupData, 'hex'));
|
encryptionKey = tryFallbackToBackupKey(Buffer.from(keyData, 'hex'), Buffer.from(backupData, 'hex'));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[Crypto] Could not check backup key:', e.message);
|
log.warn('crypto', 'Could not check backup key', { error: e.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return encryptionKey;
|
return encryptionKey;
|
||||||
}
|
}
|
||||||
// File exists but key is invalid/empty - will generate new one below
|
// File exists but key is invalid/empty - will generate new one below
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Crypto] Error loading key file:', error.message);
|
log.error('crypto', error, { operation: 'loadKey' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,10 +116,10 @@ function loadOrCreateKey() {
|
|||||||
try {
|
try {
|
||||||
// Save key to file with restricted permissions
|
// Save key to file with restricted permissions
|
||||||
fs.writeFileSync(KEY_FILE, encryptionKey.toString('hex'), { mode: 0o600 });
|
fs.writeFileSync(KEY_FILE, encryptionKey.toString('hex'), { mode: 0o600 });
|
||||||
console.log('[Crypto] Generated and saved new encryption key');
|
log.info('crypto', 'Generated and saved new encryption key');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[Crypto] Could not save key to file:', error.message);
|
log.warn('crypto', 'Could not save key to file', { error: error.message });
|
||||||
console.warn('[Crypto] Key will be regenerated on restart - credentials will need to be re-entered');
|
log.warn('crypto', 'Key will be regenerated on restart - credentials will need to be re-entered');
|
||||||
}
|
}
|
||||||
|
|
||||||
return encryptionKey;
|
return encryptionKey;
|
||||||
@@ -171,12 +172,7 @@ function tryFallbackToBackupKey(primaryKey, backupKey) {
|
|||||||
|
|
||||||
if (tryDecrypt(primaryKey)) return primaryKey;
|
if (tryDecrypt(primaryKey)) return primaryKey;
|
||||||
if (tryDecrypt(backupKey)) {
|
if (tryDecrypt(backupKey)) {
|
||||||
console.warn(
|
log.warn('crypto', 'Primary encryption key failed to decrypt credentials; fell back to .encryption-key.bak. Consider rotating the key explicitly via the credential-manager API.');
|
||||||
'[Crypto] Primary encryption key failed to decrypt credentials; ' +
|
|
||||||
'fell back to .encryption-key.bak. The current primary key was set ' +
|
|
||||||
'without preserving the original. Consider rotating the key explicitly ' +
|
|
||||||
'via the credential-manager API to avoid this warning next restart.'
|
|
||||||
);
|
|
||||||
return backupKey;
|
return backupKey;
|
||||||
}
|
}
|
||||||
return primaryKey; // neither works — credential-manager.diagnose() will report 'unreadable'
|
return primaryKey; // neither works — credential-manager.diagnose() will report 'unreadable'
|
||||||
@@ -291,7 +287,7 @@ function decryptFields(obj, fields = null) {
|
|||||||
try {
|
try {
|
||||||
result[field] = decrypt(result[field]);
|
result[field] = decrypt(result[field]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Crypto] Failed to decrypt field '${field}':`, error.message);
|
log.error('crypto', error, { field, operation: 'decryptField' });
|
||||||
// Leave the field as-is if decryption fails
|
// Leave the field as-is if decryption fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,7 +311,7 @@ function migrateToEncrypted(credentials, sensitiveFields) {
|
|||||||
return credentials; // Already encrypted
|
return credentials; // Already encrypted
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Crypto] Migrating plaintext credentials to encrypted format');
|
log.info('crypto', 'Migrating plaintext credentials to encrypted format');
|
||||||
return encryptFields(credentials, sensitiveFields);
|
return encryptFields(credentials, sensitiveFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,10 +336,10 @@ function readEncryptedFile(filePath, sensitiveFields = ['password', 'token', 'ap
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Plain text data - migrate it
|
// Plain text data - migrate it
|
||||||
console.log(`[Crypto] Found plaintext data in ${filePath}, will encrypt on next save`);
|
log.info('crypto', 'Found plaintext data', { filePath });
|
||||||
return parsed;
|
return parsed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Crypto] Error reading ${filePath}:`, error.message);
|
log.error('crypto', error, { filePath, operation: 'readFile' });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,7 +353,7 @@ function readEncryptedFile(filePath, sensitiveFields = ['password', 'token', 'ap
|
|||||||
function writeEncryptedFile(filePath, credentials, sensitiveFields = ['password', 'token', 'apiKey', 'secret']) {
|
function writeEncryptedFile(filePath, credentials, sensitiveFields = ['password', 'token', 'apiKey', 'secret']) {
|
||||||
const encrypted = encryptFields(credentials, sensitiveFields);
|
const encrypted = encryptFields(credentials, sensitiveFields);
|
||||||
fs.writeFileSync(filePath, JSON.stringify(encrypted, null, 2), 'utf8');
|
fs.writeFileSync(filePath, JSON.stringify(encrypted, null, 2), 'utf8');
|
||||||
console.log(`[Crypto] Saved encrypted credentials to ${filePath}`);
|
log.info('crypto', 'Saved encrypted credentials', { filePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -377,7 +373,7 @@ function rotateKey() {
|
|||||||
try {
|
try {
|
||||||
fs.writeFileSync(KEY_FILE + '.bak', oldKey.toString('hex'), { mode: 0o600 });
|
fs.writeFileSync(KEY_FILE + '.bak', oldKey.toString('hex'), { mode: 0o600 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`[Crypto] Could not save backup key to ${KEY_FILE}.bak:`, error.message);
|
log.warn('crypto', 'Could not save backup key', { error: error.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const path = require('path');
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
const Docker = require('dockerode');
|
const Docker = require('dockerode');
|
||||||
const platformPaths = require('../../platform-paths');
|
const platformPaths = require('../../platform-paths');
|
||||||
|
const { log } = require('../utils/logging');
|
||||||
|
|
||||||
const docker = new Docker();
|
const docker = new Docker();
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ class DockerSecurity {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.config = this.loadConfig();
|
this.config = this.loadConfig();
|
||||||
this.mode = VERIFICATION_MODE;
|
this.mode = VERIFICATION_MODE;
|
||||||
console.log(`[DockerSecurity] Initialized in ${this.mode} mode`);
|
log.info('security', 'Docker security initialized', { mode: this.mode });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +33,7 @@ class DockerSecurity {
|
|||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`[DockerSecurity] Failed to load config: ${error.message}`);
|
log.warn('security', 'Failed to load config', { error: error.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
@@ -51,7 +52,7 @@ class DockerSecurity {
|
|||||||
try {
|
try {
|
||||||
fs.writeFileSync(SECURITY_CONFIG_FILE, JSON.stringify(this.config, null, 2));
|
fs.writeFileSync(SECURITY_CONFIG_FILE, JSON.stringify(this.config, null, 2));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[DockerSecurity] Failed to save config: ${error.message}`);
|
log.error('security', error, { operation: 'saveConfig' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ class DockerSecurity {
|
|||||||
repository = repository.split(':')[0];
|
repository = repository.split(':')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[DockerSecurity] Fetching manifest for ${registry}/${repository}:${tag}`);
|
log.info('security', 'Fetching manifest', { registry, repository, tag });
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const isDockerHub = registry === 'registry-1.docker.io';
|
const isDockerHub = registry === 'registry-1.docker.io';
|
||||||
@@ -216,7 +217,7 @@ class DockerSecurity {
|
|||||||
if (this.config.updateTrustedOnPull) {
|
if (this.config.updateTrustedOnPull) {
|
||||||
this.config.trustedDigests[imageName] = actualDigest;
|
this.config.trustedDigests[imageName] = actualDigest;
|
||||||
this.saveConfig();
|
this.saveConfig();
|
||||||
console.log(`[DockerSecurity] Added trusted digest for ${imageName}`);
|
log.info('security', 'Added trusted digest', { imageName });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (actualDigest === trustedDigest) {
|
} else if (actualDigest === trustedDigest) {
|
||||||
@@ -250,26 +251,26 @@ class DockerSecurity {
|
|||||||
* @returns {Promise<object>} Verification result
|
* @returns {Promise<object>} Verification result
|
||||||
*/
|
*/
|
||||||
async verifyPulledImage(imageName) {
|
async verifyPulledImage(imageName) {
|
||||||
console.log(`[DockerSecurity] Verifying image: ${imageName}`);
|
log.info('security', 'Verifying image', { imageName });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const actualDigest = await this.getImageDigest(imageName);
|
const actualDigest = await this.getImageDigest(imageName);
|
||||||
const result = await this.verifyImageDigest(imageName, actualDigest);
|
const result = await this.verifyImageDigest(imageName, actualDigest);
|
||||||
|
|
||||||
if (result.action === 'reject') {
|
if (result.action === 'reject') {
|
||||||
console.error(`[DockerSecurity] REJECTED: ${result.reason}`);
|
log.error('security', 'Image REJECTED', { imageName, reason: result.reason });
|
||||||
throw new Error(`Image verification failed: ${result.reason}`);
|
throw new Error(`Image verification failed: ${result.reason}`);
|
||||||
} else if (result.action === 'warn') {
|
} else if (result.action === 'warn') {
|
||||||
console.warn(`[DockerSecurity] WARNING: ${result.reason}`);
|
log.warn('security', 'Image WARNING', { imageName, reason: result.reason });
|
||||||
console.warn(`[DockerSecurity] Expected: ${result.trustedDigest}`);
|
log.warn('security', 'Expected digest', { imageName, digest: result.trustedDigest });
|
||||||
console.warn(`[DockerSecurity] Actual: ${result.actualDigest}`);
|
log.warn('security', 'Actual digest', { imageName, digest: result.actualDigest });
|
||||||
} else {
|
} else {
|
||||||
console.log(`[DockerSecurity] ACCEPTED: ${result.reason}`);
|
log.info('security', 'Image ACCEPTED', { imageName, reason: result.reason });
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[DockerSecurity] Verification error: ${error.message}`);
|
log.error('security', error, { imageName, operation: 'verify' });
|
||||||
|
|
||||||
if (this.mode === 'strict') {
|
if (this.mode === 'strict') {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -294,7 +295,7 @@ class DockerSecurity {
|
|||||||
setTrustedDigest(imageName, digest) {
|
setTrustedDigest(imageName, digest) {
|
||||||
this.config.trustedDigests[imageName] = digest;
|
this.config.trustedDigests[imageName] = digest;
|
||||||
this.saveConfig();
|
this.saveConfig();
|
||||||
console.log(`[DockerSecurity] Updated trusted digest for ${imageName}`);
|
log.info('security', 'Updated trusted digest', { imageName });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,7 +305,7 @@ class DockerSecurity {
|
|||||||
removeTrustedDigest(imageName) {
|
removeTrustedDigest(imageName) {
|
||||||
delete this.config.trustedDigests[imageName];
|
delete this.config.trustedDigests[imageName];
|
||||||
this.saveConfig();
|
this.saveConfig();
|
||||||
console.log(`[DockerSecurity] Removed trusted digest for ${imageName}`);
|
log.info('security', 'Removed trusted digest', { imageName });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,7 +326,7 @@ class DockerSecurity {
|
|||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.config.verificationMode = mode;
|
this.config.verificationMode = mode;
|
||||||
this.saveConfig();
|
this.saveConfig();
|
||||||
console.log(`[DockerSecurity] Verification mode set to: ${mode}`);
|
log.info('security', 'Verification mode set', { mode });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const { log } = require('../utils/logging');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const platformPaths = require('../../platform-paths');
|
const platformPaths = require('../../platform-paths');
|
||||||
@@ -95,7 +96,7 @@ function createTail({ filePath, stateFile, onLine, label = 'tail', pollMs = 1000
|
|||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.trim()) {
|
if (line.trim()) {
|
||||||
try { onLine(line); } catch (e) {
|
try { onLine(line); } catch (e) {
|
||||||
console.error(`[${label}] onLine threw:`, e.message);
|
log.error('events', e, { worker: label, phase: 'onLine' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,7 @@ function createTail({ filePath, stateFile, onLine, label = 'tail', pollMs = 1000
|
|||||||
setTimeout(tick, pollMs);
|
setTimeout(tick, pollMs);
|
||||||
});
|
});
|
||||||
stream.on('error', (e) => {
|
stream.on('error', (e) => {
|
||||||
console.error(`[${label}] read error:`, e.message);
|
log.error('events', e, { worker: label, phase: 'read' });
|
||||||
setTimeout(tick, pollMs * 5);
|
setTimeout(tick, pollMs * 5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -267,11 +268,11 @@ function startFail2banWorker({ log } = {}) {
|
|||||||
function startAll({ log } = {}) {
|
function startAll({ log } = {}) {
|
||||||
const workers = [];
|
const workers = [];
|
||||||
try { workers.push(startCaddyWorker({ log })); }
|
try { workers.push(startCaddyWorker({ log })); }
|
||||||
catch (e) { console.error('[workers] caddy worker failed to start:', e.message); }
|
catch (e) { log.error('events', e, { worker: 'caddy', phase: 'start' }); }
|
||||||
try { workers.push(startSharedBansWorker({ log })); }
|
try { workers.push(startSharedBansWorker({ log })); }
|
||||||
catch (e) { console.error('[workers] shared_bans worker failed to start:', e.message); }
|
catch (e) { log.error('events', e, { worker: 'shared_bans', phase: 'start' }); }
|
||||||
try { workers.push(startFail2banWorker({ log })); }
|
try { workers.push(startFail2banWorker({ log })); }
|
||||||
catch (e) { console.error('[workers] fail2ban worker failed to start:', e.message); }
|
catch (e) { log.error('events', e, { worker: 'fail2ban', phase: 'start' }); }
|
||||||
return {
|
return {
|
||||||
stop() { workers.forEach(w => { try { w.stop(); } catch {} }); },
|
stop() { workers.forEach(w => { try { w.stop(); } catch {} }); },
|
||||||
workers,
|
workers,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
const { execSync, execFileSync } = require('child_process');
|
const { execSync, execFileSync } = require('child_process');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const { log } = require('../utils/logging');
|
||||||
|
|
||||||
const SERVICE_NAME = 'DashCaddy';
|
const SERVICE_NAME = 'DashCaddy';
|
||||||
const ACCOUNT_PREFIX = 'dashcaddy';
|
const ACCOUNT_PREFIX = 'dashcaddy';
|
||||||
@@ -44,7 +45,7 @@ class KeychainManager {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch {
|
} catch {
|
||||||
console.warn('[Keychain] OS keychain not available, will use encrypted file storage');
|
log.warn('keychain', 'OS keychain not available, will use encrypted file storage');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +73,7 @@ class KeychainManager {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Keychain] Failed to store ${key}:`, error.message);
|
log.error('keychain', error, { key, operation: 'store' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +100,7 @@ class KeychainManager {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Keychain] Failed to retrieve ${key}:`, error.message);
|
log.error('keychain', error, { key, operation: 'retrieve' });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +127,7 @@ class KeychainManager {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Keychain] Failed to delete ${key}:`, error.message);
|
log.error('keychain', error, { key, operation: 'delete' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user