mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: added project data key
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasKmsSecretManagerEncryptedDataKey = await knex.schema.hasColumn(
|
||||
TableName.Project,
|
||||
"kmsSecretManagerEncryptedDataKey"
|
||||
);
|
||||
|
||||
await knex.schema.alterTable(TableName.Project, (tb) => {
|
||||
if (!hasKmsSecretManagerEncryptedDataKey) {
|
||||
tb.binary("kmsSecretManagerEncryptedDataKey");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasKmsSecretManagerEncryptedDataKey = await knex.schema.hasColumn(
|
||||
TableName.Project,
|
||||
"kmsSecretManagerEncryptedDataKey"
|
||||
);
|
||||
|
||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||
if (hasKmsSecretManagerEncryptedDataKey) {
|
||||
t.dropColumn("kmsSecretManagerEncryptedDataKey");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { zodBuffer } from "@app/lib/zod";
|
||||
|
||||
import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const ProjectsSchema = z.object({
|
||||
@@ -20,7 +22,8 @@ export const ProjectsSchema = z.object({
|
||||
pitVersionLimit: z.number().default(10),
|
||||
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
|
||||
auditLogsRetentionDays: z.number().nullable().optional(),
|
||||
kmsSecretManagerKeyId: z.string().uuid().nullable().optional()
|
||||
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
||||
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional()
|
||||
});
|
||||
|
||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||
|
||||
@@ -6,7 +6,11 @@ export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
|
||||
|
||||
// all the key prefixes used must be set here to avoid conflict
|
||||
export enum KeyStorePrefixes {
|
||||
SecretReplication = "secret-replication-import-lock"
|
||||
SecretReplication = "secret-replication-import-lock",
|
||||
KmsProjectDataKeyCreation = "kms-project-data-key-creation-lock",
|
||||
KmsProjectKeyCreation = "kms-project-key-creation-lock",
|
||||
WaitUntilReadyKmsProjectDataKeyCreation = "wait-until-ready-kms-project-data-key-creation-",
|
||||
WaitUntilReadyKmsProjectKeyCreation = "wait-until-ready-kms-project-key-creation-"
|
||||
}
|
||||
|
||||
type TWaitTillReady = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { randomSecureBytes } from "@app/lib/crypto";
|
||||
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||
@@ -167,35 +167,120 @@ export const kmsServiceFactory = ({
|
||||
};
|
||||
|
||||
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
|
||||
const keyId = await projectDAL.transaction(async (tx) => {
|
||||
const project = await projectDAL.findById(projectId, tx);
|
||||
if (!project) {
|
||||
throw new BadRequestError({ message: "Project not found" });
|
||||
let project = await projectDAL.findById(projectId);
|
||||
if (!project) {
|
||||
throw new BadRequestError({ message: "Project not found" });
|
||||
}
|
||||
|
||||
if (!project.kmsSecretManagerKeyId) {
|
||||
// create default kms key for certificate service
|
||||
const lock = await keyStore
|
||||
.acquireLock([KeyStorePrefixes.KmsProjectKeyCreation, projectId], 3000, { retryCount: 3 })
|
||||
.catch(() => null);
|
||||
|
||||
try {
|
||||
if (!lock) {
|
||||
await keyStore.waitTillReady({
|
||||
key: `${KeyStorePrefixes.WaitUntilReadyKmsProjectKeyCreation}${projectId}`,
|
||||
keyCheckCb: (val) => val === "true",
|
||||
waitingCb: () => logger.info("KMS. Waiting for project key to be created")
|
||||
});
|
||||
|
||||
project = await projectDAL.findById(projectId);
|
||||
} else {
|
||||
const kmsKeyId = await projectDAL.transaction(async (tx) => {
|
||||
const key = await generateKmsKey({
|
||||
isReserved: true,
|
||||
orgId: project.orgId,
|
||||
tx
|
||||
});
|
||||
|
||||
await projectDAL.updateById(
|
||||
projectId,
|
||||
{
|
||||
kmsSecretManagerKeyId: key.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
return key.id;
|
||||
});
|
||||
|
||||
await keyStore.setItemWithExpiry(
|
||||
`${KeyStorePrefixes.WaitUntilReadyKmsProjectKeyCreation}${projectId}`,
|
||||
10,
|
||||
"true"
|
||||
);
|
||||
|
||||
return kmsKeyId;
|
||||
}
|
||||
} finally {
|
||||
await lock?.release();
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.kmsSecretManagerKeyId) {
|
||||
// create default kms key for certificate service
|
||||
const key = await generateKmsKey({
|
||||
isReserved: true,
|
||||
orgId: project.orgId,
|
||||
tx
|
||||
});
|
||||
if (!project.kmsSecretManagerKeyId) {
|
||||
throw new Error("Missing project KMS key ID");
|
||||
}
|
||||
|
||||
await projectDAL.updateById(
|
||||
projectId,
|
||||
{
|
||||
kmsSecretManagerKeyId: key.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
return project.kmsSecretManagerKeyId;
|
||||
};
|
||||
|
||||
return key.id;
|
||||
const getProjectSecretManagerKmsDataKey = async (projectId: string) => {
|
||||
const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId);
|
||||
let project = await projectDAL.findById(projectId);
|
||||
|
||||
if (!project.kmsSecretManagerEncryptedDataKey) {
|
||||
const lock = await keyStore
|
||||
.acquireLock([KeyStorePrefixes.KmsProjectDataKeyCreation, projectId], 3000, { retryCount: 3 })
|
||||
.catch(() => null);
|
||||
|
||||
try {
|
||||
if (!lock) {
|
||||
await keyStore.waitTillReady({
|
||||
key: `${KeyStorePrefixes.WaitUntilReadyKmsProjectDataKeyCreation}${projectId}`,
|
||||
keyCheckCb: (val) => val === "true",
|
||||
waitingCb: () => logger.info("KMS. Waiting for project data key to be created")
|
||||
});
|
||||
|
||||
project = await projectDAL.findById(projectId);
|
||||
} else {
|
||||
const dataKey = randomSecureBytes();
|
||||
const kmsEncryptor = await encryptWithKmsKey({
|
||||
kmsId: kmsKeyId
|
||||
});
|
||||
|
||||
const { cipherTextBlob } = kmsEncryptor({
|
||||
plainText: dataKey
|
||||
});
|
||||
|
||||
await projectDAL.updateById(projectId, {
|
||||
kmsSecretManagerEncryptedDataKey: cipherTextBlob
|
||||
});
|
||||
|
||||
await keyStore.setItemWithExpiry(
|
||||
`${KeyStorePrefixes.WaitUntilReadyKmsProjectDataKeyCreation}${projectId}`,
|
||||
10,
|
||||
"true"
|
||||
);
|
||||
return dataKey;
|
||||
}
|
||||
} finally {
|
||||
await lock?.release();
|
||||
}
|
||||
}
|
||||
|
||||
return project.kmsSecretManagerKeyId;
|
||||
if (!project.kmsSecretManagerEncryptedDataKey) {
|
||||
throw new Error("Missing project data key");
|
||||
}
|
||||
|
||||
const kmsDecryptor = await decryptWithKmsKey({
|
||||
kmsId: kmsKeyId
|
||||
});
|
||||
|
||||
return keyId;
|
||||
return kmsDecryptor({
|
||||
cipherTextBlob: project.kmsSecretManagerEncryptedDataKey
|
||||
});
|
||||
};
|
||||
|
||||
const startService = async () => {
|
||||
@@ -251,6 +336,7 @@ export const kmsServiceFactory = ({
|
||||
decryptWithKmsKey,
|
||||
decryptWithInputKey,
|
||||
getOrgKmsKeyId,
|
||||
getProjectSecretManagerKmsKeyId
|
||||
getProjectSecretManagerKmsKeyId,
|
||||
getProjectSecretManagerKmsDataKey
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user