@vollcrypt/db-guard 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +271 -0
- package/compliance-config.json +39 -0
- package/dist/blind-index.d.ts +7 -0
- package/dist/blind-index.js +24 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +211 -0
- package/dist/compliance-report.html +562 -0
- package/dist/compliance.d.ts +40 -0
- package/dist/compliance.js +659 -0
- package/dist/drizzle.d.ts +65 -0
- package/dist/drizzle.js +118 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +44 -0
- package/dist/kms.d.ts +46 -0
- package/dist/kms.js +154 -0
- package/dist/mongoose.d.ts +30 -0
- package/dist/mongoose.js +317 -0
- package/dist/prisma.d.ts +54 -0
- package/dist/prisma.js +390 -0
- package/dist/provenance.json +222 -0
- package/dist/provenance.json.sig +1 -0
- package/dist/sbom.json +209 -0
- package/dist/sbom.json.sig +1 -0
- package/dist/security.d.ts +88 -0
- package/dist/security.js +547 -0
- package/dist/typeorm.d.ts +26 -0
- package/dist/typeorm.js +149 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# db-guard
|
|
2
|
+
|
|
3
|
+
Application-level, field-level encryption integrations for ORMs (Prisma, Mongoose, Drizzle, TypeORM, Diesel, SeaORM) powered by FIPS-compliant cryptography.
|
|
4
|
+
|
|
5
|
+
`db-guard` secures sensitive database columns (SSN, credit card numbers, addresses, personal data) by encrypting them before they hit the database. It prevents data leakage from compromised database dumps, unauthorized database connections, or compromised database administrators (DBAs).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
- **Multi-ORM Support**: Integrations for Node.js (Prisma, Mongoose, Drizzle, TypeORM) and Rust (Diesel, SeaORM).
|
|
12
|
+
- **Dynamic Multi-Tenant Routing**: Dynamically resolves distinct KMS keys or database configurations per request context using AsyncLocalStorage.
|
|
13
|
+
- **Secure Key Cache**: Protects memory from dumps using an ephemeral master key generated randomly at boot. Plaint-text DEKs are wrapped with AES-256-KW and cached with automated TTL eviction and zeroization.
|
|
14
|
+
- **Schema Evolution & Crypto-Agility**: Features backward-compatible prefixes for smooth algorithm transitions without database downtime.
|
|
15
|
+
- **M-of-N Break-Glass Protocol**: Emergency KMS bypass via threshold Ed25519 signature verification.
|
|
16
|
+
- **Compliance Scorecard CLI**: Built-in CLI scans configuration and outputs compliance scorecards for GDPR Article 32, KVKK Article 12, and PCI-DSS v4.0.
|
|
17
|
+
- **Supply Chain Security (SLSA Level 4)**: Build pipeline automatically compiles CycloneDX SBOM and SLSA Level 4 Provenance files, cryptographically signed with Ed25519.
|
|
18
|
+
- **FIPS 140-3 & Post-Quantum Hybrid Transition**: Conforms to FIPS 140-3 logical and physical boundaries with NIST FIPS 203 (ML-KEM) lattice-based algorithms registered for hybrid key exchange.
|
|
19
|
+
- **Hardened Blind Indexing**: Allows exact-match querying on encrypted columns via HKDF-SHA256 shadow columns, avoiding frequency analysis vulnerabilities.
|
|
20
|
+
- **RAM Security**: Aggressive memory zeroization (null-byte writing) for keys and plaintext buffers in memory.
|
|
21
|
+
- **Batch Migration CLI**: Built-in CLI tool to perform chunked shadow database migrations in the background.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
For Node.js (Prisma, Mongoose, Drizzle, TypeORM):
|
|
28
|
+
```bash
|
|
29
|
+
npm install @vollcrypt/db-guard
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For Rust (Diesel, SeaORM):
|
|
33
|
+
```toml
|
|
34
|
+
# Cargo.toml
|
|
35
|
+
[dependencies]
|
|
36
|
+
vollcrypt-db-guard = { path = "db-guard/rust", features = ["sqlite", "sea-orm"] }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Configuration & Usage
|
|
42
|
+
|
|
43
|
+
### 1. Prisma ORM (TypeScript)
|
|
44
|
+
|
|
45
|
+
Register `prismaDbGuard` extension on your client:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { PrismaClient } from '@prisma/client';
|
|
49
|
+
import { prismaDbGuard } from '@vollcrypt/db-guard';
|
|
50
|
+
|
|
51
|
+
const key = Buffer.from('your-secure-32-byte-encryption-key-here');
|
|
52
|
+
|
|
53
|
+
const basePrisma = new PrismaClient();
|
|
54
|
+
export const prisma = basePrisma.$extends(
|
|
55
|
+
prismaDbGuard({
|
|
56
|
+
key,
|
|
57
|
+
models: {
|
|
58
|
+
User: ['credit_card', 'ssn'],
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. Mongoose (TypeScript)
|
|
65
|
+
|
|
66
|
+
Register `mongooseDbGuard` as a schema plugin:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { Schema, model } from 'mongoose';
|
|
70
|
+
import { mongooseDbGuard } from '@vollcrypt/db-guard';
|
|
71
|
+
|
|
72
|
+
const key = Buffer.from('your-secure-32-byte-encryption-key-here');
|
|
73
|
+
|
|
74
|
+
const UserSchema = new Schema({
|
|
75
|
+
name: String,
|
|
76
|
+
credit_card: String,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
UserSchema.plugin(mongooseDbGuard, {
|
|
80
|
+
key,
|
|
81
|
+
fields: ['credit_card'],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const User = model('User', UserSchema);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 3. Drizzle ORM (TypeScript)
|
|
88
|
+
|
|
89
|
+
Use the `createDrizzleGuard` factory to declare encrypted text columns:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { pgTable, serial } from 'drizzle-orm/pg-core';
|
|
93
|
+
import { createDrizzleGuard } from '@vollcrypt/db-guard';
|
|
94
|
+
|
|
95
|
+
const guard = createDrizzleGuard({
|
|
96
|
+
key: Buffer.from('your-secure-32-byte-encryption-key-here'),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const users = pgTable('users', {
|
|
100
|
+
id: serial('id').primaryKey(),
|
|
101
|
+
creditCard: guard.pgText('credit_card'), // Automatically encrypted/decrypted
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 4. TypeORM (TypeScript)
|
|
106
|
+
|
|
107
|
+
Define your entity subscribers using `createTypeOrmSubscriber`:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { DataSource } from 'typeorm';
|
|
111
|
+
import { createTypeOrmSubscriber } from '@vollcrypt/db-guard';
|
|
112
|
+
|
|
113
|
+
const key = Buffer.from('your-secure-32-byte-encryption-key-here');
|
|
114
|
+
|
|
115
|
+
const VollcryptSubscriber = createTypeOrmSubscriber({
|
|
116
|
+
key,
|
|
117
|
+
entities: {
|
|
118
|
+
User: ['credit_card', 'ssn'],
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export const AppDataSource = new DataSource({
|
|
123
|
+
subscribers: [VollcryptSubscriber],
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 5. Diesel (Rust)
|
|
128
|
+
|
|
129
|
+
Use `EncryptedString` in your schema and models:
|
|
130
|
+
|
|
131
|
+
```rust
|
|
132
|
+
use diesel::prelude::*;
|
|
133
|
+
use vollcrypt_db_guard::diesel_impl::EncryptedString;
|
|
134
|
+
|
|
135
|
+
#[derive(Queryable, Selectable, Insertable)]
|
|
136
|
+
#[diesel(table_name = users)]
|
|
137
|
+
pub struct User {
|
|
138
|
+
pub id: i32,
|
|
139
|
+
pub name: String,
|
|
140
|
+
pub credit_card: EncryptedString,
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 6. SeaORM (Rust)
|
|
145
|
+
|
|
146
|
+
Use the SeaORM-compatible `EncryptedString` type wrapper:
|
|
147
|
+
|
|
148
|
+
```rust
|
|
149
|
+
use sea_orm::entity::prelude::*;
|
|
150
|
+
use vollcrypt_db_guard::seaorm_impl::EncryptedString;
|
|
151
|
+
|
|
152
|
+
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
|
153
|
+
#[sea_orm(table_name = "users")]
|
|
154
|
+
pub struct Model {
|
|
155
|
+
#[sea_orm(primary_key)]
|
|
156
|
+
pub id: i32,
|
|
157
|
+
pub name: String,
|
|
158
|
+
pub credit_card: EncryptedString,
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Initialize your keys at application boot for Rust:
|
|
163
|
+
```rust
|
|
164
|
+
use vollcrypt_db_guard::{set_key, set_active_version};
|
|
165
|
+
|
|
166
|
+
fn main() {
|
|
167
|
+
let key = [0u8; 32]; // Secure 32-byte key
|
|
168
|
+
set_key("1", &key);
|
|
169
|
+
set_active_version("1").unwrap();
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Cloud & On-Premises KMS Providers
|
|
176
|
+
|
|
177
|
+
`db-guard` supports multiple key management systems (KMS) and hardware security modules (HSM) to resolve keys dynamically for envelope encryption.
|
|
178
|
+
|
|
179
|
+
### 1. Node.js KMS Providers
|
|
180
|
+
|
|
181
|
+
We provide several KmsProvider implementations:
|
|
182
|
+
|
|
183
|
+
- **AwsKmsProvider**: Resolves keys using AWS KMS.
|
|
184
|
+
- **GcpKmsProvider**: Resolves keys using Google Cloud KMS.
|
|
185
|
+
- **VaultKmsProvider**: Resolves keys using HashiCorp Vault.
|
|
186
|
+
- **Pkcs11KmsProvider**: Interacts with physical or virtual HSMs (YubiHSM2, Thales, Nitrokey, SoftHSM2, etc.) using the standard PKCS#11 protocol.
|
|
187
|
+
|
|
188
|
+
#### Node.js PKCS#11 Configuration Example:
|
|
189
|
+
```typescript
|
|
190
|
+
import { Pkcs11KmsProvider } from '@vollcrypt/db-guard';
|
|
191
|
+
|
|
192
|
+
const kmsProvider = new Pkcs11KmsProvider({
|
|
193
|
+
libraryPath: '/usr/local/lib/softhsm/libsofthsm2.so', // Path to vendor PKCS#11 library
|
|
194
|
+
pin: '123456', // Slot/Token PIN
|
|
195
|
+
slotId: 0, // Target Slot Index (optional, default: 0)
|
|
196
|
+
keyId: '000102', // Hex-encoded CKA_ID of the AES-256 key in HSM
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Decrypt wrapped key (DEK) inside HSM
|
|
200
|
+
const decryptedKey = await kmsProvider.decrypt(wrappedKeyBuffer);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 2. Rust PKCS#11 Support
|
|
204
|
+
|
|
205
|
+
To use PKCS#11 in Rust, enable the `pkcs11` feature:
|
|
206
|
+
```toml
|
|
207
|
+
# Cargo.toml
|
|
208
|
+
[dependencies]
|
|
209
|
+
vollcrypt-db-guard = { path = "db-guard/rust", features = ["sqlite", "pkcs11"] }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
You can then decrypt wrapped keys directly inside your HSM:
|
|
213
|
+
```rust
|
|
214
|
+
use vollcrypt_db_guard::pkcs11_impl::decrypt_with_hsm;
|
|
215
|
+
|
|
216
|
+
let decrypted = decrypt_with_hsm(
|
|
217
|
+
"/usr/local/lib/softhsm/libsofthsm2.so", // Path to PKCS#11 module
|
|
218
|
+
"123456", // PIN
|
|
219
|
+
Some(0), // Slot ID
|
|
220
|
+
"010203", // Hex CKA_ID
|
|
221
|
+
&wrapped_data, // Ciphertext containing wrapped DEK
|
|
222
|
+
).unwrap();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## CLI Commands
|
|
228
|
+
|
|
229
|
+
The package includes a dual-purpose CLI tool for database migrations and compliance auditing.
|
|
230
|
+
|
|
231
|
+
### 1. Database Migrations (`migrate`)
|
|
232
|
+
Encrypts existing plaintext records in a live database using batch processing:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Run PostgreSQL migration
|
|
236
|
+
npx vollcrypt-db-guard migrate \
|
|
237
|
+
--db-type postgres \
|
|
238
|
+
--db-url "postgres://user:pass@localhost:5432/db" \
|
|
239
|
+
--table users \
|
|
240
|
+
--column credit_card \
|
|
241
|
+
--key "your_32_byte_hex_key_here" \
|
|
242
|
+
--chunk-size 100 \
|
|
243
|
+
--id-col id
|
|
244
|
+
|
|
245
|
+
# Run MongoDB migration
|
|
246
|
+
npx vollcrypt-db-guard migrate \
|
|
247
|
+
--db-type mongodb \
|
|
248
|
+
--db-url "mongodb://localhost:27017/db" \
|
|
249
|
+
--table users \
|
|
250
|
+
--column credit_card \
|
|
251
|
+
--key "your_32_byte_hex_key_here" \
|
|
252
|
+
--chunk-size 100 \
|
|
253
|
+
--id-col _id
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 2. Compliance Scorecard Generator (`compliance`)
|
|
257
|
+
Scans cryptographic configurations and generates an auditor-ready HTML compliance report:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npx vollcrypt-db-guard compliance \
|
|
261
|
+
--config compliance-config.json \
|
|
262
|
+
--output compliance-report.html
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Supply Chain & Compliance Verification
|
|
268
|
+
|
|
269
|
+
Refer to the following standalone validation documentation for formal verification processes:
|
|
270
|
+
- **Supply Chain Artifacts**: Signed CycloneDX SBOM and SLSA Level 4 Provenance are located in `dist/sbom.json` and `dist/provenance.json` post-build.
|
|
271
|
+
- **FIPS 140-3 Boundaries**: Detailed logical boundaries, Approved algorithm registries, and post-quantum hybrid structures are defined in [FIPS_VALIDATION.md](file:///c:/Users/iTopya/Desktop/Project/vollcrypt/db-guard/FIPS_VALIDATION.md).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kms": {
|
|
3
|
+
"provider": "aws",
|
|
4
|
+
"wrappedKey": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f",
|
|
5
|
+
"wrappedKek": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f",
|
|
6
|
+
"activeKeyVersion": "1"
|
|
7
|
+
},
|
|
8
|
+
"models": {
|
|
9
|
+
"User": ["email", "credit_card"]
|
|
10
|
+
},
|
|
11
|
+
"blindIndexes": {
|
|
12
|
+
"rootSalt": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f",
|
|
13
|
+
"models": {
|
|
14
|
+
"User": ["email"]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"cryptoRbac": {
|
|
18
|
+
"roles": {
|
|
19
|
+
"ADMIN": {
|
|
20
|
+
"decrypt": ["User.email", "User.credit_card"]
|
|
21
|
+
},
|
|
22
|
+
"SUPPORT": {
|
|
23
|
+
"decrypt": [],
|
|
24
|
+
"mask": {
|
|
25
|
+
"User.credit_card": "credit_card"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"rateLimiter": {
|
|
31
|
+
"maxDecryptionsPerSecond": 100,
|
|
32
|
+
"mode": "fail_closed",
|
|
33
|
+
"maxPageSize": 100,
|
|
34
|
+
"onPageSizeExceeded": "error"
|
|
35
|
+
},
|
|
36
|
+
"breakGlassThreshold": 2,
|
|
37
|
+
"breakGlassPublicKeys": ["2be3f91a7b204d104b60d3ce756b7555f7c7c437f1470b826d03c877b9349dd8", "1234f91a7b204d104b60d3ce756b7555f7c7c437f1470b826d03c877b9349dd8"],
|
|
38
|
+
"postQuantumEnabled": true
|
|
39
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computes a hardened, frequency-resistant blind index for a database field.
|
|
3
|
+
*
|
|
4
|
+
* Uses HKDF-SHA256 to derive a unique column key from the root salt,
|
|
5
|
+
* preventing cross-column frequency analysis. Zeroizes intermediate keys immediately.
|
|
6
|
+
*/
|
|
7
|
+
export declare function computeBlindIndex(value: any, rootSalt: Buffer, columnName: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeBlindIndex = computeBlindIndex;
|
|
4
|
+
const security_1 = require("./security");
|
|
5
|
+
/**
|
|
6
|
+
* Computes a hardened, frequency-resistant blind index for a database field.
|
|
7
|
+
*
|
|
8
|
+
* Uses HKDF-SHA256 to derive a unique column key from the root salt,
|
|
9
|
+
* preventing cross-column frequency analysis. Zeroizes intermediate keys immediately.
|
|
10
|
+
*/
|
|
11
|
+
function computeBlindIndex(value, rootSalt, columnName) {
|
|
12
|
+
if (value === null || value === undefined)
|
|
13
|
+
return value;
|
|
14
|
+
const plaintext = typeof value === 'string' ? value : JSON.stringify(value);
|
|
15
|
+
const columnNameBuf = Buffer.from(columnName, 'utf8');
|
|
16
|
+
// 1. Derive column-specific key using HKDF-SHA256
|
|
17
|
+
const derivedColumnKey = (0, security_1.deriveHkdf)(rootSalt, null, columnNameBuf, 32);
|
|
18
|
+
// 2. Compute the final blind index using the derived column key
|
|
19
|
+
const plaintextBuf = Buffer.from(plaintext, 'utf8');
|
|
20
|
+
const blindIndex = (0, security_1.deriveHkdf)(derivedColumnKey, null, plaintextBuf, 32);
|
|
21
|
+
// 3. RAM Security: Zeroize the derived key immediately (Anti-Core Dump)
|
|
22
|
+
derivedColumnKey.fill(0);
|
|
23
|
+
return blindIndex.toString('hex');
|
|
24
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const prisma_1 = require("./prisma");
|
|
5
|
+
function printProgressBar(current, total) {
|
|
6
|
+
const percentage = Math.min(100, Math.floor((current / total) * 100));
|
|
7
|
+
const barLength = 40;
|
|
8
|
+
const completedLength = Math.floor((percentage / 100) * barLength);
|
|
9
|
+
const remainingLength = barLength - completedLength;
|
|
10
|
+
const progressBar = '='.repeat(completedLength) + '>'.repeat(completedLength > 0 && remainingLength > 0 ? 1 : 0) + ' '.repeat(Math.max(0, remainingLength - (completedLength > 0 && remainingLength > 0 ? 1 : 0)));
|
|
11
|
+
process.stdout.write(`\rProgress: [${progressBar}] ${percentage}% (${current}/${total} records)`);
|
|
12
|
+
}
|
|
13
|
+
async function run() {
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
if (args.length === 0 || (args[0] !== 'migrate' && args[0] !== 'compliance')) {
|
|
16
|
+
console.error("Usage: vollcrypt-db-guard <command> [options]");
|
|
17
|
+
console.error("\nCommands:");
|
|
18
|
+
console.error(" migrate Run shadow database migrations");
|
|
19
|
+
console.error(" compliance Scan database configurations and generate a compliance HTML scorecard");
|
|
20
|
+
console.error("\nMigrate Options:");
|
|
21
|
+
console.error(" --db-type <postgres|mongodb> Database type");
|
|
22
|
+
console.error(" --db-url <url> Database connection URL");
|
|
23
|
+
console.error(" --table <table-name> Table/collection name to migrate");
|
|
24
|
+
console.error(" --column <column-name> Column/field name to encrypt");
|
|
25
|
+
console.error(" --key <32-byte-hex-key> Encryption key (hex-encoded)");
|
|
26
|
+
console.error(" --active-version <version> Encryption key version (default: 1)");
|
|
27
|
+
console.error(" --chunk-size <size> Batch processing chunk size (default: 100)");
|
|
28
|
+
console.error(" --id-col <id-col-name> Primary key column (default: 'id' / '_id')");
|
|
29
|
+
console.error("\nCompliance Options:");
|
|
30
|
+
console.error(" --config <path-to-json-file> Path to configuration file");
|
|
31
|
+
console.error(" --output <output-html-path> Path to write HTML compliance report (default: compliance-report.html)");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (args[0] === 'compliance') {
|
|
35
|
+
const options = {};
|
|
36
|
+
for (let i = 1; i < args.length; i += 2) {
|
|
37
|
+
if (args[i] && args[i + 1]) {
|
|
38
|
+
const key = args[i].replace(/^--/, '');
|
|
39
|
+
const val = args[i + 1];
|
|
40
|
+
options[key] = val;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const configPath = options['config'];
|
|
44
|
+
const outputPath = options['output'] || 'compliance-report.html';
|
|
45
|
+
if (!configPath) {
|
|
46
|
+
console.error("Error: --config <path-to-json-file> is required for compliance scan.");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const fs = require('fs');
|
|
50
|
+
const path = require('path');
|
|
51
|
+
try {
|
|
52
|
+
const fullPath = path.resolve(configPath);
|
|
53
|
+
if (!fs.existsSync(fullPath)) {
|
|
54
|
+
console.error(`Error: Configuration file not found at ${fullPath}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const raw = fs.readFileSync(fullPath, 'utf8');
|
|
58
|
+
const config = JSON.parse(raw);
|
|
59
|
+
const { generateComplianceHtmlReport } = require('./compliance');
|
|
60
|
+
const html = generateComplianceHtmlReport(config);
|
|
61
|
+
const outFullPath = path.resolve(outputPath);
|
|
62
|
+
fs.writeFileSync(outFullPath, html, 'utf8');
|
|
63
|
+
console.log(`Compliance report generated successfully at ${outFullPath}`);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
console.error(`Error generating compliance report: ${err.message}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Parse arguments
|
|
72
|
+
const options = {};
|
|
73
|
+
for (let i = 1; i < args.length; i += 2) {
|
|
74
|
+
if (args[i] && args[i + 1]) {
|
|
75
|
+
const key = args[i].replace(/^--/, '');
|
|
76
|
+
const val = args[i + 1];
|
|
77
|
+
options[key] = val;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const dbType = options['db-type'];
|
|
81
|
+
const dbUrl = options['db-url'];
|
|
82
|
+
const table = options['table'] || options['collection'];
|
|
83
|
+
const column = options['column'];
|
|
84
|
+
const keyHex = options['key'];
|
|
85
|
+
const activeVersion = options['active-version'] || '1';
|
|
86
|
+
const chunkSize = parseInt(options['chunk-size'] || '100', 10);
|
|
87
|
+
const idCol = options['id-col'] || (dbType === 'mongodb' ? '_id' : 'id');
|
|
88
|
+
if (!dbType || !dbUrl || !table || !column || !keyHex) {
|
|
89
|
+
console.error("Error: Missing required arguments. --db-type, --db-url, --table, --column, and --key are required.");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
if (keyHex.length !== 64) {
|
|
93
|
+
console.error("Error: Key must be a 32-byte hex-encoded string (64 characters).");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const encryptionKey = Buffer.from(keyHex, 'hex');
|
|
97
|
+
console.log(`Starting shadow migration on: table/collection: "${table}", column: "${column}"`);
|
|
98
|
+
console.log(`Active key version: "${activeVersion}", Batch size: ${chunkSize}`);
|
|
99
|
+
if (dbType === 'postgres') {
|
|
100
|
+
await migratePostgres(dbUrl, table, column, idCol, encryptionKey, activeVersion, chunkSize);
|
|
101
|
+
}
|
|
102
|
+
else if (dbType === 'mongodb') {
|
|
103
|
+
await migrateMongo(dbUrl, table, column, idCol, encryptionKey, activeVersion, chunkSize);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.error(`Error: Unsupported db-type "${dbType}". Supported: postgres, mongodb.`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function migratePostgres(url, table, column, idCol, key, version, chunkSize) {
|
|
111
|
+
let Client;
|
|
112
|
+
try {
|
|
113
|
+
Client = require('pg').Client;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error("Error: The 'pg' package is not installed. Please install 'pg' to run migrations on PostgreSQL.");
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
const client = new Client({ connectionString: url });
|
|
120
|
+
await client.connect();
|
|
121
|
+
try {
|
|
122
|
+
// 1. Count unencrypted rows (value doesn't start with VOLLVALT:)
|
|
123
|
+
const countRes = await client.query(`SELECT COUNT(*) FROM "${table}" WHERE "${column}" IS NOT NULL AND "${column}" NOT LIKE 'VOLLVALT:%'`);
|
|
124
|
+
const total = parseInt(countRes.rows[0].count, 10);
|
|
125
|
+
if (total === 0) {
|
|
126
|
+
console.log("No unencrypted records found. Migration complete!");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.log(`Found ${total} unencrypted records. Processing...`);
|
|
130
|
+
let processed = 0;
|
|
131
|
+
printProgressBar(processed, total);
|
|
132
|
+
while (true) {
|
|
133
|
+
// 2. Fetch a batch of unencrypted rows
|
|
134
|
+
const batchRes = await client.query(`SELECT "${idCol}", "${column}" FROM "${table}" WHERE "${column}" IS NOT NULL AND "${column}" NOT LIKE 'VOLLVALT:%' LIMIT $1`, [chunkSize]);
|
|
135
|
+
if (batchRes.rows.length === 0) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
// 3. Encrypt and update each row
|
|
139
|
+
for (const row of batchRes.rows) {
|
|
140
|
+
const rawVal = row[column];
|
|
141
|
+
const encryptedVal = (0, prisma_1.encryptValue)(rawVal, key, version);
|
|
142
|
+
await client.query(`UPDATE "${table}" SET "${column}" = $1 WHERE "${idCol}" = $2`, [encryptedVal, row[idCol]]);
|
|
143
|
+
processed++;
|
|
144
|
+
printProgressBar(processed, total);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
console.log(`\nSuccessfully migrated ${processed} records!`);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
console.error(`\nMigration failed: ${err.message}`);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
await client.end();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function migrateMongo(url, collectionName, field, idCol, key, version, chunkSize) {
|
|
157
|
+
let MongoClient;
|
|
158
|
+
try {
|
|
159
|
+
MongoClient = require('mongodb').MongoClient;
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
console.error("Error: The 'mongodb' package is not installed. Please install 'mongodb' to run migrations on MongoDB.");
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
const client = new MongoClient(url);
|
|
166
|
+
await client.connect();
|
|
167
|
+
try {
|
|
168
|
+
const db = client.db();
|
|
169
|
+
const collection = db.collection(collectionName);
|
|
170
|
+
// Filter to find docs where field exists, is not null, and doesn't start with VOLLVALT:
|
|
171
|
+
const filter = {
|
|
172
|
+
[field]: {
|
|
173
|
+
$exists: true,
|
|
174
|
+
$ne: null,
|
|
175
|
+
$not: /^VOLLVALT:/
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const total = await collection.countDocuments(filter);
|
|
179
|
+
if (total === 0) {
|
|
180
|
+
console.log("No unencrypted records found. Migration complete!");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
console.log(`Found ${total} unencrypted records. Processing...`);
|
|
184
|
+
let processed = 0;
|
|
185
|
+
printProgressBar(processed, total);
|
|
186
|
+
while (true) {
|
|
187
|
+
const batch = await collection.find(filter).limit(chunkSize).toArray();
|
|
188
|
+
if (batch.length === 0) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
for (const doc of batch) {
|
|
192
|
+
const rawVal = doc[field];
|
|
193
|
+
const encryptedVal = (0, prisma_1.encryptValue)(rawVal, key, version);
|
|
194
|
+
await collection.updateOne({ [idCol]: doc[idCol] }, { $set: { [field]: encryptedVal } });
|
|
195
|
+
processed++;
|
|
196
|
+
printProgressBar(processed, total);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
console.log(`\nSuccessfully migrated ${processed} records!`);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
console.error(`\nMigration failed: ${err.message}`);
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
await client.close();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
run().catch((err) => {
|
|
209
|
+
console.error(err);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
});
|