mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 23:27:14 +00:00
Merge pull request #4890 from Infisical/feat/ENG-3443
fix: group rotated secrets under rotation row on the UI dashboard
This commit is contained in:
@@ -214,7 +214,10 @@ export const secretRotationV2DALFactory = (
|
||||
tx?: Knex
|
||||
) => {
|
||||
try {
|
||||
const extendedQuery = baseSecretRotationV2Query({ filter, db, tx, options })
|
||||
const { limit, offset = 0, sort, ...queryOptions } = options || {};
|
||||
const baseOptions = { ...queryOptions };
|
||||
|
||||
const subquery = baseSecretRotationV2Query({ filter, db, tx, options: baseOptions })
|
||||
.join(
|
||||
TableName.SecretRotationV2SecretMapping,
|
||||
`${TableName.SecretRotationV2SecretMapping}.rotationId`,
|
||||
@@ -233,6 +236,7 @@ export const secretRotationV2DALFactory = (
|
||||
)
|
||||
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
|
||||
.select(
|
||||
selectAllTableCols(TableName.SecretRotationV2),
|
||||
db.ref("id").withSchema(TableName.SecretV2).as("secretId"),
|
||||
db.ref("key").withSchema(TableName.SecretV2).as("secretKey"),
|
||||
db.ref("version").withSchema(TableName.SecretV2).as("secretVersion"),
|
||||
@@ -252,18 +256,31 @@ export const secretRotationV2DALFactory = (
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
||||
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
|
||||
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
|
||||
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
|
||||
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue"),
|
||||
db.raw(`DENSE_RANK() OVER (ORDER BY ${TableName.SecretRotationV2}."createdAt" DESC) as rank`)
|
||||
);
|
||||
|
||||
if (search) {
|
||||
void extendedQuery.where((query) => {
|
||||
void query
|
||||
void subquery.where((qb) => {
|
||||
void qb
|
||||
.whereILike(`${TableName.SecretV2}.key`, `%${search}%`)
|
||||
.orWhereILike(`${TableName.SecretRotationV2}.name`, `%${search}%`);
|
||||
});
|
||||
}
|
||||
|
||||
const secretRotations = await extendedQuery;
|
||||
let secretRotations: Awaited<typeof subquery>;
|
||||
if (limit !== undefined) {
|
||||
const rankOffset = offset + 1;
|
||||
const queryWithLimit = (tx || db)
|
||||
.with("inner", subquery)
|
||||
.select("*")
|
||||
.from("inner")
|
||||
.where("inner.rank", ">=", rankOffset)
|
||||
.andWhere("inner.rank", "<", rankOffset + limit);
|
||||
secretRotations = (await queryWithLimit) as unknown as Awaited<typeof subquery>;
|
||||
} else {
|
||||
secretRotations = await subquery;
|
||||
}
|
||||
|
||||
if (!secretRotations.length) return [];
|
||||
|
||||
|
||||
@@ -624,7 +624,10 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
secretValueHidden: z.boolean(),
|
||||
secretPath: z.string().optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tags: SanitizedTagSchema.array().optional()
|
||||
tags: SanitizedTagSchema.array().optional(),
|
||||
reminder: RemindersSchema.extend({
|
||||
recipients: z.string().array()
|
||||
}).nullable()
|
||||
})
|
||||
.nullable()
|
||||
.array()
|
||||
@@ -743,6 +746,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
ReturnType<typeof server.services.secretRotationV2.getDashboardSecretRotations>
|
||||
>[number]["secrets"][number] & {
|
||||
isEmpty: boolean;
|
||||
reminder: Awaited<ReturnType<typeof server.services.reminder.getRemindersForDashboard>>[string] | null;
|
||||
}
|
||||
> | null)[];
|
||||
})[]
|
||||
@@ -847,27 +851,38 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
);
|
||||
|
||||
if (remainingLimit > 0 && totalSecretRotationCount > adjustedOffset) {
|
||||
secretRotations = (
|
||||
await server.services.secretRotationV2.getDashboardSecretRotations(
|
||||
{
|
||||
projectId,
|
||||
search,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
environments: [environment],
|
||||
secretPath,
|
||||
limit: remainingLimit,
|
||||
offset: adjustedOffset
|
||||
},
|
||||
req.permission
|
||||
)
|
||||
).map((rotation) => ({
|
||||
const rawSecretRotations = await server.services.secretRotationV2.getDashboardSecretRotations(
|
||||
{
|
||||
projectId,
|
||||
search,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
environments: [environment],
|
||||
secretPath,
|
||||
limit: remainingLimit,
|
||||
offset: adjustedOffset
|
||||
},
|
||||
req.permission
|
||||
);
|
||||
|
||||
const allRotationSecretIds = rawSecretRotations
|
||||
.flatMap((rotation) => rotation.secrets)
|
||||
.filter((secret) => Boolean(secret))
|
||||
.map((secret) => secret.id);
|
||||
|
||||
const rotationReminders =
|
||||
allRotationSecretIds.length > 0
|
||||
? await server.services.reminder.getRemindersForDashboard(allRotationSecretIds)
|
||||
: {};
|
||||
|
||||
secretRotations = rawSecretRotations.map((rotation) => ({
|
||||
...rotation,
|
||||
secrets: rotation.secrets.map((secret) =>
|
||||
secret
|
||||
? {
|
||||
...secret,
|
||||
isEmpty: !secret.secretValue
|
||||
isEmpty: !secret.secretValue,
|
||||
reminder: rotationReminders[secret.id] ?? null
|
||||
}
|
||||
: secret
|
||||
)
|
||||
@@ -948,7 +963,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
search,
|
||||
tagSlugs: tags,
|
||||
includeTagsInSearch: true,
|
||||
includeMetadataInSearch: true
|
||||
includeMetadataInSearch: true,
|
||||
excludeRotatedSecrets: includeSecretRotations
|
||||
});
|
||||
|
||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||
@@ -970,7 +986,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
offset: adjustedOffset,
|
||||
tagSlugs: tags,
|
||||
includeTagsInSearch: true,
|
||||
includeMetadataInSearch: true
|
||||
includeMetadataInSearch: true,
|
||||
excludeRotatedSecrets: includeSecretRotations
|
||||
})
|
||||
).secrets;
|
||||
|
||||
|
||||
@@ -416,6 +416,7 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
tagSlugs?: string[];
|
||||
includeTagsInSearch?: boolean;
|
||||
includeMetadataInSearch?: boolean;
|
||||
excludeRotatedSecrets?: boolean;
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
@@ -481,6 +482,10 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (filters?.excludeRotatedSecrets) {
|
||||
void query.whereNull(`${TableName.SecretRotationV2SecretMapping}.secretId`);
|
||||
}
|
||||
|
||||
const secrets = await query;
|
||||
|
||||
// @ts-expect-error not inferred by knex
|
||||
@@ -594,6 +599,11 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
void bd.whereIn(`${TableName.SecretTag}.slug`, slugs);
|
||||
}
|
||||
})
|
||||
.where((bd) => {
|
||||
if (filters?.excludeRotatedSecrets) {
|
||||
void bd.whereNull(`${TableName.SecretRotationV2SecretMapping}.secretId`);
|
||||
}
|
||||
})
|
||||
.orderBy(
|
||||
filters?.orderBy === SecretsOrderBy.Name ? "key" : "id",
|
||||
filters?.orderDirection ?? OrderByDirection.ASC
|
||||
|
||||
@@ -483,8 +483,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" });
|
||||
if (sharedSecretToModify.isRotatedSecret && inputSecret.newSecretName)
|
||||
throw new BadRequestError({ message: "Cannot update rotated secret name" });
|
||||
secretId = sharedSecretToModify.id;
|
||||
secret = sharedSecretToModify;
|
||||
}
|
||||
@@ -888,6 +888,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
| "tagSlugs"
|
||||
| "environment"
|
||||
| "search"
|
||||
| "excludeRotatedSecrets"
|
||||
>) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -1934,8 +1935,14 @@ export const secretV2BridgeServiceFactory = ({
|
||||
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}` });
|
||||
if (input) {
|
||||
if (input.newSecretName) {
|
||||
delete input.newSecretName;
|
||||
}
|
||||
if (input.secretValue !== undefined) {
|
||||
delete input.secretValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2061,8 +2068,11 @@ export const secretV2BridgeServiceFactory = ({
|
||||
commitChanges,
|
||||
inputSecrets: secretsToUpdate.map((el) => {
|
||||
const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0];
|
||||
const shouldUpdateValue = !originalSecret.isRotatedSecret && typeof el.secretValue !== "undefined";
|
||||
const shouldUpdateName = !originalSecret.isRotatedSecret && el.newSecretName;
|
||||
|
||||
const encryptedValue =
|
||||
typeof el.secretValue !== "undefined"
|
||||
shouldUpdateValue && el.secretValue !== undefined
|
||||
? {
|
||||
encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob,
|
||||
references: secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences
|
||||
@@ -2077,7 +2087,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||
),
|
||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||
key: el.newSecretName || el.secretKey,
|
||||
key: shouldUpdateName ? el.newSecretName : el.secretKey,
|
||||
tags: el.tagIds,
|
||||
secretMetadata: el.secretMetadata,
|
||||
...encryptedValue
|
||||
|
||||
@@ -50,6 +50,7 @@ export type TGetSecretsDTO = {
|
||||
limit?: number;
|
||||
search?: string;
|
||||
keys?: string[];
|
||||
excludeRotatedSecrets?: boolean;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetSecretsMissingReadValuePermissionDTO = Omit<
|
||||
@@ -362,6 +363,7 @@ export type TFindSecretsByFolderIdsFilter = {
|
||||
includeTagsInSearch?: boolean;
|
||||
includeMetadataInSearch?: boolean;
|
||||
keys?: string[];
|
||||
excludeRotatedSecrets?: boolean;
|
||||
};
|
||||
|
||||
export type TGetSecretsRawByFolderMappingsDTO = {
|
||||
|
||||
@@ -1154,6 +1154,7 @@ export const secretServiceFactory = ({
|
||||
| "search"
|
||||
| "includeTagsInSearch"
|
||||
| "includeMetadataInSearch"
|
||||
| "excludeRotatedSecrets"
|
||||
>) => {
|
||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||
|
||||
|
||||
@@ -214,6 +214,7 @@ export type TGetSecretsRawDTO = {
|
||||
keys?: string[];
|
||||
includeTagsInSearch?: boolean;
|
||||
includeMetadataInSearch?: boolean;
|
||||
excludeRotatedSecrets?: boolean;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TGetSecretAccessListDTO = {
|
||||
|
||||
Reference in New Issue
Block a user