zhizhi/scripts/skyeye/credential-validator.js

234 lines
8.2 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* scripts/skyeye/credential-validator.js
*
* 天眼密钥流完整性校验器 (Sky-Eye Credential Validator)
*
* 功能 Google Drive Service Account JSON 执行结构化检测
* - JSON 语法完整性检测未闭合 {} / 截断
* - 必需字段存在性验证
* - 字段值格式校验private_key PEM 格式等
*
* 可作为模块 require() 使用也可直接运行
* GOOGLE_DRIVE_SERVICE_ACCOUNT='...' node credential-validator.js
*
* 守护: PER-ZY001 铸渊
* 系统: SYS-GLW-0001
* 主控: TCS-0002 (冰朔)
*/
'use strict';
// Google Service Account JSON 必需字段
const REQUIRED_FIELDS = [
'type',
'project_id',
'private_key_id',
'private_key',
'client_email',
'client_id',
'auth_uri',
'token_uri'
];
/**
* 验证 Service Account JSON 字符串
* @param {string} jsonStr - JSON 字符串
* @returns {{ valid: boolean, credentials: object|null, issues: string[], diagnostics: object }}
*/
function validateServiceAccountJSON(jsonStr) {
const result = {
valid: false,
credentials: null,
issues: [],
diagnostics: {
input_length: 0,
is_empty: true,
json_parseable: false,
is_object: false,
has_all_required_fields: false,
missing_fields: [],
field_checks: {},
truncation_suspected: false
}
};
// ① 空值检查
if (!jsonStr || typeof jsonStr !== 'string') {
result.issues.push('Credential string is empty or not a string');
return result;
}
const trimmed = jsonStr.trim();
result.diagnostics.input_length = trimmed.length;
result.diagnostics.is_empty = trimmed.length === 0;
if (trimmed.length === 0) {
result.issues.push('Credential string is empty after trimming');
return result;
}
// ② 基本结构检查(闭合括号)
if (!trimmed.startsWith('{')) {
result.issues.push(`JSON does not start with '{' (starts with '${trimmed[0]}')`);
}
if (!trimmed.endsWith('}')) {
result.issues.push(`JSON does not end with '}' (ends with '${trimmed[trimmed.length - 1]}')`);
result.diagnostics.truncation_suspected = true;
}
// 检查大括号平衡
let braceDepth = 0;
for (let i = 0; i < trimmed.length; i++) {
if (trimmed[i] === '{') braceDepth++;
else if (trimmed[i] === '}') braceDepth--;
}
if (braceDepth !== 0) {
result.issues.push(`Unbalanced braces: depth=${braceDepth} (${braceDepth > 0 ? 'missing closing }' : 'extra closing }'})`);
result.diagnostics.truncation_suspected = braceDepth > 0;
}
// ③ JSON 解析
let parsed;
try {
parsed = JSON.parse(trimmed);
result.diagnostics.json_parseable = true;
} catch (err) {
result.issues.push(`JSON parse error: ${err.message}`);
// 尝试诊断截断位置
const posMatch = err.message.match(/position (\d+)/);
if (posMatch) {
const pos = parseInt(posMatch[1], 10);
result.diagnostics.error_position = pos;
result.diagnostics.context_at_error = trimmed.substring(
Math.max(0, pos - 30),
Math.min(trimmed.length, pos + 30)
);
}
return result;
}
// ④ 类型检查
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
result.issues.push('Parsed JSON is not a plain object');
return result;
}
result.diagnostics.is_object = true;
result.credentials = parsed;
// ⑤ 必需字段检查
for (const field of REQUIRED_FIELDS) {
if (!(field in parsed) || parsed[field] === undefined || parsed[field] === null || parsed[field] === '') {
result.diagnostics.missing_fields.push(field);
result.issues.push(`Missing or empty required field: '${field}'`);
}
}
result.diagnostics.has_all_required_fields = result.diagnostics.missing_fields.length === 0;
// ⑥ 字段格式校验
if (parsed.type) {
const typeOk = parsed.type === 'service_account';
result.diagnostics.field_checks.type = typeOk ? 'ok' : `expected 'service_account', got '${parsed.type}'`;
if (!typeOk) result.issues.push(`Field 'type' should be 'service_account', got '${parsed.type}'`);
}
if (parsed.private_key) {
const pkStr = String(parsed.private_key);
const hasBegin = pkStr.includes('-----BEGIN');
const hasEnd = pkStr.includes('-----END');
result.diagnostics.field_checks.private_key_format = hasBegin && hasEnd ? 'ok' : 'PEM markers missing';
if (!hasBegin || !hasEnd) {
result.issues.push('Field \'private_key\' does not contain valid PEM BEGIN/END markers');
result.diagnostics.truncation_suspected = true;
}
}
if (parsed.client_email) {
const emailOk = String(parsed.client_email).includes('@') && String(parsed.client_email).includes('.iam.gserviceaccount.com');
result.diagnostics.field_checks.client_email_format = emailOk ? 'ok' : 'not a valid service account email';
if (!emailOk) result.issues.push('Field \'client_email\' does not match service account email pattern');
}
// ⑦ 最终判定
result.valid = result.issues.length === 0;
return result;
}
/**
* 生成人类可读的诊断报告
* @param {object} validationResult - validateServiceAccountJSON 的返回值
* @returns {string}
*/
function formatDiagnosticReport(validationResult) {
const r = validationResult;
const lines = [];
lines.push('╔════════════════════════════════════════════════════════╗');
lines.push('║ 🛰️ 天眼密钥流完整性校验报告 (Credential Audit) ║');
lines.push('╚════════════════════════════════════════════════════════╝');
lines.push('');
lines.push(`状态: ${r.valid ? '✅ 绿色信号 (PASS)' : '🔴 逻辑红区 (FAIL)'}`);
lines.push(`输入长度: ${r.diagnostics.input_length} bytes`);
lines.push(`JSON 可解析: ${r.diagnostics.json_parseable ? '是' : '否'}`);
lines.push(`是否为对象: ${r.diagnostics.is_object ? '是' : '否'}`);
lines.push(`全部必需字段: ${r.diagnostics.has_all_required_fields ? '齐全' : '缺失'}`);
lines.push(`截断嫌疑: ${r.diagnostics.truncation_suspected ? '⚠️ 是' : '否'}`);
if (r.diagnostics.missing_fields.length > 0) {
lines.push(`缺失字段: ${r.diagnostics.missing_fields.join(', ')}`);
}
if (r.diagnostics.error_position !== undefined) {
lines.push(`错误位置: position ${r.diagnostics.error_position}`);
lines.push(`上下文: ...${r.diagnostics.context_at_error}...`);
}
if (Object.keys(r.diagnostics.field_checks).length > 0) {
lines.push('');
lines.push('字段检查:');
for (const [k, v] of Object.entries(r.diagnostics.field_checks)) {
lines.push(` ${k}: ${v}`);
}
}
if (r.issues.length > 0) {
lines.push('');
lines.push('发现问题:');
r.issues.forEach((issue, i) => {
lines.push(` ${i + 1}. ${issue}`);
});
}
return lines.join('\n');
}
// ═══════════════════════════════════════════════
// 模块导出
// ═══════════════════════════════════════════════
module.exports = { validateServiceAccountJSON, formatDiagnosticReport, REQUIRED_FIELDS };
// ═══════════════════════════════════════════════
// CLI 模式
// ═══════════════════════════════════════════════
if (require.main === module) {
const jsonStr = process.env.GOOGLE_DRIVE_SERVICE_ACCOUNT;
if (!jsonStr) {
console.error('[credential-validator] GOOGLE_DRIVE_SERVICE_ACCOUNT not set');
process.exit(1);
}
const result = validateServiceAccountJSON(jsonStr);
console.log(formatDiagnosticReport(result));
console.log('');
console.log('---CREDENTIAL_AUDIT_JSON---');
// 不输出 credentials 本身以避免泄露密钥
const safeResult = { ...result, credentials: result.credentials ? '[REDACTED]' : null };
console.log(JSON.stringify(safeResult, null, 2));
process.exit(result.valid ? 0 : 1);
}