mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 01:52:22 +00:00
feature secret rotations v2
This commit is contained in:
@@ -25,6 +25,7 @@ import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-app
|
||||
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
||||
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
|
||||
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { diff, groupBy } from "@app/lib/fn";
|
||||
import { setKnexStringValue } from "@app/lib/knex";
|
||||
@@ -414,6 +415,8 @@ export const secretV2BridgeServiceFactory = ({
|
||||
});
|
||||
if (!sharedSecretToModify)
|
||||
throw new NotFoundError({ message: `Secret with name ${inputSecret.secretName} not found` });
|
||||
if (sharedSecretToModify.isRotatedSecret && (inputSecret.newSecretName || inputSecret.secretValue))
|
||||
throw new BadRequestError({ message: "Cannot update rotated secret name or value" });
|
||||
secretId = sharedSecretToModify.id;
|
||||
secret = sharedSecretToModify;
|
||||
}
|
||||
@@ -624,66 +627,79 @@ export const secretV2BridgeServiceFactory = ({
|
||||
})
|
||||
);
|
||||
|
||||
const deletedSecret = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkDelete({
|
||||
projectId,
|
||||
folderId,
|
||||
actorId,
|
||||
secretDAL,
|
||||
secretQueueService,
|
||||
inputSecrets: [
|
||||
{
|
||||
type: inputSecret.type as SecretType,
|
||||
secretKey: inputSecret.secretName
|
||||
}
|
||||
],
|
||||
tx
|
||||
})
|
||||
);
|
||||
try {
|
||||
const deletedSecret = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkDelete({
|
||||
projectId,
|
||||
folderId,
|
||||
actorId,
|
||||
secretDAL,
|
||||
secretQueueService,
|
||||
inputSecrets: [
|
||||
{
|
||||
type: inputSecret.type as SecretType,
|
||||
secretKey: inputSecret.secretName
|
||||
}
|
||||
],
|
||||
tx
|
||||
})
|
||||
);
|
||||
|
||||
if (inputSecret.type === SecretType.Shared) {
|
||||
await snapshotService.performSnapshot(folderId);
|
||||
await secretQueueService.syncSecrets({
|
||||
secretPath,
|
||||
actorId,
|
||||
actor,
|
||||
projectId,
|
||||
orgId: actorOrgId,
|
||||
environmentSlug: folder.environment.slug
|
||||
if (inputSecret.type === SecretType.Shared) {
|
||||
await snapshotService.performSnapshot(folderId);
|
||||
await secretQueueService.syncSecrets({
|
||||
secretPath,
|
||||
actorId,
|
||||
actor,
|
||||
projectId,
|
||||
orgId: actorOrgId,
|
||||
environmentSlug: folder.environment.slug
|
||||
});
|
||||
}
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
}
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
{
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretToDelete.key,
|
||||
secretTags: secretToDelete.tags?.map((el) => el.slug)
|
||||
}
|
||||
);
|
||||
|
||||
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
{
|
||||
return reshapeBridgeSecret(
|
||||
projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: secretToDelete.key,
|
||||
secretTags: secretToDelete.tags?.map((el) => el.slug)
|
||||
{
|
||||
...deletedSecret[0],
|
||||
value: deletedSecret[0].encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedValue }).toString()
|
||||
: "",
|
||||
comment: deletedSecret[0].encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedComment }).toString()
|
||||
: ""
|
||||
},
|
||||
secretValueHidden
|
||||
);
|
||||
} catch (err) {
|
||||
// deferred errors aren't return as DatabaseError
|
||||
const error = err as { code: string; table: string };
|
||||
if (
|
||||
error?.code === DatabaseErrorCode.ForeignKeyViolation &&
|
||||
error?.table === TableName.SecretRotationV2SecretMapping
|
||||
) {
|
||||
throw new BadRequestError({ message: "Cannot delete rotated secrets" });
|
||||
}
|
||||
);
|
||||
|
||||
return reshapeBridgeSecret(
|
||||
projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
{
|
||||
...deletedSecret[0],
|
||||
value: deletedSecret[0].encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedValue }).toString()
|
||||
: "",
|
||||
comment: deletedSecret[0].encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedComment }).toString()
|
||||
: ""
|
||||
},
|
||||
secretValueHidden
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
// get unique secrets count for multiple envs
|
||||
@@ -946,6 +962,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
projectId
|
||||
});
|
||||
|
||||
// scott: if any of this changes it also needs to be mirrored in secret rotation for getting dashboard secrets
|
||||
const decryptedSecrets = secrets
|
||||
.filter((el) => {
|
||||
const canDescribeSecret = hasSecretReadValueOrDescribePermission(
|
||||
@@ -1667,6 +1684,13 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretTags: el.tags.map((i) => i.slug)
|
||||
})
|
||||
);
|
||||
|
||||
if (el.isRotatedSecret) {
|
||||
const input = secretsToUpdateGroupByPath[secretPath].find((i) => i.secretKey === el.key);
|
||||
|
||||
if (input && (input.newSecretName || input.secretValue))
|
||||
throw new BadRequestError({ message: `Cannot update rotated secret name or value: ${el.key}` });
|
||||
}
|
||||
});
|
||||
|
||||
// get all tags
|
||||
@@ -1969,61 +1993,76 @@ export const secretV2BridgeServiceFactory = ({
|
||||
);
|
||||
});
|
||||
|
||||
const secretsDeleted = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkDelete({
|
||||
secretDAL,
|
||||
secretQueueService,
|
||||
inputSecrets: inputSecrets.map(({ type, secretKey }) => ({
|
||||
secretKey,
|
||||
type: type || SecretType.Shared
|
||||
})),
|
||||
projectId,
|
||||
folderId,
|
||||
actorId,
|
||||
tx
|
||||
})
|
||||
);
|
||||
|
||||
// await snapshotService.performSnapshot(folderId);
|
||||
await secretQueueService.syncSecrets({
|
||||
actor,
|
||||
actorId,
|
||||
secretPath,
|
||||
projectId,
|
||||
orgId: actorOrgId,
|
||||
environmentSlug: folder.environment.slug
|
||||
});
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
return secretsDeleted.map((el) => {
|
||||
const secretToDeleteMatch = secretsToDelete.find(
|
||||
(i) => i.key === el.key && (i.type || SecretType.Shared) === el.type
|
||||
try {
|
||||
const secretsDeleted = await secretDAL.transaction(async (tx) =>
|
||||
fnSecretBulkDelete({
|
||||
secretDAL,
|
||||
secretQueueService,
|
||||
inputSecrets: inputSecrets.map(({ type, secretKey }) => ({
|
||||
secretKey,
|
||||
type: type || SecretType.Shared
|
||||
})),
|
||||
projectId,
|
||||
folderId,
|
||||
actorId,
|
||||
tx
|
||||
})
|
||||
);
|
||||
|
||||
const secretValueHidden =
|
||||
!secretToDeleteMatch ||
|
||||
!hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||
await snapshotService.performSnapshot(folderId);
|
||||
await secretQueueService.syncSecrets({
|
||||
actor,
|
||||
actorId,
|
||||
secretPath,
|
||||
projectId,
|
||||
orgId: actorOrgId,
|
||||
environmentSlug: folder.environment.slug
|
||||
});
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
return secretsDeleted.map((el) => {
|
||||
const secretToDeleteMatch = secretsToDelete.find(
|
||||
(i) => i.key === el.key && (i.type || SecretType.Shared) === el.type
|
||||
);
|
||||
|
||||
const secretValueHidden =
|
||||
!secretToDeleteMatch ||
|
||||
!hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.key,
|
||||
secretTags: secretToDeleteMatch.tags?.map((i) => i.slug)
|
||||
});
|
||||
|
||||
return reshapeBridgeSecret(
|
||||
projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
secretName: el.key,
|
||||
secretTags: secretToDeleteMatch.tags?.map((i) => i.slug)
|
||||
});
|
||||
{
|
||||
...el,
|
||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||
comment: el.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||
: ""
|
||||
},
|
||||
secretValueHidden
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
// deferred errors aren't return as DatabaseError
|
||||
const error = err as { code: string; table: string };
|
||||
if (
|
||||
error?.code === DatabaseErrorCode.ForeignKeyViolation &&
|
||||
error?.table === TableName.SecretRotationV2SecretMapping
|
||||
) {
|
||||
throw new BadRequestError({ message: "Cannot delete rotated secrets" });
|
||||
}
|
||||
|
||||
return reshapeBridgeSecret(
|
||||
projectId,
|
||||
environment,
|
||||
secretPath,
|
||||
{
|
||||
...el,
|
||||
value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||
comment: el.encryptedComment ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() : ""
|
||||
},
|
||||
secretValueHidden
|
||||
);
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const getSecretVersions = async ({
|
||||
|
||||
Reference in New Issue
Block a user