Merge branch 'main' into secret-rotations-v2-feature

This commit is contained in:
Scott Wilson
2025-04-03 21:21:06 -07:00
96 changed files with 4509 additions and 926 deletions
@@ -1818,7 +1818,8 @@ export const certificateAuthorityServiceFactory = ({
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
issuingCaCertificate,
serialNumber,
ca
ca,
commonName: cn
};
};
@@ -423,7 +423,7 @@ export const projectMembershipServiceFactory = ({
const usernamesAndEmails = [...emails, ...usernames];
const projectMembers = await projectMembershipDAL.findMembershipsByUsername(projectId, [
...new Set(usernamesAndEmails.map((element) => element.toLowerCase()))
...new Set(usernamesAndEmails.map((element) => element))
]);
if (projectMembers.length !== usernamesAndEmails.length) {
@@ -402,7 +402,8 @@ export const secretFolderServiceFactory = ({
orderDirection,
limit,
offset,
recursive
recursive,
lastSecretModified
}: TGetFolderDTO) => {
// folder list is allowed to be read by anyone
// permission to check does user has access
@@ -425,7 +426,16 @@ export const secretFolderServiceFactory = ({
const recursiveFolders = await folderDAL.findByEnvsDeep({ parentIds: [parentFolder.id] });
// remove the parent folder
return recursiveFolders
.filter((folder) => folder.id !== parentFolder.id)
.filter((folder) => {
if (lastSecretModified) {
if (!folder.lastSecretModified) return false;
if (folder.lastSecretModified < new Date(lastSecretModified)) {
return false;
}
}
return folder.id !== parentFolder.id;
})
.map((folder) => ({
...folder,
relativePath: folder.path
@@ -445,6 +455,11 @@ export const secretFolderServiceFactory = ({
offset
}
);
if (lastSecretModified) {
return folders.filter((el) =>
el.lastSecretModified ? el.lastSecretModified >= new Date(lastSecretModified) : false
);
}
return folders;
};
@@ -619,10 +634,29 @@ export const secretFolderServiceFactory = ({
const relevantFolders = folders.filter((folder) => folder.envId === env.id);
const foldersMap = Object.fromEntries(relevantFolders.map((folder) => [folder.id, folder]));
const foldersWithPath = relevantFolders.map((folder) => ({
...folder,
path: buildFolderPath(folder, foldersMap)
}));
const foldersWithPath = relevantFolders
.map((folder) => {
try {
return {
...folder,
path: buildFolderPath(folder, foldersMap)
};
} catch (error) {
return null;
}
})
.filter(Boolean) as {
path: string;
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
envId: string;
version?: number | null | undefined;
parentId?: string | null | undefined;
isReserved?: boolean | undefined;
description?: string | undefined;
}[];
return [env.slug, { ...env, folders: foldersWithPath }];
})
@@ -46,6 +46,7 @@ export type TGetFolderDTO = {
limit?: number;
offset?: number;
recursive?: boolean;
lastSecretModified?: string;
} & TProjectPermission;
export type TGetFolderByIdDTO = {
@@ -356,7 +356,7 @@ export const fnSecretBulkDelete = async ({
interface FolderMap {
[parentId: string]: TSecretFolders[];
}
const buildHierarchy = (folders: TSecretFolders[]): FolderMap => {
export const buildHierarchy = (folders: TSecretFolders[]): FolderMap => {
const map: FolderMap = {};
map.null = []; // Initialize mapping for root directory
@@ -371,7 +371,7 @@ const buildHierarchy = (folders: TSecretFolders[]): FolderMap => {
return map;
};
const generatePaths = (
export const generatePaths = (
map: FolderMap,
parentId: string = "null",
basePath: string = "",
@@ -45,10 +45,12 @@ import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
import {
buildHierarchy,
expandSecretReferencesFactory,
fnSecretBulkDelete,
fnSecretBulkInsert,
fnSecretBulkUpdate,
generatePaths,
getAllSecretReferences,
recursivelyGetSecretPaths,
reshapeBridgeSecret
@@ -2663,7 +2665,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
actor,
actorAuthMethod,
actorOrgId
actorOrgId,
recursive
}: TGetAccessibleSecretsDTO) => {
const { permission } = await permissionService.getProjectPermission({
actor,
@@ -2678,10 +2681,38 @@ export const secretV2BridgeServiceFactory = ({
secretPath
});
const folders = [];
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) return { secrets: [] };
folders.push({ ...folder, parentId: null });
const secrets = await secretDAL.findByFolderIds([folder.id]);
const env = await projectEnvDAL.findOne({
projectId,
slug: environment
});
if (!env) {
throw new NotFoundError({
message: `Environment with slug '${environment}' in project with ID ${projectId} not found`
});
}
if (recursive) {
const subFolders = await folderDAL.find({
envId: env.id,
isReserved: false
});
folders.push(...subFolders);
}
if (folders.length === 0) return { secrets: [] };
const folderMap = buildHierarchy(folders);
const paths = Object.fromEntries(
generatePaths(folderMap).map(({ folderId, path }) => [folderId, path === "/" ? path : path.substring(1)])
);
const secrets = await secretDAL.findByFolderIds(folders.map((f) => f.id));
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
@@ -2693,7 +2724,7 @@ export const secretV2BridgeServiceFactory = ({
if (
!hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
environment,
secretPath,
secretPath: paths[el.folderId],
secretName: el.key,
secretTags: el.tags.map((i) => i.slug)
})
@@ -2704,7 +2735,7 @@ export const secretV2BridgeServiceFactory = ({
if (filterByAction === ProjectPermissionSecretActions.ReadValue) {
return hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
environment,
secretPath,
secretPath: paths[el.folderId],
secretName: el.key,
secretTags: el.tags.map((i) => i.slug)
});
@@ -2717,7 +2748,7 @@ export const secretV2BridgeServiceFactory = ({
filterByAction === ProjectPermissionSecretActions.DescribeSecret &&
!hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
environment,
secretPath,
secretPath: paths[secret.folderId],
secretName: secret.key,
secretTags: secret.tags.map((i) => i.slug)
});
@@ -2725,7 +2756,7 @@ export const secretV2BridgeServiceFactory = ({
return reshapeBridgeSecret(
projectId,
environment,
secretPath,
paths[secret.folderId],
{
...secret,
value: secret.encryptedValue
@@ -356,5 +356,6 @@ export type TGetAccessibleSecretsDTO = {
environment: string;
projectId: string;
secretPath: string;
recursive?: boolean;
filterByAction: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue;
} & TProjectPermission;
@@ -646,6 +646,10 @@ export const secretQueueFactory = ({
}
);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) return;
await folderDAL.updateById(folder.id, { lastSecretModified: new Date() });
await secretSyncQueue.queueSecretSyncsSyncSecretsByPath({ projectId, environmentSlug: environment, secretPath });
await syncIntegrations({ secretPath, projectId, environment, deDupeQueue, isManual: false });
@@ -1321,7 +1321,8 @@ export const secretServiceFactory = ({
actorOrgId,
actorAuthMethod,
environment,
filterByAction
filterByAction,
recursive
}: TGetAccessibleSecretsDTO) => {
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
@@ -1340,7 +1341,8 @@ export const secretServiceFactory = ({
actor,
actorId,
actorOrgId,
actorAuthMethod
actorAuthMethod,
recursive
});
return secrets;
@@ -184,6 +184,7 @@ export enum SecretsOrderBy {
export type TGetAccessibleSecretsDTO = {
secretPath: string;
environment: string;
recursive?: boolean;
filterByAction: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue;
} & TProjectPermission;
@@ -17,7 +17,9 @@ export enum PostHogEventTypes {
SecretRequestCreated = "Secret Request Created",
SecretRequestDeleted = "Secret Request Deleted",
SignSshKey = "Sign SSH Key",
IssueSshCreds = "Issue SSH Credentials"
IssueSshCreds = "Issue SSH Credentials",
SignCert = "Sign PKI Certificate",
IssueCert = "Issue PKI Certificate"
}
export type TSecretModifiedEvent = {
@@ -159,6 +161,26 @@ export type TIssueSshCredsEvent = {
};
};
export type TSignCertificateEvent = {
event: PostHogEventTypes.SignCert;
properties: {
caId?: string;
certificateTemplateId?: string;
commonName: string;
userAgent?: string;
};
};
export type TIssueCertificateEvent = {
event: PostHogEventTypes.IssueCert;
properties: {
caId?: string;
certificateTemplateId?: string;
commonName: string;
userAgent?: string;
};
};
export type TPostHogEvent = { distinctId: string } & (
| TSecretModifiedEvent
| TAdminInitEvent
@@ -173,4 +195,6 @@ export type TPostHogEvent = { distinctId: string } & (
| TSecretRequestDeletedEvent
| TSignSshKeyEvent
| TIssueSshCredsEvent
| TSignCertificateEvent
| TIssueCertificateEvent
);