mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 00:28:49 +00:00
458 lines
10 KiB
TypeScript
458 lines
10 KiB
TypeScript
/* eslint-disable no-console */
|
|
import crypto from "crypto";
|
|
import { Types } from "mongoose";
|
|
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
|
|
import { EESecretService } from "../../ee/services";
|
|
import { ISecretVersion, SecretSnapshot, SecretVersion } from "../../ee/models";
|
|
import {
|
|
BackupPrivateKey,
|
|
Bot,
|
|
ISecret,
|
|
Integration,
|
|
IntegrationAuth,
|
|
Secret,
|
|
SecretBlindIndexData,
|
|
ServiceTokenData,
|
|
Workspace
|
|
} from "../../models";
|
|
import { generateKeyPair } from "../../utils/crypto";
|
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
|
|
import {
|
|
ALGORITHM_AES_256_GCM,
|
|
ENCODING_SCHEME_BASE64,
|
|
ENCODING_SCHEME_UTF8
|
|
} from "../../variables";
|
|
import { InternalServerError } from "../errors";
|
|
|
|
/**
|
|
* Backfill secrets to ensure that they're all versioned and have
|
|
* corresponding secret versions
|
|
*/
|
|
export const backfillSecretVersions = async () => {
|
|
await Secret.updateMany({ version: { $exists: false } }, { $set: { version: 1 } });
|
|
|
|
const unversionedSecrets: ISecret[] = await Secret.aggregate([
|
|
{
|
|
$lookup: {
|
|
from: "secretversions",
|
|
localField: "_id",
|
|
foreignField: "secret",
|
|
as: "versions"
|
|
}
|
|
},
|
|
{
|
|
$match: {
|
|
versions: { $size: 0 }
|
|
}
|
|
}
|
|
]);
|
|
|
|
if (unversionedSecrets.length > 0) {
|
|
await EESecretService.addSecretVersions({
|
|
secretVersions: unversionedSecrets.map(
|
|
(s, idx) =>
|
|
new SecretVersion({
|
|
...s,
|
|
secret: s._id,
|
|
version: s.version ? s.version : 1,
|
|
isDeleted: false,
|
|
workspace: s.workspace,
|
|
environment: s.environment,
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
})
|
|
)
|
|
});
|
|
}
|
|
console.log("Migration: Secret version migration v1 complete");
|
|
};
|
|
|
|
/**
|
|
* Backfill workspace bots to ensure that every workspace has a bot
|
|
*/
|
|
export const backfillBots = async () => {
|
|
const encryptionKey = await getEncryptionKey();
|
|
const rootEncryptionKey = await getRootEncryptionKey();
|
|
|
|
const workspaceIdsWithBot = await Bot.distinct("workspace");
|
|
const workspaceIdsToAddBot = await Workspace.distinct("_id", {
|
|
_id: {
|
|
$nin: workspaceIdsWithBot
|
|
}
|
|
});
|
|
|
|
if (workspaceIdsToAddBot.length === 0) return;
|
|
|
|
const botsToInsert = await Promise.all(
|
|
workspaceIdsToAddBot.map(async (workspaceToAddBot) => {
|
|
const { publicKey, privateKey } = generateKeyPair();
|
|
|
|
if (rootEncryptionKey) {
|
|
const {
|
|
ciphertext: encryptedPrivateKey,
|
|
iv,
|
|
tag
|
|
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
|
|
|
return new Bot({
|
|
name: "Infisical Bot",
|
|
workspace: workspaceToAddBot,
|
|
isActive: false,
|
|
publicKey,
|
|
encryptedPrivateKey,
|
|
iv,
|
|
tag,
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_BASE64
|
|
});
|
|
} else if (encryptionKey) {
|
|
const {
|
|
ciphertext: encryptedPrivateKey,
|
|
iv,
|
|
tag
|
|
} = encryptSymmetric128BitHexKeyUTF8({
|
|
plaintext: privateKey,
|
|
key: encryptionKey
|
|
});
|
|
|
|
return new Bot({
|
|
name: "Infisical Bot",
|
|
workspace: workspaceToAddBot,
|
|
isActive: false,
|
|
publicKey,
|
|
encryptedPrivateKey,
|
|
iv,
|
|
tag,
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
});
|
|
}
|
|
|
|
throw InternalServerError({
|
|
message: "Failed to backfill workspace bots due to missing encryption key"
|
|
});
|
|
})
|
|
);
|
|
|
|
await Bot.insertMany(botsToInsert);
|
|
};
|
|
|
|
/**
|
|
* Backfill secret blind index data to ensure that every workspace
|
|
* has a secret blind index data
|
|
*/
|
|
export const backfillSecretBlindIndexData = async () => {
|
|
const encryptionKey = await getEncryptionKey();
|
|
const rootEncryptionKey = await getRootEncryptionKey();
|
|
|
|
const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct("workspace");
|
|
const workspaceIdsToBlindIndex = await Workspace.distinct("_id", {
|
|
_id: {
|
|
$nin: workspaceIdsBlindIndexed
|
|
}
|
|
});
|
|
|
|
if (workspaceIdsToBlindIndex.length === 0) return;
|
|
|
|
const secretBlindIndexDataToInsert = await Promise.all(
|
|
workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => {
|
|
const salt = crypto.randomBytes(16).toString("base64");
|
|
|
|
if (rootEncryptionKey) {
|
|
const {
|
|
ciphertext: encryptedSaltCiphertext,
|
|
iv: saltIV,
|
|
tag: saltTag
|
|
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
|
|
|
return new SecretBlindIndexData({
|
|
workspace: workspaceToBlindIndex,
|
|
encryptedSaltCiphertext,
|
|
saltIV,
|
|
saltTag,
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_BASE64
|
|
});
|
|
} else if (encryptionKey) {
|
|
const {
|
|
ciphertext: encryptedSaltCiphertext,
|
|
iv: saltIV,
|
|
tag: saltTag
|
|
} = encryptSymmetric128BitHexKeyUTF8({
|
|
plaintext: salt,
|
|
key: encryptionKey
|
|
});
|
|
|
|
return new SecretBlindIndexData({
|
|
workspace: workspaceToBlindIndex,
|
|
encryptedSaltCiphertext,
|
|
saltIV,
|
|
saltTag,
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
});
|
|
}
|
|
|
|
throw InternalServerError({
|
|
message: "Failed to backfill secret blind index data due to missing encryption key"
|
|
});
|
|
})
|
|
);
|
|
|
|
SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert);
|
|
};
|
|
|
|
/**
|
|
* Backfill Secret, SecretVersion, SecretBlindIndexData, Bot,
|
|
* BackupPrivateKey, IntegrationAuth collections to ensure that
|
|
* they all have encryption metadata documented
|
|
*/
|
|
export const backfillEncryptionMetadata = async () => {
|
|
// backfill secret encryption metadata
|
|
await Secret.updateMany(
|
|
{
|
|
algorithm: {
|
|
$exists: false
|
|
},
|
|
keyEncoding: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
}
|
|
}
|
|
);
|
|
|
|
// backfill secret version encryption metadata
|
|
await SecretVersion.updateMany(
|
|
{
|
|
algorithm: {
|
|
$exists: false
|
|
},
|
|
keyEncoding: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
}
|
|
}
|
|
);
|
|
|
|
// backfill secret blind index encryption metadata
|
|
await SecretBlindIndexData.updateMany(
|
|
{
|
|
algorithm: {
|
|
$exists: false
|
|
},
|
|
keyEncoding: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
}
|
|
}
|
|
);
|
|
|
|
// backfill bot encryption metadata
|
|
await Bot.updateMany(
|
|
{
|
|
algorithm: {
|
|
$exists: false
|
|
},
|
|
keyEncoding: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
}
|
|
}
|
|
);
|
|
|
|
// backfill backup private key encryption metadata
|
|
await BackupPrivateKey.updateMany(
|
|
{
|
|
algorithm: {
|
|
$exists: false
|
|
},
|
|
keyEncoding: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
}
|
|
}
|
|
);
|
|
|
|
// backfill integration auth encryption metadata
|
|
await IntegrationAuth.updateMany(
|
|
{
|
|
algorithm: {
|
|
$exists: false
|
|
},
|
|
keyEncoding: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
algorithm: ALGORITHM_AES_256_GCM,
|
|
keyEncoding: ENCODING_SCHEME_UTF8
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
export const backfillSecretFolders = async () => {
|
|
await Secret.updateMany(
|
|
{
|
|
folder: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
folder: "root"
|
|
}
|
|
}
|
|
);
|
|
|
|
await SecretVersion.updateMany(
|
|
{
|
|
folder: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
folder: "root"
|
|
}
|
|
}
|
|
);
|
|
|
|
// Back fill because tags were missing in secret versions
|
|
await SecretVersion.updateMany(
|
|
{
|
|
tags: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
tags: []
|
|
}
|
|
}
|
|
);
|
|
|
|
let secretSnapshots = await SecretSnapshot.find({
|
|
environment: {
|
|
$exists: false
|
|
}
|
|
})
|
|
.populate<{ secretVersions: ISecretVersion[] }>("secretVersions")
|
|
.limit(50);
|
|
|
|
while (secretSnapshots.length > 0) {
|
|
for (const secSnapshot of secretSnapshots) {
|
|
const groupSnapByEnv: Record<string, Array<ISecretVersion>> = {};
|
|
secSnapshot.secretVersions.forEach((secVer) => {
|
|
if (!groupSnapByEnv?.[secVer.environment]) groupSnapByEnv[secVer.environment] = [];
|
|
groupSnapByEnv[secVer.environment].push(secVer);
|
|
});
|
|
|
|
const newSnapshots = Object.keys(groupSnapByEnv).map((snapEnv) => {
|
|
const secretIdsOfEnvGroup = groupSnapByEnv[snapEnv]
|
|
? groupSnapByEnv[snapEnv].map((secretVersion) => secretVersion._id)
|
|
: [];
|
|
return {
|
|
...secSnapshot.toObject({ virtuals: false }),
|
|
_id: new Types.ObjectId(),
|
|
environment: snapEnv,
|
|
secretVersions: secretIdsOfEnvGroup
|
|
};
|
|
});
|
|
|
|
await SecretSnapshot.insertMany(newSnapshots);
|
|
await secSnapshot.delete();
|
|
}
|
|
|
|
secretSnapshots = await SecretSnapshot.find({
|
|
environment: {
|
|
$exists: false
|
|
}
|
|
})
|
|
.populate<{ secretVersions: ISecretVersion[] }>("secretVersions")
|
|
.limit(50);
|
|
}
|
|
|
|
console.log("Migration: Folder migration v1 complete");
|
|
};
|
|
|
|
export const backfillServiceToken = async () => {
|
|
await ServiceTokenData.updateMany(
|
|
{
|
|
secretPath: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
secretPath: "/"
|
|
}
|
|
}
|
|
);
|
|
console.log("Migration: Service token migration v1 complete");
|
|
};
|
|
|
|
export const backfillIntegration = async () => {
|
|
await Integration.updateMany(
|
|
{
|
|
secretPath: {
|
|
$exists: false
|
|
}
|
|
},
|
|
{
|
|
$set: {
|
|
secretPath: "/"
|
|
}
|
|
}
|
|
);
|
|
console.log("Migration: Integration migration v1 complete");
|
|
};
|
|
|
|
export const backfillServiceTokenMultiScope = async () => {
|
|
await ServiceTokenData.updateMany(
|
|
{
|
|
scopes: {
|
|
$exists: false
|
|
}
|
|
},
|
|
[
|
|
{
|
|
$set: {
|
|
scopes: [{ environment: "$environment", secretPath: "$secretPath" }]
|
|
}
|
|
},
|
|
{
|
|
$unset: ["environment", "secretPath"]
|
|
}
|
|
]
|
|
);
|
|
|
|
console.log("Migration: Service token migration v2 complete");
|
|
};
|