mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 09:26:14 +00:00
misc: finalized switching of project KMS
This commit is contained in:
@@ -226,7 +226,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { secretManagerKmsKey } = await server.services.kms.updateProjectKmsKey({
|
const { secretManagerKmsKey } = await server.services.project.updateProjectKmsKey({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@@ -235,6 +235,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_PROJECT_KMS,
|
||||||
|
metadata: {
|
||||||
|
secretManagerKmsKey: {
|
||||||
|
id: secretManagerKmsKey.id,
|
||||||
|
slug: secretManagerKmsKey.slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secretManagerKmsKey
|
secretManagerKmsKey
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ export enum EventType {
|
|||||||
CREATE_KMS = "create-kms",
|
CREATE_KMS = "create-kms",
|
||||||
UPDATE_KMS = "update-kms",
|
UPDATE_KMS = "update-kms",
|
||||||
DELETE_KMS = "delete-kms",
|
DELETE_KMS = "delete-kms",
|
||||||
GET_KMS = "get-kms"
|
GET_KMS = "get-kms",
|
||||||
|
UPDATE_PROJECT_KMS = "update-project-kms"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -1212,6 +1213,16 @@ interface GetKmsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UpdateProjectKmsEvent {
|
||||||
|
type: EventType.UPDATE_PROJECT_KMS;
|
||||||
|
metadata: {
|
||||||
|
secretManagerKmsKey: {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@@ -1317,4 +1328,5 @@ export type Event =
|
|||||||
| CreateKmsEvent
|
| CreateKmsEvent
|
||||||
| UpdateKmsEvent
|
| UpdateKmsEvent
|
||||||
| DeleteKmsEvent
|
| DeleteKmsEvent
|
||||||
| GetKmsEvent;
|
| GetKmsEvent
|
||||||
|
| UpdateProjectKmsEvent;
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedCrl = kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
|
const decryptedCrl = await kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
|
||||||
const crl = new x509.X509Crl(decryptedCrl);
|
const crl = new x509.X509Crl(decryptedCrl);
|
||||||
|
|
||||||
const base64crl = crl.toString("base64");
|
const base64crl = crl.toString("base64");
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export enum ProjectPermissionSub {
|
|||||||
SecretRotation = "secret-rotation",
|
SecretRotation = "secret-rotation",
|
||||||
Identity = "identity",
|
Identity = "identity",
|
||||||
CertificateAuthorities = "certificate-authorities",
|
CertificateAuthorities = "certificate-authorities",
|
||||||
Certificates = "certificates"
|
Certificates = "certificates",
|
||||||
|
Kms = "kms"
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubjectFields = {
|
type SubjectFields = {
|
||||||
@@ -60,7 +61,8 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback];
|
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
||||||
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
||||||
|
|
||||||
const buildAdminPermissionRules = () => {
|
const buildAdminPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
@@ -157,6 +159,8 @@ const buildAdminPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -310,8 +310,7 @@ export const registerRoutes = async (
|
|||||||
kmsDAL,
|
kmsDAL,
|
||||||
internalKmsDAL,
|
internalKmsDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL
|
||||||
permissionService
|
|
||||||
});
|
});
|
||||||
const externalKmsService = externalKmsServiceFactory({
|
const externalKmsService = externalKmsServiceFactory({
|
||||||
kmsDAL,
|
kmsDAL,
|
||||||
@@ -627,7 +626,8 @@ export const registerRoutes = async (
|
|||||||
certificateDAL,
|
certificateDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
keyStore
|
keyStore,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectEnvService = projectEnvServiceFactory({
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const getCaCredentials = async ({
|
|||||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
const decryptedPrivateKey = kmsDecryptor({
|
const decryptedPrivateKey = await kmsDecryptor({
|
||||||
cipherTextBlob: caSecret.encryptedPrivateKey
|
cipherTextBlob: caSecret.encryptedPrivateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,13 +129,13 @@ export const getCaCertChain = async ({
|
|||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedCaCert = kmsDecryptor({
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
cipherTextBlob: caCert.encryptedCertificate
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
});
|
});
|
||||||
|
|
||||||
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
||||||
|
|
||||||
const decryptedChain = kmsDecryptor({
|
const decryptedChain = await kmsDecryptor({
|
||||||
cipherTextBlob: caCert.encryptedCertificateChain
|
cipherTextBlob: caCert.encryptedCertificateChain
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ export const rebuildCaCrl = async ({
|
|||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
|
|
||||||
const privateKey = kmsDecryptor({
|
const privateKey = await kmsDecryptor({
|
||||||
cipherTextBlob: caSecret.encryptedPrivateKey
|
cipherTextBlob: caSecret.encryptedPrivateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ export const rebuildCaCrl = async ({
|
|||||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
|
const { cipherTextBlob: encryptedCrl } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(new Uint8Array(crl.rawData))
|
plainText: Buffer.from(new Uint8Array(crl.rawData))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const certificateAuthorityQueueFactory = ({
|
|||||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
const privateKey = kmsDecryptor({
|
const privateKey = await kmsDecryptor({
|
||||||
cipherTextBlob: caSecret.encryptedPrivateKey
|
cipherTextBlob: caSecret.encryptedPrivateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ export const certificateAuthorityQueueFactory = ({
|
|||||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
|
const { cipherTextBlob: encryptedCrl } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(new Uint8Array(crl.rawData))
|
plainText: Buffer.from(new Uint8Array(crl.rawData))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -181,11 +181,11 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(new Uint8Array(cert.rawData))
|
plainText: Buffer.from(new Uint8Array(cert.rawData))
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedCertificateChain } = kmsEncryptor({
|
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||||
plainText: Buffer.alloc(0)
|
plainText: Buffer.alloc(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
signingKey: keys.privateKey
|
signingKey: keys.privateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
|
const { cipherTextBlob: encryptedCrl } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(new Uint8Array(crl.rawData))
|
plainText: Buffer.from(new Uint8Array(crl.rawData))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -224,7 +224,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
|
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
|
||||||
const skObj = KeyObject.from(keys.privateKey);
|
const skObj = KeyObject.from(keys.privateKey);
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedPrivateKey } = kmsEncryptor({
|
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
|
||||||
plainText: skObj.export({
|
plainText: skObj.export({
|
||||||
type: "pkcs8",
|
type: "pkcs8",
|
||||||
format: "der"
|
format: "der"
|
||||||
@@ -458,7 +458,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
|
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
|
||||||
const decryptedCaCert = kmsDecryptor({
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
cipherTextBlob: caCert.encryptedCertificate
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -615,11 +615,11 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
kmsId: certificateManagerKmsId
|
kmsId: certificateManagerKmsId
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(new Uint8Array(certObj.rawData))
|
plainText: Buffer.from(new Uint8Array(certObj.rawData))
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedCertificateChain } = kmsEncryptor({
|
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(certificateChain)
|
plainText: Buffer.from(certificateChain)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -693,7 +693,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
kmsId: certificateManagerKmsId
|
kmsId: certificateManagerKmsId
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedCaCert = kmsDecryptor({
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
cipherTextBlob: caCert.encryptedCertificate
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -803,7 +803,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
kmsId: certificateManagerKmsId
|
kmsId: certificateManagerKmsId
|
||||||
});
|
});
|
||||||
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ export const certificateServiceFactory = ({
|
|||||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
kmsId: certificateManagerKeyId
|
kmsId: certificateManagerKeyId
|
||||||
});
|
});
|
||||||
const decryptedCert = kmsDecryptor({
|
const decryptedCert = await kmsDecryptor({
|
||||||
cipherTextBlob: certBody.encryptedCertificate
|
cipherTextBlob: certBody.encryptedCertificate
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const kmskeyDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
const result = await (tx || db.replicaNode())(TableName.KmsKey)
|
const result = await (tx || db.replicaNode())(TableName.KmsKey)
|
||||||
.where({ [`${TableName.KmsKey}.id` as "id"]: id })
|
.where({ [`${TableName.KmsKey}.id` as "id"]: id })
|
||||||
|
.join(TableName.Organization, `${TableName.KmsKey}.orgId`, `${TableName.Organization}.id`)
|
||||||
.leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`)
|
.leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`)
|
||||||
.leftJoin(TableName.ExternalKms, `${TableName.KmsKey}.id`, `${TableName.ExternalKms}.kmsKeyId`)
|
.leftJoin(TableName.ExternalKms, `${TableName.KmsKey}.id`, `${TableName.ExternalKms}.kmsKeyId`)
|
||||||
.first()
|
.first()
|
||||||
@@ -31,11 +32,19 @@ export const kmskeyDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("encryptedProviderInputs").withSchema(TableName.ExternalKms).as("externalKmsEncryptedProviderInput"),
|
db.ref("encryptedProviderInputs").withSchema(TableName.ExternalKms).as("externalKmsEncryptedProviderInput"),
|
||||||
db.ref("status").withSchema(TableName.ExternalKms).as("externalKmsStatus"),
|
db.ref("status").withSchema(TableName.ExternalKms).as("externalKmsStatus"),
|
||||||
db.ref("statusDetails").withSchema(TableName.ExternalKms).as("externalKmsStatusDetails")
|
db.ref("statusDetails").withSchema(TableName.ExternalKms).as("externalKmsStatusDetails")
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
db.ref("kmsDefaultKeyId").withSchema(TableName.Organization).as("orgKmsDefaultKeyId"),
|
||||||
|
db.ref("kmsEncryptedDataKey").withSchema(TableName.Organization).as("orgKmsEncryptedDataKey")
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
...KmsKeysSchema.parse(result),
|
...KmsKeysSchema.parse(result),
|
||||||
isExternal: Boolean(result?.externalKmsId),
|
isExternal: Boolean(result?.externalKmsId),
|
||||||
|
orgKms: {
|
||||||
|
id: result?.orgKmsDefaultKeyId,
|
||||||
|
encryptedDataKey: result?.orgKmsEncryptedDataKey
|
||||||
|
},
|
||||||
externalKms: result?.externalKmsId
|
externalKms: result?.externalKmsId
|
||||||
? {
|
? {
|
||||||
id: result.externalKmsId,
|
id: result.externalKmsId,
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import crypto from "node:crypto";
|
|
||||||
|
|
||||||
import { ForbiddenError } from "@casl/ability";
|
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { AwsKmsProviderFactory } from "@app/ee/services/external-kms/providers/aws-kms";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ExternalKmsAwsSchema,
|
||||||
|
KmsProviders,
|
||||||
|
TExternalKmsProviderFns
|
||||||
|
} from "@app/ee/services/external-kms/providers/model";
|
||||||
import { KeyStorePrefixes, 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";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
@@ -24,8 +25,7 @@ import {
|
|||||||
TDecryptWithKmsDTO,
|
TDecryptWithKmsDTO,
|
||||||
TEncryptionWithKeyDTO,
|
TEncryptionWithKeyDTO,
|
||||||
TEncryptWithKmsDTO,
|
TEncryptWithKmsDTO,
|
||||||
TGenerateKMSDTO,
|
TGenerateKMSDTO
|
||||||
TUpdateProjectKmsDTO
|
|
||||||
} from "./kms-types";
|
} from "./kms-types";
|
||||||
|
|
||||||
type TKmsServiceFactoryDep = {
|
type TKmsServiceFactoryDep = {
|
||||||
@@ -34,12 +34,12 @@ type TKmsServiceFactoryDep = {
|
|||||||
orgDAL: Pick<TOrgDALFactory, "findById" | "updateById" | "transaction">;
|
orgDAL: Pick<TOrgDALFactory, "findById" | "updateById" | "transaction">;
|
||||||
kmsRootConfigDAL: Pick<TKmsRootConfigDALFactory, "findById" | "create">;
|
kmsRootConfigDAL: Pick<TKmsRootConfigDALFactory, "findById" | "create">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "waitTillReady" | "setItemWithExpiry">;
|
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "waitTillReady" | "setItemWithExpiry">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
|
||||||
internalKmsDAL: Pick<TInternalKmsDALFactory, "create">;
|
internalKmsDAL: Pick<TInternalKmsDALFactory, "create">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TKmsServiceFactory = ReturnType<typeof kmsServiceFactory>;
|
export type TKmsServiceFactory = ReturnType<typeof kmsServiceFactory>;
|
||||||
|
|
||||||
|
const INTERNAL_KMS_KEY_ID = "internal";
|
||||||
const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000";
|
const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000";
|
||||||
|
|
||||||
const KMS_ROOT_CREATION_WAIT_KEY = "wait_till_ready_kms_root_key";
|
const KMS_ROOT_CREATION_WAIT_KEY = "wait_till_ready_kms_root_key";
|
||||||
@@ -54,8 +54,7 @@ export const kmsServiceFactory = ({
|
|||||||
keyStore,
|
keyStore,
|
||||||
internalKmsDAL,
|
internalKmsDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL
|
||||||
permissionService
|
|
||||||
}: TKmsServiceFactoryDep) => {
|
}: TKmsServiceFactoryDep) => {
|
||||||
let ROOT_ENCRYPTION_KEY = Buffer.alloc(0);
|
let ROOT_ENCRYPTION_KEY = Buffer.alloc(0);
|
||||||
|
|
||||||
@@ -91,22 +90,6 @@ export const kmsServiceFactory = ({
|
|||||||
return doc;
|
return doc;
|
||||||
};
|
};
|
||||||
|
|
||||||
const encryptWithKmsKey = async ({ kmsId }: Omit<TEncryptWithKmsDTO, "plainText">) => {
|
|
||||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
|
||||||
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
|
|
||||||
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
|
|
||||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
|
||||||
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
|
||||||
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
|
||||||
const encryptedPlainTextBlob = cipher.encrypt(plainText, kmsKey);
|
|
||||||
|
|
||||||
// Buffer#1 encrypted text + Buffer#2 version number
|
|
||||||
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
|
|
||||||
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
|
|
||||||
return { cipherTextBlob };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const encryptWithInputKey = async ({ key }: Omit<TEncryptionWithKeyDTO, "plainText">) => {
|
const encryptWithInputKey = async ({ key }: Omit<TEncryptionWithKeyDTO, "plainText">) => {
|
||||||
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
|
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
|
||||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||||
@@ -119,19 +102,6 @@ export const kmsServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const decryptWithKmsKey = async ({ kmsId }: Omit<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
|
||||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
|
||||||
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
|
|
||||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
|
||||||
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
|
||||||
|
|
||||||
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
|
||||||
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
|
|
||||||
const decryptedBlob = cipher.decrypt(cipherTextBlob, kmsKey);
|
|
||||||
return decryptedBlob;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const decryptWithInputKey = async ({ key }: Omit<TDecryptWithKeyDTO, "cipherTextBlob">) => {
|
const decryptWithInputKey = async ({ key }: Omit<TDecryptWithKeyDTO, "cipherTextBlob">) => {
|
||||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||||
|
|
||||||
@@ -146,7 +116,7 @@ export const kmsServiceFactory = ({
|
|||||||
const keyId = await orgDAL.transaction(async (tx) => {
|
const keyId = await orgDAL.transaction(async (tx) => {
|
||||||
const org = await orgDAL.findById(orgId, tx);
|
const org = await orgDAL.findById(orgId, tx);
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new BadRequestError({ message: "Org not found" });
|
throw new NotFoundError({ message: "Org not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!org.kmsDefaultKeyId) {
|
if (!org.kmsDefaultKeyId) {
|
||||||
@@ -174,23 +144,154 @@ export const kmsServiceFactory = ({
|
|||||||
return keyId;
|
return keyId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const decryptWithKmsKey = async ({ kmsId }: Omit<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
||||||
|
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
||||||
|
if (!kmsDoc) {
|
||||||
|
throw new NotFoundError({ message: "KMS ID not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kmsDoc.externalKms) {
|
||||||
|
let externalKms: TExternalKmsProviderFns;
|
||||||
|
|
||||||
|
if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) {
|
||||||
|
throw new Error("Invalid organization KMS");
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgKmsDecryptor = await decryptWithKmsKey({
|
||||||
|
kmsId: kmsDoc.orgKms.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// fetch encryptedDataKey straight from kmsDoc by joining it in query :D
|
||||||
|
const orgKmsDataKey = await orgKmsDecryptor({
|
||||||
|
cipherTextBlob: kmsDoc.orgKms.encryptedDataKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsDecryptor = await decryptWithInputKey({
|
||||||
|
key: orgKmsDataKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedProviderInputBlob = kmsDecryptor({
|
||||||
|
cipherTextBlob: kmsDoc.externalKms.encryptedProviderInput
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (kmsDoc.externalKms.provider) {
|
||||||
|
case KmsProviders.Aws: {
|
||||||
|
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
||||||
|
);
|
||||||
|
|
||||||
|
externalKms = await AwsKmsProviderFactory({
|
||||||
|
inputs: decryptedProviderInput
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid KMS provider.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return async ({ cipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
||||||
|
const { data } = await externalKms.decrypt(cipherTextBlob);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal KMS
|
||||||
|
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||||
|
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||||
|
|
||||||
|
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
||||||
|
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
|
||||||
|
const decryptedBlob = cipher.decrypt(cipherTextBlob, kmsKey);
|
||||||
|
return Promise.resolve(decryptedBlob);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const encryptWithKmsKey = async ({ kmsId }: Omit<TEncryptWithKmsDTO, "plainText">, tx?: Knex) => {
|
||||||
|
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId, tx);
|
||||||
|
if (!kmsDoc) {
|
||||||
|
throw new NotFoundError({ message: "KMS ID not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kmsDoc.externalKms) {
|
||||||
|
let externalKms: TExternalKmsProviderFns;
|
||||||
|
if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) {
|
||||||
|
throw new Error("Invalid organization KMS");
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgKmsDecryptor = await decryptWithKmsKey({
|
||||||
|
kmsId: kmsDoc.orgKms.id
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgKmsDataKey = await orgKmsDecryptor({
|
||||||
|
cipherTextBlob: kmsDoc.orgKms.encryptedDataKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsDecryptor = await decryptWithInputKey({
|
||||||
|
key: orgKmsDataKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedProviderInputBlob = kmsDecryptor({
|
||||||
|
cipherTextBlob: kmsDoc.externalKms.encryptedProviderInput
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (kmsDoc.externalKms.provider) {
|
||||||
|
case KmsProviders.Aws: {
|
||||||
|
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
||||||
|
);
|
||||||
|
|
||||||
|
externalKms = await AwsKmsProviderFactory({
|
||||||
|
inputs: decryptedProviderInput
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid KMS provider.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return async ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||||
|
const { encryptedBlob } = await externalKms.encrypt(plainText);
|
||||||
|
|
||||||
|
return { cipherTextBlob: encryptedBlob };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal KMS
|
||||||
|
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
|
||||||
|
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||||
|
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||||
|
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||||
|
const encryptedPlainTextBlob = cipher.encrypt(plainText, kmsKey);
|
||||||
|
|
||||||
|
// Buffer#1 encrypted text + Buffer#2 version number
|
||||||
|
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
|
||||||
|
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
|
||||||
|
|
||||||
|
return Promise.resolve({ cipherTextBlob });
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const getOrgKmsDataKey = async (orgId: string) => {
|
const getOrgKmsDataKey = async (orgId: string) => {
|
||||||
const kmsKeyId = await getOrgKmsKeyId(orgId);
|
const kmsKeyId = await getOrgKmsKeyId(orgId);
|
||||||
const orgKmsDataKey = await orgDAL.transaction(async (tx) => {
|
const orgKmsDataKey = await orgDAL.transaction(async (tx) => {
|
||||||
const org = await orgDAL.findById(orgId, tx);
|
const org = await orgDAL.findById(orgId, tx);
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new BadRequestError({ message: "Org not found" });
|
throw new NotFoundError({ message: "Org not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
let encryptedDataKey = org.kmsEncryptedDataKey;
|
let encryptedDataKey = org.kmsEncryptedDataKey;
|
||||||
if (!encryptedDataKey) {
|
if (!encryptedDataKey) {
|
||||||
const dataKey = crypto.randomBytes(32);
|
const dataKey = randomSecureBytes();
|
||||||
const kmsEncryptor = await encryptWithKmsKey({
|
const kmsEncryptor = await encryptWithKmsKey(
|
||||||
kmsId: kmsKeyId
|
{
|
||||||
});
|
kmsId: kmsKeyId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
const { cipherTextBlob } = kmsEncryptor({
|
const { cipherTextBlob } = await kmsEncryptor({
|
||||||
plainText: dataKey
|
plainText: dataKey
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -221,11 +322,10 @@ export const kmsServiceFactory = ({
|
|||||||
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
|
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
|
||||||
let project = await projectDAL.findById(projectId);
|
let project = await projectDAL.findById(projectId);
|
||||||
if (!project) {
|
if (!project) {
|
||||||
throw new BadRequestError({ message: "Project not found" });
|
throw new NotFoundError({ message: "Project not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!project.kmsSecretManagerKeyId) {
|
if (!project.kmsSecretManagerKeyId) {
|
||||||
// create default kms key for certificate service
|
|
||||||
const lock = await keyStore
|
const lock = await keyStore
|
||||||
.acquireLock([KeyStorePrefixes.KmsProjectKeyCreation, projectId], 3000, { retryCount: 3 })
|
.acquireLock([KeyStorePrefixes.KmsProjectKeyCreation, projectId], 3000, { retryCount: 3 })
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
@@ -309,7 +409,7 @@ export const kmsServiceFactory = ({
|
|||||||
kmsId: kmsKeyId
|
kmsId: kmsKeyId
|
||||||
});
|
});
|
||||||
|
|
||||||
const { cipherTextBlob } = kmsEncryptor({
|
const { cipherTextBlob } = await kmsEncryptor({
|
||||||
plainText: dataKey
|
plainText: dataKey
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -344,17 +444,38 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
const updateProjectSecretManagerKmsKey = async (projectId: string, kmsId: string) => {
|
const updateProjectSecretManagerKmsKey = async (projectId: string, kmsId: string) => {
|
||||||
const currentKms = await getProjectSecretManagerKmsKey(projectId);
|
const currentKms = await getProjectSecretManagerKmsKey(projectId);
|
||||||
const dataKey = await getProjectSecretManagerKmsDataKey(projectId);
|
|
||||||
|
|
||||||
if (currentKms.isReserved && kmsId === "internal") {
|
if ((currentKms.isReserved && kmsId === INTERNAL_KMS_KEY_ID) || currentKms.id === kmsId) {
|
||||||
return currentKms;
|
return currentKms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kmsId !== INTERNAL_KMS_KEY_ID) {
|
||||||
|
const project = await projectDAL.findById(projectId);
|
||||||
|
if (!project) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Project not found."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
||||||
|
if (!kmsDoc) {
|
||||||
|
throw new NotFoundError({ message: "KMS ID not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kmsDoc.orgId !== project.orgId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "KMS ID does not belong in the organization."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataKey = await getProjectSecretManagerKmsDataKey(projectId);
|
||||||
|
|
||||||
return kmsDAL.transaction(async (tx) => {
|
return kmsDAL.transaction(async (tx) => {
|
||||||
const project = await projectDAL.findById(projectId, tx);
|
const project = await projectDAL.findById(projectId, tx);
|
||||||
let newKmsId = kmsId;
|
let newKmsId = kmsId;
|
||||||
|
|
||||||
if (newKmsId === "internal") {
|
if (newKmsId === INTERNAL_KMS_KEY_ID) {
|
||||||
const key = await generateKmsKey({
|
const key = await generateKmsKey({
|
||||||
isReserved: true,
|
isReserved: true,
|
||||||
orgId: project.orgId,
|
orgId: project.orgId,
|
||||||
@@ -364,8 +485,8 @@ export const kmsServiceFactory = ({
|
|||||||
newKmsId = key.id;
|
newKmsId = key.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const kmsEncryptor = await encryptWithKmsKey({ kmsId: newKmsId });
|
const kmsEncryptor = await encryptWithKmsKey({ kmsId: newKmsId }, tx);
|
||||||
const { cipherTextBlob } = kmsEncryptor({ plainText: dataKey });
|
const { cipherTextBlob } = await kmsEncryptor({ plainText: dataKey });
|
||||||
await projectDAL.updateById(
|
await projectDAL.updateById(
|
||||||
projectId,
|
projectId,
|
||||||
{
|
{
|
||||||
@@ -379,45 +500,10 @@ export const kmsServiceFactory = ({
|
|||||||
await kmsDAL.deleteById(currentKms.id, tx);
|
await kmsDAL.deleteById(currentKms.id, tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return kmsDAL.findByIdWithAssociatedKms(newKmsId);
|
return kmsDAL.findByIdWithAssociatedKms(newKmsId, tx);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateProjectKmsKey = async ({
|
|
||||||
projectId,
|
|
||||||
secretManagerKmsKeyId,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
}: TUpdateProjectKmsDTO) => {
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
|
||||||
if (secretManagerKmsKeyId !== "internal") {
|
|
||||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(secretManagerKmsKeyId);
|
|
||||||
if (!kmsDoc) {
|
|
||||||
throw new BadRequestError({ message: "KMS ID not found." });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kmsDoc.orgId !== actorOrgId) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "KMS ID does not belong in the organization."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
secretManagerKmsKey: await updateProjectSecretManagerKmsKey(projectId, secretManagerKmsKeyId)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const startService = async () => {
|
const startService = async () => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
// This will switch to a seal process and HMS flow in future
|
// This will switch to a seal process and HMS flow in future
|
||||||
@@ -475,7 +561,6 @@ export const kmsServiceFactory = ({
|
|||||||
getOrgKmsDataKey,
|
getOrgKmsDataKey,
|
||||||
getProjectSecretManagerKmsDataKey,
|
getProjectSecretManagerKmsDataKey,
|
||||||
getProjectSecretManagerKmsKey,
|
getProjectSecretManagerKmsKey,
|
||||||
updateProjectKmsKey,
|
|
||||||
updateProjectSecretManagerKmsKey
|
updateProjectSecretManagerKmsKey
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
|
||||||
|
|
||||||
export type TGenerateKMSDTO = {
|
export type TGenerateKMSDTO = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
isReserved?: boolean;
|
isReserved?: boolean;
|
||||||
@@ -28,7 +26,3 @@ export type TDecryptWithKeyDTO = {
|
|||||||
key: Buffer;
|
key: Buffer;
|
||||||
cipherTextBlob: Buffer;
|
cipherTextBlob: Buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateProjectKmsDTO = {
|
|
||||||
secretManagerKmsKeyId: string;
|
|
||||||
} & TProjectPermission;
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { TCertificateAuthorityDALFactory } from "../certificate-authority/certif
|
|||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal";
|
||||||
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TOrgServiceFactory } from "../org/org-service";
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||||
@@ -43,6 +44,7 @@ import {
|
|||||||
TToggleProjectAutoCapitalizationDTO,
|
TToggleProjectAutoCapitalizationDTO,
|
||||||
TUpdateAuditLogsRetentionDTO,
|
TUpdateAuditLogsRetentionDTO,
|
||||||
TUpdateProjectDTO,
|
TUpdateProjectDTO,
|
||||||
|
TUpdateProjectKmsDTO,
|
||||||
TUpdateProjectNameDTO,
|
TUpdateProjectNameDTO,
|
||||||
TUpdateProjectVersionLimitDTO,
|
TUpdateProjectVersionLimitDTO,
|
||||||
TUpgradeProjectDTO
|
TUpgradeProjectDTO
|
||||||
@@ -76,6 +78,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findOne">;
|
orgDAL: Pick<TOrgDALFactory, "findOne">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "deleteItem">;
|
keyStore: Pick<TKeyStoreFactory, "deleteItem">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "updateProjectSecretManagerKmsKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>;
|
export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>;
|
||||||
@@ -100,7 +103,8 @@ export const projectServiceFactory = ({
|
|||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
keyStore
|
keyStore,
|
||||||
|
kmsService
|
||||||
}: TProjectServiceFactoryDep) => {
|
}: TProjectServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Create workspace. Make user the admin
|
* Create workspace. Make user the admin
|
||||||
@@ -664,6 +668,31 @@ export const projectServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateProjectKmsKey = async ({
|
||||||
|
projectId,
|
||||||
|
secretManagerKmsKeyId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateProjectKmsDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
|
||||||
|
|
||||||
|
const secretManagerKmsKey = await kmsService.updateProjectSecretManagerKmsKey(projectId, secretManagerKmsKeyId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
secretManagerKmsKey
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createProject,
|
createProject,
|
||||||
deleteProject,
|
deleteProject,
|
||||||
@@ -677,6 +706,7 @@ export const projectServiceFactory = ({
|
|||||||
listProjectCas,
|
listProjectCas,
|
||||||
listProjectCertificates,
|
listProjectCertificates,
|
||||||
updateVersionLimit,
|
updateVersionLimit,
|
||||||
updateAuditLogsRetention
|
updateAuditLogsRetention,
|
||||||
|
updateProjectKmsKey
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -103,3 +103,7 @@ export type TListProjectCertsDTO = {
|
|||||||
friendlyName?: string;
|
friendlyName?: string;
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateProjectKmsDTO = {
|
||||||
|
secretManagerKmsKeyId: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export enum ProjectPermissionSub {
|
|||||||
SecretRotation = "secret-rotation",
|
SecretRotation = "secret-rotation",
|
||||||
Identity = "identity",
|
Identity = "identity",
|
||||||
CertificateAuthorities = "certificate-authorities",
|
CertificateAuthorities = "certificate-authorities",
|
||||||
Certificates = "certificates"
|
Certificates = "certificates",
|
||||||
|
Kms = "kms"
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubjectFields = {
|
type SubjectFields = {
|
||||||
|
|||||||
+53
-40
@@ -1,10 +1,11 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { Button, ContentLoader, FormControl, Select, SelectItem } from "@app/components/v2";
|
import { Button, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
@@ -19,30 +20,41 @@ const formSchema = z.object({
|
|||||||
|
|
||||||
type TForm = z.infer<typeof formSchema>;
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
const INTERNAL_KMS_KEY_ID = "internal";
|
||||||
|
|
||||||
export const EncryptionTab = () => {
|
export const EncryptionTab = () => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const { data: externalKmsList, isLoading: isExternalKmsListLoading } = useGetExternalKmsList(
|
const { data: externalKmsList } = useGetExternalKmsList(currentOrg?.id!);
|
||||||
currentOrg?.id!
|
const { data: activeKms } = useGetActiveProjectKms(currentWorkspace?.id!);
|
||||||
);
|
|
||||||
const { data: activeKms, isLoading: isActiveKmsLoading } = useGetActiveProjectKms(
|
|
||||||
currentWorkspace?.id!
|
|
||||||
);
|
|
||||||
|
|
||||||
const { mutateAsync: updateProjectKms } = useUpdateProjectKms(currentWorkspace?.id!);
|
const { mutateAsync: updateProjectKms } = useUpdateProjectKms(currentWorkspace?.id!);
|
||||||
|
const [kmsKeyId, setKmsKeyId] = useState("");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
formState: { isSubmitting }
|
setValue,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
} = useForm<TForm>({
|
} = useForm<TForm>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema)
|
||||||
defaultValues: {
|
|
||||||
kmsKeyId: activeKms?.isExternal ? activeKms?.id : "internal"
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeKms) {
|
||||||
|
setKmsKeyId(activeKms.isExternal ? activeKms.id : INTERNAL_KMS_KEY_ID);
|
||||||
|
} else {
|
||||||
|
setKmsKeyId(INTERNAL_KMS_KEY_ID);
|
||||||
|
}
|
||||||
|
}, [activeKms]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (kmsKeyId) {
|
||||||
|
setValue("kmsKeyId", kmsKeyId);
|
||||||
|
}
|
||||||
|
}, [kmsKeyId]);
|
||||||
|
|
||||||
const onFormSubmit = async (data: TForm) => {
|
const onFormSubmit = async (data: TForm) => {
|
||||||
try {
|
try {
|
||||||
await updateProjectKms({
|
await updateProjectKms({
|
||||||
@@ -68,41 +80,42 @@ export const EncryptionTab = () => {
|
|||||||
Select which Key Management System to use for encrypting your project data
|
Select which Key Management System to use for encrypting your project data
|
||||||
</p>
|
</p>
|
||||||
<div className="mb-6 max-w-md">
|
<div className="mb-6 max-w-md">
|
||||||
{isExternalKmsListLoading || isActiveKmsLoading ? (
|
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Kms}>
|
||||||
<ContentLoader />
|
{(isAllowed) => (
|
||||||
) : (
|
<Controller
|
||||||
<Controller
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
<Select
|
||||||
<Select
|
{...field}
|
||||||
{...field}
|
isDisabled={!isAllowed}
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
onChange(e);
|
onChange(e);
|
||||||
}}
|
}}
|
||||||
className="w-3/4 bg-mineshaft-600"
|
className="w-3/4 bg-mineshaft-600"
|
||||||
>
|
>
|
||||||
<SelectItem value="internal" key="kms-internal">
|
<SelectItem value={INTERNAL_KMS_KEY_ID} key="kms-internal">
|
||||||
Default Infisical KMS
|
Default Infisical KMS
|
||||||
</SelectItem>
|
|
||||||
{externalKmsList?.map((kms) => (
|
|
||||||
<SelectItem value={kms.id} key={`kms-${kms.id}`}>
|
|
||||||
{kms.slug}
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
{externalKmsList?.map((kms) => (
|
||||||
</Select>
|
<SelectItem value={kms.id} key={`kms-${kms.id}`}>
|
||||||
</FormControl>
|
{kms.slug}
|
||||||
)}
|
</SelectItem>
|
||||||
control={control}
|
))}
|
||||||
name="kmsKeyId"
|
</Select>
|
||||||
/>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
control={control}
|
||||||
|
name="kmsKeyId"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ProjectPermissionCan>
|
||||||
</div>
|
</div>
|
||||||
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Workspace}>
|
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Workspace}>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
type="submit"
|
type="submit"
|
||||||
isDisabled={!isAllowed || isSubmitting}
|
isDisabled={!isAllowed || isSubmitting || !isDirty}
|
||||||
isLoading={isSubmitting}
|
isLoading={isSubmitting}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
|
|||||||
Reference in New Issue
Block a user