index.ts•4.45 kB
/**
* Configuration management for Cobalt Strike MCP Server
*/
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs/promises';
import { CobaltStrikeConfig } from '../types/index.js';
const SERVICE_NAME = 'cobaltstrike-mcp';
const CONFIG_DIR = path.join(os.homedir(), '.cobaltstrike-mcp');
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
// Lazy load keytar to handle cases where it fails to load
let keytar: any = null;
let keytarLoaded = false;
async function loadKeytar(): Promise<any> {
if (keytarLoaded) {
return keytar;
}
try {
// Use dynamic import for ES modules
const keytarModule = await import('keytar');
keytar = keytarModule.default || keytarModule;
keytarLoaded = true;
return keytar;
} catch (error) {
// keytar failed to load (e.g., native module issue)
// Will fall back to storing credentials in config file (less secure)
keytarLoaded = true; // Mark as attempted to avoid repeated tries
return null;
}
}
export class ConfigManager {
/**
* Ensure config directory exists
*/
static async ensureConfigDir(): Promise<void> {
try {
await fs.mkdir(CONFIG_DIR, { recursive: true });
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to create config directory: ${errorMsg}\n` +
`Path: ${CONFIG_DIR}\n` +
`Please check permissions and ensure the directory can be created.`);
}
}
/**
* Load configuration from file and keyring
*/
static async loadConfig(): Promise<CobaltStrikeConfig | null> {
try {
const configData = await fs.readFile(CONFIG_FILE, 'utf-8');
const config = JSON.parse(configData) as Partial<CobaltStrikeConfig>;
if (!config.url) {
return null;
}
let username = config.username || '';
let password = config.password || '';
// Try to load credentials from keyring if available
const keytarInstance = await loadKeytar();
if (keytarInstance) {
try {
const keytarPassword = await keytarInstance.getPassword(SERVICE_NAME, 'password');
const keytarUsername = await keytarInstance.getPassword(SERVICE_NAME, 'username');
if (keytarPassword) password = keytarPassword;
if (keytarUsername) username = keytarUsername;
} catch (error) {
// Keyring access failed, use config file values
console.error('Warning: Failed to load credentials from keyring, using config file');
}
}
return {
url: config.url,
username: username,
password: password,
verifySSL: config.verifySSL !== false,
};
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return null;
}
throw error;
}
}
/**
* Save configuration to file and keyring
*/
static async saveConfig(config: CobaltStrikeConfig): Promise<void> {
await this.ensureConfigDir();
// Try to save credentials to keyring if available
const keytarInstance = await loadKeytar();
let useKeytar = false;
if (keytarInstance) {
try {
if (config.username) {
await keytarInstance.setPassword(SERVICE_NAME, 'username', config.username);
}
if (config.password) {
await keytarInstance.setPassword(SERVICE_NAME, 'password', config.password);
}
useKeytar = true;
} catch (error) {
console.error('Warning: Failed to save credentials to keyring, storing in config file');
// Fall through to save in config file
}
}
// Save config to file
// If keytar is not available, we store credentials in the file (less secure but functional)
const configToSave: any = {
url: config.url,
verifySSL: config.verifySSL !== false,
};
// Only store credentials in file if keytar is not available
if (!useKeytar) {
configToSave.username = config.username;
configToSave.password = config.password;
if (!keytarInstance) {
console.warn('Warning: Credentials are stored in config file (keytar unavailable). This is less secure.');
}
}
await fs.writeFile(CONFIG_FILE, JSON.stringify(configToSave, null, 2), 'utf-8');
}
/**
* Get config directory path
*/
static getConfigDir(): string {
return CONFIG_DIR;
}
}