dnsweeper
Version:
Advanced CLI tool for DNS record risk analysis and cleanup. Features CSV import for Cloudflare/Route53, automated risk assessment, and parallel DNS validation.
205 lines • 6.49 kB
JavaScript
import { promises as fs } from 'fs';
import os from 'os';
import path from 'path';
/**
* デフォルト設定
*/
const DEFAULT_CONFIG = {
dns: {
timeout: 5000,
retries: 3,
concurrent: 10,
},
csv: {
encoding: 'utf8',
delimiter: ',',
quote: '"',
skipEmptyLines: true,
maxRows: 1000000,
},
risk: {
weights: {
unusedDays: 0.4,
namingPattern: 0.3,
ttl: 0.3,
},
thresholds: {
high: 70,
medium: 40,
},
},
output: {
format: 'table',
colors: true,
verbose: false,
quiet: false,
},
};
/**
* 設定ファイルのパスを検索
*/
async function findConfigFile() {
const configFileNames = ['.dnsweeper.json', '.dnsweeperrc', 'dnsweeper.config.json'];
// 現在のディレクトリから上位ディレクトリまで探索
let currentDir = process.cwd();
while (true) {
for (const fileName of configFileNames) {
const configPath = path.join(currentDir, fileName);
try {
await fs.access(configPath);
return configPath;
}
catch {
// ファイルが存在しない場合は次を試す
}
}
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
break; // ルートディレクトリに到達
}
currentDir = parentDir;
}
// ホームディレクトリもチェック
const homeDir = os.homedir();
for (const fileName of configFileNames) {
const configPath = path.join(homeDir, fileName);
try {
await fs.access(configPath);
return configPath;
}
catch {
// ファイルが存在しない場合は次を試す
}
}
return null;
}
/**
* 設定ファイルを読み込む
*/
export async function loadConfig(configPath) {
let config = { ...DEFAULT_CONFIG };
// 設定ファイルパスが指定されていない場合は自動検索
const actualConfigPath = configPath || (await findConfigFile());
if (actualConfigPath) {
try {
const configContent = await fs.readFile(actualConfigPath, 'utf8');
const fileConfig = JSON.parse(configContent);
// 深いマージを実行
config = deepMerge(config, fileConfig);
console.log(`設定ファイルを読み込みました: ${actualConfigPath}`);
}
catch (error) {
console.error(`設定ファイルの読み込みエラー: ${actualConfigPath}`, error);
throw error;
}
}
// 環境変数から設定を上書き
config = mergeEnvironmentVariables(config);
return config;
}
/**
* 環境変数から設定をマージ
*/
function mergeEnvironmentVariables(config) {
const env = process.env;
// DNS設定
if (env.DNSWEEPER_DNS_TIMEOUT) {
config.dns = config.dns || {};
config.dns.timeout = parseInt(env.DNSWEEPER_DNS_TIMEOUT, 10);
}
if (env.DNSWEEPER_DNS_SERVERS) {
config.dns = config.dns || {};
config.dns.servers = env.DNSWEEPER_DNS_SERVERS.split(',');
}
// API設定
if (env.CLOUDFLARE_API_KEY) {
config.api = config.api || {};
config.api.cloudflare = config.api.cloudflare || {};
config.api.cloudflare.apiKey = env.CLOUDFLARE_API_KEY;
}
if (env.CLOUDFLARE_EMAIL) {
config.api = config.api || {};
config.api.cloudflare = config.api.cloudflare || {};
config.api.cloudflare.email = env.CLOUDFLARE_EMAIL;
}
if (env.AWS_ACCESS_KEY_ID) {
config.api = config.api || {};
config.api.route53 = config.api.route53 || {};
config.api.route53.accessKeyId = env.AWS_ACCESS_KEY_ID;
}
if (env.AWS_SECRET_ACCESS_KEY) {
config.api = config.api || {};
config.api.route53 = config.api.route53 || {};
config.api.route53.secretAccessKey = env.AWS_SECRET_ACCESS_KEY;
}
// 出力設定
if (env.DNSWEEPER_OUTPUT_FORMAT) {
config.output = config.output || {};
config.output.format = env.DNSWEEPER_OUTPUT_FORMAT;
}
if (env.NO_COLOR || env.DNSWEEPER_NO_COLOR) {
config.output = config.output || {};
config.output.colors = false;
}
return config;
}
/**
* オブジェクトの深いマージ
*/
function deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
}
else {
result[key] = source[key];
}
}
}
return result;
}
/**
* 設定の検証
*/
export function validateConfig(config) {
// DNS設定の検証
if (config.dns?.timeout && config.dns.timeout < 0) {
throw new Error('DNS timeout must be positive');
}
if (config.dns?.retries && config.dns.retries < 0) {
throw new Error('DNS retries must be positive');
}
// リスク重みの検証
if (config.risk?.weights) {
const weights = config.risk.weights;
const total = (weights.unusedDays || 0) + (weights.namingPattern || 0) + (weights.ttl || 0);
if (Math.abs(total - 1.0) > 0.01) {
throw new Error('Risk weights must sum to 1.0');
}
}
// しきい値の検証
if (config.risk?.thresholds) {
const { high = 70, medium = 40 } = config.risk.thresholds;
if (high <= medium) {
throw new Error('High risk threshold must be greater than medium threshold');
}
}
}
/**
* 設定をファイルに保存
*/
export async function saveConfig(config, configPath) {
const actualConfigPath = configPath || path.join(process.cwd(), '.dnsweeper.json');
try {
const configContent = JSON.stringify(config, null, 2);
await fs.writeFile(actualConfigPath, configContent, 'utf8');
console.log(`設定ファイルを保存しました: ${actualConfigPath}`);
}
catch (error) {
console.error(`設定ファイルの保存エラー: ${actualConfigPath}`, error);
throw error;
}
}
//# sourceMappingURL=config.js.map