fix(GOV-007): 收紧 Schema 基线安全门禁
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
assertMysql57Metadata,
|
||||
assertNotApplicableMatchesDevelopmentConfiguration,
|
||||
assertCurrentReleasedBaselinePublished,
|
||||
assertSafeDevelopmentConfiguration,
|
||||
buildCapturedArtifacts,
|
||||
@@ -45,6 +47,16 @@ function makeTemporaryDirectory() {
|
||||
return mkdtempSync(path.join(tmpdir(), 'a-share-schema-baseline-'));
|
||||
}
|
||||
|
||||
function replaceCapturedSql(target, sql) {
|
||||
const checksum = createHash('sha256').update(sql).digest('hex');
|
||||
const manifestPath = path.join(target, 'manifest.yaml');
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
||||
manifest.schema_sha256 = checksum;
|
||||
writeFileSync(path.join(target, 'schema.sql'), sql);
|
||||
writeFileSync(path.join(target, 'checksum.sha256'), `${checksum} schema.sql\n`);
|
||||
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||
}
|
||||
|
||||
test('拒绝任何非开发环境和非固定开发库配置', () => {
|
||||
assert.throws(
|
||||
() => assertSafeDevelopmentConfiguration({ ...validConfig, APP_ENV: 'production' }),
|
||||
@@ -179,6 +191,41 @@ test('数据扫描拒绝同行顶层写入但保留例程中的合法 DML', () =
|
||||
}
|
||||
});
|
||||
|
||||
test('数据扫描拒绝所有顶层数据修改和装载语句', () => {
|
||||
for (const statement of [
|
||||
'UPDATE `sample` SET `id` = 2',
|
||||
'DELETE FROM `sample`',
|
||||
'TRUNCATE TABLE `sample`',
|
||||
"LOAD DATA LOCAL INFILE '/tmp/sample.csv' INTO TABLE `sample`",
|
||||
"LOAD XML LOCAL INFILE '/tmp/sample.xml' INTO TABLE `sample`",
|
||||
]) {
|
||||
assert.throws(() => normalizeSchemaSql(`${statement};\n`), /禁止的数据操作/);
|
||||
}
|
||||
});
|
||||
|
||||
test('安全扫描拒绝导出噪声、疑似凭据、连接串和生产主机', () => {
|
||||
for (const unsafeSql of [
|
||||
'-- API_TOKEN=supersecret\nCREATE TABLE `sample` (`id` int);\n',
|
||||
'-- CLIENT_SECRET: supersecret\nCREATE TABLE `sample` (`id` int);\n',
|
||||
'-- mysql://schema_reader:secret@127.0.0.1/a_share_analysis_dev\nCREATE TABLE `sample` (`id` int);\n',
|
||||
'-- production host: prod-db.internal\nCREATE TABLE `sample` (`id` int);\n',
|
||||
]) {
|
||||
assert.throws(() => normalizeSchemaSql(unsafeSql), /Schema SQL 包含禁止内容/);
|
||||
}
|
||||
|
||||
const root = makeTemporaryDirectory();
|
||||
const target = path.join(root, 'v0.0.5');
|
||||
persistBaselineArtifacts(
|
||||
target,
|
||||
buildCapturedArtifacts(validMetadata, 'CREATE TABLE `sample` (`id` int);\n'),
|
||||
);
|
||||
replaceCapturedSql(
|
||||
target,
|
||||
'-- MySQL dump 10.13 Distrib 5.7.44\nCREATE TABLE `sample` (`id` int);\n',
|
||||
);
|
||||
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /MySQL dump/);
|
||||
});
|
||||
|
||||
test('真实基线的清单、校验文件和 SQL 必须三方一致', () => {
|
||||
const sql = 'CREATE TABLE `sample` (`id` bigint NOT NULL AUTO_INCREMENT);\n';
|
||||
const root = makeTemporaryDirectory();
|
||||
@@ -384,6 +431,33 @@ test('不适用清单拒绝未知对象键和伪造的格式元数据', () => {
|
||||
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /清单格式/);
|
||||
});
|
||||
|
||||
test('不适用基线只在当前开发环境仍关闭 MySQL 时有效', () => {
|
||||
const root = makeTemporaryDirectory();
|
||||
const envDirectory = path.join(root, 'deploy', 'env', 'development');
|
||||
const envFile = path.join(envDirectory, 'api.env.example');
|
||||
const result = { status: 'not_applicable' };
|
||||
|
||||
mkdirSync(envDirectory, { recursive: true });
|
||||
writeFileSync(
|
||||
envFile,
|
||||
'APP_ENV=development\nMYSQL_ENABLED=false\nMYSQL_DATABASE=a_share_analysis_dev\nMYSQL_CHARSET=utf8mb4\nMYSQL_COLLATION=utf8mb4_general_ci\n',
|
||||
);
|
||||
assert.doesNotThrow(() =>
|
||||
assertNotApplicableMatchesDevelopmentConfiguration(root, result),
|
||||
);
|
||||
|
||||
writeFileSync(
|
||||
envFile,
|
||||
Object.entries(validConfig)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n'),
|
||||
);
|
||||
assert.throws(
|
||||
() => assertNotApplicableMatchesDevelopmentConfiguration(root, result),
|
||||
/已启用 MySQL.*not_applicable/,
|
||||
);
|
||||
});
|
||||
|
||||
test('严格发布门禁通过已提交且远程同步的当前基线', () => {
|
||||
assert.doesNotThrow(() => assertCurrentReleasedBaselinePublished(process.cwd()));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user