mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 10:26:00 +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 { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const ProjectsSchema = z.object({
|
export const ProjectsSchema = z.object({
|
||||||
@@ -20,7 +22,8 @@ export const ProjectsSchema = z.object({
|
|||||||
pitVersionLimit: z.number().default(10),
|
pitVersionLimit: z.number().default(10),
|
||||||
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
|
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
|
||||||
auditLogsRetentionDays: z.number().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>;
|
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
|
// all the key prefixes used must be set here to avoid conflict
|
||||||
export enum KeyStorePrefixes {
|
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 = {
|
type TWaitTillReady = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { Knex } from "knex";
|
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 { getConfig } from "@app/lib/config/env";
|
||||||
import { randomSecureBytes } from "@app/lib/crypto";
|
import { randomSecureBytes } from "@app/lib/crypto";
|
||||||
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
|
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||||
@@ -167,35 +167,120 @@ export const kmsServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
|
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
|
||||||
const keyId = await projectDAL.transaction(async (tx) => {
|
let project = await projectDAL.findById(projectId);
|
||||||
const project = await projectDAL.findById(projectId, tx);
|
if (!project) {
|
||||||
if (!project) {
|
throw new BadRequestError({ message: "Project not found" });
|
||||||
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) {
|
if (!project.kmsSecretManagerKeyId) {
|
||||||
// create default kms key for certificate service
|
throw new Error("Missing project KMS key ID");
|
||||||
const key = await generateKmsKey({
|
}
|
||||||
isReserved: true,
|
|
||||||
orgId: project.orgId,
|
|
||||||
tx
|
|
||||||
});
|
|
||||||
|
|
||||||
await projectDAL.updateById(
|
return project.kmsSecretManagerKeyId;
|
||||||
projectId,
|
};
|
||||||
{
|
|
||||||
kmsSecretManagerKeyId: key.id
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
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 () => {
|
const startService = async () => {
|
||||||
@@ -251,6 +336,7 @@ export const kmsServiceFactory = ({
|
|||||||
decryptWithKmsKey,
|
decryptWithKmsKey,
|
||||||
decryptWithInputKey,
|
decryptWithInputKey,
|
||||||
getOrgKmsKeyId,
|
getOrgKmsKeyId,
|
||||||
getProjectSecretManagerKmsKeyId
|
getProjectSecretManagerKmsKeyId,
|
||||||
|
getProjectSecretManagerKmsDataKey
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user