From 7150b9314ddbc906597de5df0faf08e636a18d5f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 22 Jul 2025 01:35:02 +0400 Subject: [PATCH 01/24] feat(external-migrations): vault migrations --- .../routes/v3/external-migration-router.ts | 28 ++ backend/src/server/routes/v3/index.ts | 2 +- .../envkey.ts} | 361 +----------------- .../external-migration-fns/import.ts | 352 +++++++++++++++++ .../external-migration-fns/index.ts | 3 + .../external-migration-fns/vault.ts | 312 +++++++++++++++ .../external-migration-queue.ts | 3 +- .../external-migration-service.ts | 64 +++- .../external-migration-types.ts | 27 +- frontend/src/hooks/api/migration/index.ts | 1 + .../src/hooks/api/migration/mutations.tsx | 22 +- .../SelectImportFromPlatformModal.tsx | 46 ++- .../components/VaultPlatformModal copy.tsx | 226 +++++++++++ 13 files changed, 1075 insertions(+), 372 deletions(-) rename backend/src/services/external-migration/{external-migration-fns.ts => external-migration-fns/envkey.ts} (61%) create mode 100644 backend/src/services/external-migration/external-migration-fns/import.ts create mode 100644 backend/src/services/external-migration/external-migration-fns/index.ts create mode 100644 backend/src/services/external-migration/external-migration-fns/vault.ts create mode 100644 frontend/src/hooks/api/migration/index.ts create mode 100644 frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx diff --git a/backend/src/server/routes/v3/external-migration-router.ts b/backend/src/server/routes/v3/external-migration-router.ts index 865287157..d7aca92e2 100644 --- a/backend/src/server/routes/v3/external-migration-router.ts +++ b/backend/src/server/routes/v3/external-migration-router.ts @@ -1,9 +1,11 @@ import fastifyMultipart from "@fastify/multipart"; +import { z } from "zod"; import { BadRequestError } from "@app/lib/errors"; import { readLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +import { VaultMappingType } from "@app/services/external-migration/external-migration-types"; const MB25_IN_BYTES = 26214400; @@ -52,4 +54,30 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider }); } }); + + server.route({ + method: "POST", + url: "/vault", + config: { + rateLimit: readLimit + }, + schema: { + body: z.object({ + vaultAccessToken: z.string(), + vaultNamespace: z.string(), + vaultUrl: z.string(), + mappingType: z.nativeEnum(VaultMappingType) + }) + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + await server.services.migration.importVaultData({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + ...req.body + }); + } + }); }; diff --git a/backend/src/server/routes/v3/index.ts b/backend/src/server/routes/v3/index.ts index ed8401560..1c31741a1 100644 --- a/backend/src/server/routes/v3/index.ts +++ b/backend/src/server/routes/v3/index.ts @@ -11,5 +11,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => { await server.register(registerUserRouter, { prefix: "/users" }); await server.register(registerSecretRouter, { prefix: "/secrets" }); await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" }); - await server.register(registerExternalMigrationRouter, { prefix: "/migrate" }); + await server.register(registerExternalMigrationRouter, { prefix: "/external-migration" }); }; diff --git a/backend/src/services/external-migration/external-migration-fns.ts b/backend/src/services/external-migration/external-migration-fns/envkey.ts similarity index 61% rename from backend/src/services/external-migration/external-migration-fns.ts rename to backend/src/services/external-migration/external-migration-fns/envkey.ts index 8af22d858..ffdf80e9c 100644 --- a/backend/src/services/external-migration/external-migration-fns.ts +++ b/backend/src/services/external-migration/external-migration-fns/envkey.ts @@ -1,32 +1,26 @@ -import slugify from "@sindresorhus/slugify"; import sjcl from "sjcl"; import tweetnacl from "tweetnacl"; import tweetnaclUtil from "tweetnacl-util"; -import { SecretType, TSecretFolders } from "@app/db/schemas"; import { crypto } from "@app/lib/crypto/cryptography"; -import { BadRequestError, NotFoundError } from "@app/lib/errors"; -import { chunkArray } from "@app/lib/fn"; +import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; -import { alphaNumericNanoId } from "@app/lib/nanoid"; -import { CommitType, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service"; -import { TKmsServiceFactory } from "../kms/kms-service"; -import { KmsDataKey } from "../kms/kms-types"; -import { TProjectDALFactory } from "../project/project-dal"; -import { TProjectServiceFactory } from "../project/project-service"; -import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; -import { TProjectEnvServiceFactory } from "../project-env/project-env-service"; -import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal"; -import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; -import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal"; -import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; -import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; -import { fnSecretBulkInsert, getAllSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns"; -import type { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bridge-service"; -import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; -import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; -import { InfisicalImportData, TEnvKeyExportJSON, TImportInfisicalDataCreate } from "./external-migration-types"; +import { TFolderCommitServiceFactory } from "../../folder-commit/folder-commit-service"; +import { TKmsServiceFactory } from "../../kms/kms-service"; +import { TProjectDALFactory } from "../../project/project-dal"; +import { TProjectServiceFactory } from "../../project/project-service"; +import { TProjectEnvDALFactory } from "../../project-env/project-env-dal"; +import { TProjectEnvServiceFactory } from "../../project-env/project-env-service"; +import { TResourceMetadataDALFactory } from "../../resource-metadata/resource-metadata-dal"; +import { TSecretFolderDALFactory } from "../../secret-folder/secret-folder-dal"; +import { TSecretFolderVersionDALFactory } from "../../secret-folder/secret-folder-version-dal"; +import { TSecretTagDALFactory } from "../../secret-tag/secret-tag-dal"; +import { TSecretV2BridgeDALFactory } from "../../secret-v2-bridge/secret-v2-bridge-dal"; +import type { TSecretV2BridgeServiceFactory } from "../../secret-v2-bridge/secret-v2-bridge-service"; +import { TSecretVersionV2DALFactory } from "../../secret-v2-bridge/secret-version-dal"; +import { TSecretVersionV2TagDALFactory } from "../../secret-v2-bridge/secret-version-tag-dal"; +import { InfisicalImportData, TEnvKeyExportJSON, TImportInfisicalDataCreate } from "../external-migration-types"; export type TImportDataIntoInfisicalDTO = { projectDAL: Pick; @@ -499,326 +493,3 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise { - // Import data to infisical - if (!data || !data.projects) { - throw new BadRequestError({ message: "No projects found in data" }); - } - - const originalToNewProjectId = new Map(); - const originalToNewEnvironmentId = new Map< - string, - { envId: string; envSlug: string; rootFolderId: string; projectId: string } - >(); - const originalToNewFolderId = new Map< - string, - { - folderId: string; - projectId: string; - } - >(); - const projectsNotImported: string[] = []; - - await projectDAL.transaction(async (tx) => { - for await (const project of data.projects) { - const newProject = await projectService - .createProject({ - actor, - actorId, - actorOrgId, - actorAuthMethod, - workspaceName: project.name, - createDefaultEnvs: false, - tx - }) - .catch((e) => { - logger.error(e, `Failed to import to project [name:${project.name}]`); - throw new BadRequestError({ message: `Failed to import to project [name:${project.name}]` }); - }); - originalToNewProjectId.set(project.id, newProject.id); - } - - // Import environments - if (data.environments) { - for await (const environment of data.environments) { - const projectId = originalToNewProjectId.get(environment.projectId); - const slug = slugify(`${environment.name}-${alphaNumericNanoId(4)}`); - - if (!projectId) { - projectsNotImported.push(environment.projectId); - // eslint-disable-next-line no-continue - continue; - } - - const existingEnv = await projectEnvDAL.findOne({ projectId, slug }, tx); - - if (existingEnv) { - throw new BadRequestError({ - message: `Environment with slug '${slug}' already exist`, - name: "CreateEnvironment" - }); - } - - const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx); - const doc = await projectEnvDAL.create({ slug, name: environment.name, projectId, position: lastPos + 1 }, tx); - const folder = await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx); - - originalToNewEnvironmentId.set(environment.id, { - envSlug: doc.slug, - envId: doc.id, - rootFolderId: folder.id, - projectId - }); - } - } - - if (data.folders) { - for await (const folder of data.folders) { - const parentEnv = originalToNewEnvironmentId.get(folder.parentFolderId as string); - - if (!parentEnv) { - // eslint-disable-next-line no-continue - continue; - } - - const newFolder = await folderDAL.create( - { - name: folder.name, - envId: parentEnv.envId, - parentId: parentEnv.rootFolderId - }, - tx - ); - - const newFolderVersion = await folderVersionDAL.create( - { - name: newFolder.name, - envId: newFolder.envId, - version: newFolder.version, - folderId: newFolder.id - }, - tx - ); - - await folderCommitService.createCommit( - { - actor: { - type: actor, - metadata: { - id: actorId - } - }, - message: "Changed by external migration", - folderId: parentEnv.rootFolderId, - changes: [ - { - type: CommitType.ADD, - folderVersionId: newFolderVersion.id - } - ] - }, - tx - ); - - originalToNewFolderId.set(folder.id, { - folderId: newFolder.id, - projectId: parentEnv.projectId - }); - } - } - - // Useful for debugging: - // console.log("data.secrets", data.secrets); - // console.log("data.folders", data.folders); - // console.log("data.environment", data.environments); - - if (data.secrets && data.secrets.length > 0) { - const mappedToEnvironmentId = new Map< - string, - { - secretKey: string; - secretValue: string; - folderId?: string; - isFromBlock?: boolean; - }[] - >(); - - for (const secret of data.secrets) { - const targetId = secret.folderId || secret.environmentId; - - // Skip if we can't find either an environment or folder mapping for this secret - if (!originalToNewEnvironmentId.get(secret.environmentId) && !originalToNewFolderId.get(targetId)) { - logger.info({ secret }, "[importDataIntoInfisicalFn]: Could not find environment or folder for secret"); - - // eslint-disable-next-line no-continue - continue; - } - - if (!mappedToEnvironmentId.has(targetId)) { - mappedToEnvironmentId.set(targetId, []); - } - - const alreadyHasSecret = mappedToEnvironmentId - .get(targetId)! - .find((el) => el.secretKey === secret.name && el.folderId === secret.folderId); - - if (alreadyHasSecret && alreadyHasSecret.isFromBlock) { - // remove the existing secret if any - mappedToEnvironmentId - .get(targetId)! - .splice(mappedToEnvironmentId.get(targetId)!.indexOf(alreadyHasSecret), 1); - } - mappedToEnvironmentId.get(targetId)!.push({ - secretKey: secret.name, - secretValue: secret.value || "", - folderId: secret.folderId, - isFromBlock: secret.appBlockOrderIndex !== undefined - }); - } - - // for each of the mappedEnvironmentId - for await (const [targetId, secrets] of mappedToEnvironmentId) { - logger.info("[importDataIntoInfisicalFn]: Processing secrets for targetId", targetId); - - let selectedFolder: TSecretFolders | undefined; - let selectedProjectId: string | undefined; - - // Case 1: Secret belongs to a folder / branch / branch of a block - const foundFolder = originalToNewFolderId.get(targetId); - if (foundFolder) { - logger.info("[importDataIntoInfisicalFn]: Processing secrets for folder"); - selectedFolder = await folderDAL.findById(foundFolder.folderId, tx); - selectedProjectId = foundFolder.projectId; - } else { - logger.info("[importDataIntoInfisicalFn]: Processing secrets for normal environment"); - const environment = data.environments.find((env) => env.id === targetId); - if (!environment) { - logger.info( - { - targetId - }, - "[importDataIntoInfisicalFn]: Could not find environment for secret" - ); - // eslint-disable-next-line no-continue - continue; - } - - const projectId = originalToNewProjectId.get(environment.projectId)!; - - if (!projectId) { - throw new BadRequestError({ message: `Failed to import secret, project not found` }); - } - - const env = originalToNewEnvironmentId.get(targetId); - if (!env) { - logger.info( - { - targetId - }, - "[importDataIntoInfisicalFn]: Could not find environment for secret" - ); - - // eslint-disable-next-line no-continue - continue; - } - - const folder = await folderDAL.findBySecretPath(projectId, env.envSlug, "/", tx); - - if (!folder) { - throw new NotFoundError({ - message: `Folder not found for the given environment slug (${env.envSlug}) & secret path (/)`, - name: "Create secret" - }); - } - - selectedFolder = folder; - selectedProjectId = projectId; - } - - if (!selectedFolder) { - throw new NotFoundError({ - message: `Folder not found for the given environment slug & secret path`, - name: "CreateSecret" - }); - } - - if (!selectedProjectId) { - throw new NotFoundError({ - message: `Project not found for the given environment slug & secret path`, - name: "CreateSecret" - }); - } - - const { encryptor: secretManagerEncrypt } = await kmsService.createCipherPairWithDataKey( - { - type: KmsDataKey.SecretManager, - projectId: selectedProjectId - }, - tx - ); - - const secretBatches = chunkArray(secrets, 2500); - for await (const secretBatch of secretBatches) { - const secretsByKeys = await secretDAL.findBySecretKeys( - selectedFolder.id, - secretBatch.map((el) => ({ - key: el.secretKey, - type: SecretType.Shared - })), - tx - ); - if (secretsByKeys.length) { - throw new BadRequestError({ - message: `Secret already exist: ${secretsByKeys.map((el) => el.key).join(",")}` - }); - } - await fnSecretBulkInsert({ - inputSecrets: secretBatch.map((el) => { - const references = getAllSecretReferences(el.secretValue).nestedReferences; - - return { - version: 1, - encryptedValue: el.secretValue - ? secretManagerEncrypt({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob - : undefined, - key: el.secretKey, - references, - type: SecretType.Shared - }; - }), - folderId: selectedFolder.id, - orgId: actorOrgId, - resourceMetadataDAL, - secretDAL, - secretVersionDAL, - secretTagDAL, - secretVersionTagDAL, - folderCommitService, - actor: { - type: actor, - actorId - }, - tx - }); - } - } - } - }); - - return { projectsNotImported }; -}; diff --git a/backend/src/services/external-migration/external-migration-fns/import.ts b/backend/src/services/external-migration/external-migration-fns/import.ts new file mode 100644 index 000000000..5728bf1c0 --- /dev/null +++ b/backend/src/services/external-migration/external-migration-fns/import.ts @@ -0,0 +1,352 @@ +import slugify from "@sindresorhus/slugify"; + +import { SecretType, TSecretFolders } from "@app/db/schemas"; +import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { chunkArray } from "@app/lib/fn"; +import { logger } from "@app/lib/logger"; +import { alphaNumericNanoId } from "@app/lib/nanoid"; +import { CommitType } from "@app/services/folder-commit/folder-commit-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; +import { fnSecretBulkInsert, getAllSecretReferences } from "@app/services/secret-v2-bridge/secret-v2-bridge-fns"; + +import { TImportDataIntoInfisicalDTO } from "./envkey"; + +export const importDataIntoInfisicalFn = async ({ + projectService, + projectEnvDAL, + projectDAL, + secretDAL, + kmsService, + secretVersionDAL, + secretTagDAL, + secretVersionTagDAL, + folderDAL, + resourceMetadataDAL, + folderVersionDAL, + folderCommitService, + input: { data, actor, actorId, actorOrgId, actorAuthMethod } +}: TImportDataIntoInfisicalDTO) => { + // Import data to infisical + if (!data || !data.projects) { + throw new BadRequestError({ message: "No projects found in data" }); + } + + const originalToNewProjectId = new Map(); + const originalToNewEnvironmentId = new Map< + string, + { envId: string; envSlug: string; rootFolderId?: string; projectId: string } + >(); + const originalToNewFolderId = new Map< + string, + { + envId: string; + envSlug: string; + folderId: string; + projectId: string; + } + >(); + const projectsNotImported: string[] = []; + + await projectDAL.transaction(async (tx) => { + for await (const project of data.projects) { + const newProject = await projectService + .createProject({ + actor, + actorId, + actorOrgId, + actorAuthMethod, + workspaceName: project.name, + createDefaultEnvs: false, + tx + }) + .catch((e) => { + logger.error(e, `Failed to import to project [name:${project.name}]`); + throw new BadRequestError({ message: `Failed to import to project [name:${project.name}]` }); + }); + originalToNewProjectId.set(project.id, newProject.id); + } + + // Import environments + if (data.environments) { + for await (const environment of data.environments) { + const projectId = originalToNewProjectId.get(environment.projectId); + const slug = slugify(`${environment.name}-${alphaNumericNanoId(4)}`); + + if (!projectId) { + projectsNotImported.push(environment.projectId); + // eslint-disable-next-line no-continue + continue; + } + + const existingEnv = await projectEnvDAL.findOne({ projectId, slug }, tx); + + if (existingEnv) { + throw new BadRequestError({ + message: `Environment with slug '${slug}' already exist`, + name: "CreateEnvironment" + }); + } + + const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx); + const doc = await projectEnvDAL.create({ slug, name: environment.name, projectId, position: lastPos + 1 }, tx); + const folder = await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx); + + originalToNewEnvironmentId.set(environment.id, { + envSlug: doc.slug, + envId: doc.id, + rootFolderId: folder.id, + projectId + }); + } + } + + if (data.folders) { + for await (const folder of data.folders) { + const parentEnv = originalToNewEnvironmentId.get(folder.parentFolderId as string); + const parentFolder = originalToNewFolderId.get(folder.parentFolderId as string); + + let newFolder: TSecretFolders; + + if (parentEnv?.rootFolderId) { + newFolder = await folderDAL.create( + { + name: folder.name, + envId: parentEnv.envId, + parentId: parentEnv.rootFolderId + }, + tx + ); + } else if (parentFolder) { + newFolder = await folderDAL.create( + { + name: folder.name, + envId: parentFolder.envId, + parentId: parentFolder.folderId + }, + tx + ); + } else { + logger.info({ folder }, "No parent environment found for folder"); + // eslint-disable-next-line no-continue + continue; + } + + const newFolderVersion = await folderVersionDAL.create( + { + name: newFolder.name, + envId: newFolder.envId, + version: newFolder.version, + folderId: newFolder.id + }, + tx + ); + + await folderCommitService.createCommit( + { + actor: { + type: actor, + metadata: { + id: actorId + } + }, + message: "Changed by external migration", + folderId: parentEnv?.rootFolderId || parentFolder?.folderId || "", + changes: [ + { + type: CommitType.ADD, + folderVersionId: newFolderVersion.id + } + ] + }, + tx + ); + + originalToNewFolderId.set(folder.id, { + folderId: newFolder.id, + envId: parentEnv?.envId || parentFolder?.envId || "", + envSlug: parentEnv?.envSlug || parentFolder?.envSlug || "", + projectId: parentEnv?.projectId || parentFolder?.projectId || "" + }); + } + } + + // Useful for debugging: + // console.log("data.secrets", data.secrets); + // console.log("data.folders", data.folders); + // console.log("data.environment", data.environments); + + if (data.secrets && data.secrets.length > 0) { + const mappedToEnvironmentId = new Map< + string, + { + secretKey: string; + secretValue: string; + folderId?: string; + isFromBlock?: boolean; + }[] + >(); + + for (const secret of data.secrets) { + const targetId = secret.folderId || secret.environmentId; + + // Skip if we can't find either an environment or folder mapping for this secret + if (!originalToNewEnvironmentId.get(secret.environmentId) && !originalToNewFolderId.get(targetId)) { + logger.info({ secret }, "[importDataIntoInfisicalFn]: Could not find environment or folder for secret"); + + // eslint-disable-next-line no-continue + continue; + } + + if (!mappedToEnvironmentId.has(targetId)) { + mappedToEnvironmentId.set(targetId, []); + } + + const alreadyHasSecret = mappedToEnvironmentId + .get(targetId)! + .find((el) => el.secretKey === secret.name && el.folderId === secret.folderId); + + if (alreadyHasSecret && alreadyHasSecret.isFromBlock) { + // remove the existing secret if any + mappedToEnvironmentId + .get(targetId)! + .splice(mappedToEnvironmentId.get(targetId)!.indexOf(alreadyHasSecret), 1); + } + mappedToEnvironmentId.get(targetId)!.push({ + secretKey: secret.name, + secretValue: secret.value || "", + folderId: secret.folderId, + isFromBlock: secret.appBlockOrderIndex !== undefined + }); + } + + // for each of the mappedEnvironmentId + for await (const [targetId, secrets] of mappedToEnvironmentId) { + logger.info("[importDataIntoInfisicalFn]: Processing secrets for targetId", targetId); + + let selectedFolder: TSecretFolders | undefined; + let selectedProjectId: string | undefined; + + // Case 1: Secret belongs to a folder / branch / branch of a block + const foundFolder = originalToNewFolderId.get(targetId); + if (foundFolder) { + logger.info("[importDataIntoInfisicalFn]: Processing secrets for folder"); + selectedFolder = await folderDAL.findById(foundFolder.folderId, tx); + selectedProjectId = foundFolder.projectId; + } else { + logger.info("[importDataIntoInfisicalFn]: Processing secrets for normal environment"); + const environment = data.environments.find((env) => env.id === targetId); + if (!environment) { + logger.info( + { + targetId + }, + "[importDataIntoInfisicalFn]: Could not find environment for secret" + ); + // eslint-disable-next-line no-continue + continue; + } + + const projectId = originalToNewProjectId.get(environment.projectId)!; + + if (!projectId) { + throw new BadRequestError({ message: `Failed to import secret, project not found` }); + } + + const env = originalToNewEnvironmentId.get(targetId); + if (!env) { + logger.info( + { + targetId + }, + "[importDataIntoInfisicalFn]: Could not find environment for secret" + ); + + // eslint-disable-next-line no-continue + continue; + } + + const folder = await folderDAL.findBySecretPath(projectId, env.envSlug, "/", tx); + + if (!folder) { + throw new NotFoundError({ + message: `Folder not found for the given environment slug (${env.envSlug}) & secret path (/)`, + name: "Create secret" + }); + } + + selectedFolder = folder; + selectedProjectId = projectId; + } + + if (!selectedFolder) { + throw new NotFoundError({ + message: `Folder not found for the given environment slug & secret path`, + name: "CreateSecret" + }); + } + + if (!selectedProjectId) { + throw new NotFoundError({ + message: `Project not found for the given environment slug & secret path`, + name: "CreateSecret" + }); + } + + const { encryptor: secretManagerEncrypt } = await kmsService.createCipherPairWithDataKey( + { + type: KmsDataKey.SecretManager, + projectId: selectedProjectId + }, + tx + ); + + const secretBatches = chunkArray(secrets, 2500); + for await (const secretBatch of secretBatches) { + const secretsByKeys = await secretDAL.findBySecretKeys( + selectedFolder.id, + secretBatch.map((el) => ({ + key: el.secretKey, + type: SecretType.Shared + })), + tx + ); + if (secretsByKeys.length) { + throw new BadRequestError({ + message: `Secret already exist: ${secretsByKeys.map((el) => el.key).join(",")}` + }); + } + await fnSecretBulkInsert({ + inputSecrets: secretBatch.map((el) => { + const references = getAllSecretReferences(el.secretValue).nestedReferences; + + return { + version: 1, + encryptedValue: el.secretValue + ? secretManagerEncrypt({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob + : undefined, + key: el.secretKey, + references, + type: SecretType.Shared + }; + }), + folderId: selectedFolder.id, + orgId: actorOrgId, + resourceMetadataDAL, + secretDAL, + secretVersionDAL, + secretTagDAL, + secretVersionTagDAL, + folderCommitService, + actor: { + type: actor, + actorId + }, + tx + }); + } + } + } + }); + + return { projectsNotImported }; +}; diff --git a/backend/src/services/external-migration/external-migration-fns/index.ts b/backend/src/services/external-migration/external-migration-fns/index.ts new file mode 100644 index 000000000..4af82bf22 --- /dev/null +++ b/backend/src/services/external-migration/external-migration-fns/index.ts @@ -0,0 +1,3 @@ +export * from "./envkey"; +export * from "./import"; +export * from "./vault"; diff --git a/backend/src/services/external-migration/external-migration-fns/vault.ts b/backend/src/services/external-migration/external-migration-fns/vault.ts new file mode 100644 index 000000000..a61b2e703 --- /dev/null +++ b/backend/src/services/external-migration/external-migration-fns/vault.ts @@ -0,0 +1,312 @@ +import axios, { AxiosInstance } from "axios"; +import { v4 as uuidv4 } from "uuid"; + +import { InfisicalImportData, VaultMappingType } from "../external-migration-types"; + +type VaultData = { + namespace: string; + mount: string; + path: string; + secretData: Record; +}; + +const vaultFactory = () => { + const getMounts = async (request: AxiosInstance) => { + const response = await request.get< + Record< + string, + { + accessor: string; + options: { + version?: string; + } | null; + type: string; + } + > + >("/v1/sys/mounts"); + return response.data; + }; + + const getPaths = async ( + request: AxiosInstance, + { mountPath, secretPath = "" }: { mountPath: string; secretPath?: string } + ) => { + try { + // For KV v2: /v1/{mount}/metadata/{path}?list=true + const path = secretPath ? `${mountPath}/metadata/${secretPath}` : `${mountPath}/metadata`; + const response = await request.get<{ + data: { + keys: string[]; + }; + }>(`/v1/${path}?list=true`); + + return response.data.data.keys; + } catch (err) { + if (axios.isAxiosError(err) && err.response?.status === 404) { + return null; + } + throw err; + } + }; + + const getSecrets = async ( + request: AxiosInstance, + { mountPath, secretPath }: { mountPath: string; secretPath: string } + ) => { + // For KV v2: /v1/{mount}/data/{path} + const response = await request.get<{ + data: { + data: Record; // KV v2 has nested data structure + metadata: { + created_time: string; + deletion_time: string; + destroyed: boolean; + version: number; + }; + }; + }>(`/v1/${mountPath}/data/${secretPath}`); + + return response.data.data.data; + }; + + // helper function to check if a mount is KV v2 (will be useful if we add support for Vault KV v1) + // const isKvV2Mount = (mountInfo: { type: string; options?: { version?: string } | null }) => { + // return mountInfo.type === "kv" && mountInfo.options?.version === "2"; + // }; + + const recursivelyGetAllPaths = async ( + request: AxiosInstance, + mountPath: string, + currentPath: string = "" + ): Promise => { + const paths = await getPaths(request, { mountPath, secretPath: currentPath }); + + if (paths === null || paths.length === 0) { + return []; + } + + const allSecrets: string[] = []; + + for await (const path of paths) { + const cleanPath = path.endsWith("/") ? path.slice(0, -1) : path; + const fullItemPath = currentPath ? `${currentPath}/${cleanPath}` : cleanPath; + + if (path.endsWith("/")) { + // it's a folder so we recurse into it + const subSecrets = await recursivelyGetAllPaths(request, mountPath, fullItemPath); + allSecrets.push(...subSecrets); + } else { + // it's a secret so we add it to our results + allSecrets.push(`${mountPath}/${fullItemPath}`); + } + } + + return allSecrets; + }; + + async function collectVaultData({ + baseUrl, + namespace, + accessToken + }: { + baseUrl: string; + namespace: string; + accessToken: string; + }): Promise { + const request = axios.create({ + baseURL: baseUrl, + headers: { + "X-Vault-Namespace": namespace, + "X-Vault-Token": accessToken + } + }); + + const allData: VaultData[] = []; + + // Get all mounts in this namespace + const mounts = await getMounts(request); + + for (const mount of Object.keys(mounts)) { + if (!mount.endsWith("/")) { + delete mounts[mount]; + } + } + + for await (const [mountPath, mountInfo] of Object.entries(mounts)) { + // skip non-KV mounts + if (!mountInfo.type.startsWith("kv")) { + // eslint-disable-next-line no-continue + continue; + } + + // get all paths in this mount + const paths = await recursivelyGetAllPaths(request, `${mountPath.replace(/\/$/, "")}`); + + const cleanMountPath = mountPath.replace(/\/$/, ""); + + for await (const secretPath of paths) { + // get the actual secret data + const secretData = await getSecrets(request, { + mountPath: cleanMountPath, + secretPath: secretPath.replace(`${cleanMountPath}/`, "") + }); + + allData.push({ + namespace, + mount: mountPath.replace(/\/$/, ""), + path: secretPath.replace(`${cleanMountPath}/`, ""), + secretData + }); + } + } + + return allData; + } + + return { + collectVaultData, + getMounts, + getPaths, + getSecrets, + recursivelyGetAllPaths + }; +}; + +export const transformToInfisicalFormatNamespaceToProjects = ( + vaultData: VaultData[], + mappingType: VaultMappingType +): InfisicalImportData => { + const projects: Array<{ name: string; id: string }> = []; + const environments: Array<{ name: string; id: string; projectId: string; envParentId?: string }> = []; + const folders: Array<{ id: string; name: string; environmentId: string; parentFolderId?: string }> = []; + const secrets: Array<{ id: string; name: string; environmentId: string; value: string; folderId?: string }> = []; + + // track created entities to avoid duplicates + const projectMap = new Map(); // namespace -> projectId + const environmentMap = new Map(); // namespace:mount -> environmentId + const folderMap = new Map(); // namespace:mount:folderPath -> folderId + + let environmentId: string = ""; + for (const data of vaultData) { + const { namespace, mount, path, secretData } = data; + + if (mappingType === "namespace") { + // create project (namespace) + if (!projectMap.has(namespace)) { + const projectId = uuidv4(); + projectMap.set(namespace, projectId); + projects.push({ + name: namespace, + id: projectId + }); + } + const projectId = projectMap.get(namespace)!; + + // create environment (mount) + const envKey = `${namespace}:${mount}`; + if (!environmentMap.has(envKey)) { + environmentId = uuidv4(); + environmentMap.set(envKey, environmentId); + environments.push({ + name: mount, + id: environmentId, + projectId + }); + } + environmentId = environmentMap.get(envKey)!; + } else if (mappingType === "key-vault") { + if (!projectMap.has(mount)) { + const projectId = uuidv4(); + projectMap.set(mount, projectId); + projects.push({ + name: mount, + id: projectId + }); + } + const projectId = projectMap.get(mount)!; + + // create single "Production" environment per project, because we have no good way of determining environments from vault + if (!environmentMap.has(mount)) { + environmentId = uuidv4(); + environmentMap.set(mount, environmentId); + environments.push({ + name: "Production", + id: environmentId, + projectId + }); + } + environmentId = environmentMap.get(mount)!; + } + + // create folder structure + let currentFolderId: string | undefined; + let currentPath = ""; + + if (path.includes("/")) { + const pathParts = path.split("/").filter(Boolean); + + const folderParts = pathParts; + + // create nested folder structure for the entire path + for (const folderName of folderParts) { + currentPath = currentPath ? `${currentPath}/${folderName}` : folderName; + const folderKey = `${namespace}:${mount}:${currentPath}`; + + if (!folderMap.has(folderKey)) { + const folderId = uuidv4(); + folderMap.set(folderKey, folderId); + folders.push({ + id: folderId, + name: folderName, + environmentId, + parentFolderId: currentFolderId || environmentId + }); + currentFolderId = folderId; + } else { + currentFolderId = folderMap.get(folderKey)!; + } + } + } + + for (const [key, value] of Object.entries(secretData)) { + secrets.push({ + id: uuidv4(), + name: key, + environmentId, + value: String(value), + folderId: currentFolderId + }); + } + } + + return { + projects, + environments, + folders, + secrets + }; +}; + +export const importVaultDataFn = async ({ + vaultAccessToken, + vaultNamespace, + vaultUrl, + mappingType +}: { + vaultAccessToken: string; + vaultNamespace: string; + vaultUrl: string; + mappingType: VaultMappingType; +}) => { + const vaultApi = vaultFactory(); + + const vaultData = await vaultApi.collectVaultData({ + accessToken: vaultAccessToken, + baseUrl: vaultUrl, + namespace: vaultNamespace + }); + + const infisicalData = transformToInfisicalFormatNamespaceToProjects(vaultData, mappingType); + + return infisicalData; +}; diff --git a/backend/src/services/external-migration/external-migration-queue.ts b/backend/src/services/external-migration/external-migration-queue.ts index c4e6b43c5..8bde91aa5 100644 --- a/backend/src/services/external-migration/external-migration-queue.ts +++ b/backend/src/services/external-migration/external-migration-queue.ts @@ -19,7 +19,7 @@ import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-d import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { importDataIntoInfisicalFn } from "./external-migration-fns"; -import { ExternalPlatforms, TImportInfisicalDataCreate } from "./external-migration-types"; +import { ExternalPlatforms, ImportType, TImportInfisicalDataCreate } from "./external-migration-types"; export type TExternalMigrationQueueFactoryDep = { smtpService: TSmtpService; @@ -67,6 +67,7 @@ export const externalMigrationQueueFactory = ({ const startImport = async (dto: { actorEmail: string; data: { + importType: ImportType; iv: string; tag: string; ciphertext: string; diff --git a/backend/src/services/external-migration/external-migration-service.ts b/backend/src/services/external-migration/external-migration-service.ts index c310fd273..766b6cef4 100644 --- a/backend/src/services/external-migration/external-migration-service.ts +++ b/backend/src/services/external-migration/external-migration-service.ts @@ -4,9 +4,9 @@ import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors"; import { TUserDALFactory } from "../user/user-dal"; -import { decryptEnvKeyDataFn, parseEnvKeyDataFn } from "./external-migration-fns"; +import { decryptEnvKeyDataFn, importVaultDataFn, parseEnvKeyDataFn } from "./external-migration-fns"; import { TExternalMigrationQueueFactory } from "./external-migration-queue"; -import { TImportEnvKeyDataCreate } from "./external-migration-types"; +import { ImportType, TImportEnvKeyDataDTO, TImportVaultDataDTO } from "./external-migration-types"; type TExternalMigrationServiceFactoryDep = { permissionService: TPermissionServiceFactory; @@ -28,7 +28,7 @@ export const externalMigrationServiceFactory = ({ actorId, actorOrgId, actorAuthMethod - }: TImportEnvKeyDataCreate) => { + }: TImportEnvKeyDataDTO) => { if (crypto.isFipsModeEnabled()) { throw new BadRequestError({ message: "EnvKey migration is not supported when running in FIPS mode." }); } @@ -60,11 +60,65 @@ export const externalMigrationServiceFactory = ({ await externalMigrationQueue.startImport({ actorEmail: user.email!, - data: encrypted + data: { + importType: ImportType.EnvKey, + ...encrypted + } + }); + }; + + const importVaultData = async ({ + vaultAccessToken, + vaultNamespace, + mappingType, + vaultUrl, + actor, + actorId, + actorOrgId, + actorAuthMethod + }: TImportVaultDataDTO) => { + const { membership } = await permissionService.getOrgPermission( + actor, + actorId, + actorOrgId, + actorAuthMethod, + actorOrgId + ); + + if (membership.role !== OrgMembershipRole.Admin) { + throw new ForbiddenRequestError({ message: "Only admins can import data" }); + } + + const user = await userDAL.findById(actorId); + + const vaultData = await importVaultDataFn({ + vaultAccessToken, + vaultNamespace, + vaultUrl, + mappingType + }); + + const stringifiedJson = JSON.stringify({ + data: vaultData, + actor, + actorId, + actorOrgId, + actorAuthMethod + }); + + const encrypted = crypto.encryption().symmetric().encryptWithRootEncryptionKey(stringifiedJson); + + await externalMigrationQueue.startImport({ + actorEmail: user.email!, + data: { + importType: ImportType.Vault, + ...encrypted + } }); }; return { - importEnvKeyData + importEnvKeyData, + importVaultData }; }; diff --git a/backend/src/services/external-migration/external-migration-types.ts b/backend/src/services/external-migration/external-migration-types.ts index 32c70a688..f3431b5c9 100644 --- a/backend/src/services/external-migration/external-migration-types.ts +++ b/backend/src/services/external-migration/external-migration-types.ts @@ -1,5 +1,17 @@ +import { TOrgPermission } from "@app/lib/types"; + import { ActorAuthMethod, ActorType } from "../auth/auth-type"; +export enum ImportType { + EnvKey = "envkey", + Vault = "vault" +} + +export enum VaultMappingType { + Namespace = "namespace", + KeyVault = "key-vault" +} + export type InfisicalImportData = { projects: Array<{ name: string; id: string }>; environments: Array<{ name: string; id: string; projectId: string; envParentId?: string }>; @@ -14,14 +26,17 @@ export type InfisicalImportData = { }>; }; -export type TImportEnvKeyDataCreate = { +export type TImportEnvKeyDataDTO = { decryptionKey: string; encryptedJson: { nonce: string; data: string }; - actor: ActorType; - actorId: string; - actorOrgId: string; - actorAuthMethod: ActorAuthMethod; -}; +} & Omit; + +export type TImportVaultDataDTO = { + vaultAccessToken: string; + vaultNamespace: string; + mappingType: VaultMappingType; + vaultUrl: string; +} & Omit; export type TImportInfisicalDataCreate = { data: InfisicalImportData; diff --git a/frontend/src/hooks/api/migration/index.ts b/frontend/src/hooks/api/migration/index.ts new file mode 100644 index 000000000..f8dd99d03 --- /dev/null +++ b/frontend/src/hooks/api/migration/index.ts @@ -0,0 +1 @@ +export * from "./mutations"; diff --git a/frontend/src/hooks/api/migration/mutations.tsx b/frontend/src/hooks/api/migration/mutations.tsx index 87ba89eab..51c0f4910 100644 --- a/frontend/src/hooks/api/migration/mutations.tsx +++ b/frontend/src/hooks/api/migration/mutations.tsx @@ -15,7 +15,7 @@ export const useImportEnvKey = () => { formData.append("file", file); try { - const response = await apiRequest.post("/api/v3/migrate/env-key/", formData, { + const response = await apiRequest.post("/api/v3/external-migration/env-key/", formData, { headers: { "Content-Type": "multipart/form-data" }, @@ -39,3 +39,23 @@ export const useImportEnvKey = () => { } }); }; + +export const useImportVault = () => { + return useMutation({ + mutationFn: async ({ + vaultAccessToken, + vaultNamespace, + vaultUrl + }: { + vaultAccessToken: string; + vaultNamespace: string; + vaultUrl: string; + }) => { + await apiRequest.post("/api/v3/external-migration/vault/", { + vaultAccessToken, + vaultNamespace, + vaultUrl + }); + } + }); +}; diff --git a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx index 06631ffe1..f596a957d 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx @@ -1,11 +1,12 @@ import { useState } from "react"; -import { faKey } from "@fortawesome/free-solid-svg-icons"; +import { faKey, faVault } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { AnimatePresence, motion } from "framer-motion"; import { Modal, ModalContent } from "@app/components/v2"; import { EnvKeyPlatformModal } from "./EnvKeyPlatformModal"; +import { VaultPlatformModal } from "./VaultPlatformModal copy"; type Props = { isOpen?: boolean; @@ -22,6 +23,11 @@ const PLATFORM_LIST = [ icon: faKey, platform: "env-key", title: "Env Key" + }, + { + icon: faVault, + platform: "vault", + title: "Vault" } ] as const; @@ -82,18 +88,32 @@ export const SelectImportFromPlatformModal = ({ isOpen, onToggle }: Props) => { )} - {wizardStep === WizardSteps.PlatformInputs && - selectedPlatform?.platform === "env-key" && ( - - handleFormReset(false)} /> - - )} + {wizardStep === WizardSteps.PlatformInputs && ( + <> + {selectedPlatform?.platform === "env-key" && ( + + handleFormReset(false)} /> + + )} + {selectedPlatform?.platform === "vault" && ( + + handleFormReset(false)} /> + + )} + + )} diff --git a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx new file mode 100644 index 000000000..3d9755a28 --- /dev/null +++ b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx @@ -0,0 +1,226 @@ +import { useRef } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { twMerge } from "tailwind-merge"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, Input, Tooltip } from "@app/components/v2"; +import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2"; +import { useImportVault } from "@app/hooks/api/migration/mutations"; + +type Props = { + id?: string; + onClose: () => void; +}; + +enum VaultMappingType { + Namespace = "namespace", + KeyVault = "key-vault" +} + +const MAPPING_TYPE_MENU_ITEMS = [ + { + value: VaultMappingType.Namespace, + label: "Namespaces", + tooltip: ( +
+ When using namespaces for mapping, each namespace within Vault will be created in Infisical + as a project. Each key vault (KV) inside the namespace, will be created as an environment + inside the corresponding project. +
+
Namespace → Project
+
Key Vault → Project Environment
+
Secret Path → Secret Folder
+
Secret data → Secrets
+
+
+ ) + }, + { + value: VaultMappingType.KeyVault, + label: "Key Vaults", + tooltip: ( +
+ When using key vaults for mapping, each key vault within Vault will be created in Infisical + as a project. Each secret path inside the key vault, will be created as an environment + inside the corresponding project. When using Key Vaults as the mapping type, a default + environment called "Production" will be created for each project, which will + contain the secrets from the key vault. +
+
Key Vault → Project
+
Default Environment (Production)
+
Secret Path → Secret Folder
+
Secret data → Secrets
+
+
+ ) + } +]; + +export const VaultPlatformModal = ({ onClose }: Props) => { + const formSchema = z.object({ + vaultUrl: z.string().min(1), + vaultNamespace: z.string().min(1), + vaultAccessToken: z.string().min(1), + mappingType: z.nativeEnum(VaultMappingType) + }); + type TFormData = z.infer; + + const fileUploadRef = useRef(null); + + const { mutateAsync: importVault } = useImportVault(); + + const { + control, + handleSubmit, + reset, + formState: { isLoading, isDirty, isSubmitting, isValid } + } = useForm({ + resolver: zodResolver(formSchema) + }); + + const onSubmit = async (data: TFormData) => { + try { + await importVault({ + vaultAccessToken: data.vaultAccessToken, + vaultNamespace: data.vaultNamespace, + vaultUrl: data.vaultUrl + }); + createNotification({ + title: "Import started", + text: "Your data is being imported. You will receive an email when the import is complete or if the import fails. This may take up to 10 minutes.", + type: "info" + }); + + onClose(); + reset(); + + if (fileUploadRef.current) { + fileUploadRef.current.value = ""; + } + } catch { + reset(); + } + }; + + return ( +
+ +

+ The Vault migration currently supports importing static secrets from Vault + Dedicated/Self-Hosted. Namespaces are treated as projects, Secret Engines are treated as + environments, and secret paths are treated as folders. +

+ Currently only KV Secret Engine V2 is supported for Vault migrations. +
+

+
+
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + +
+ {MAPPING_TYPE_MENU_ITEMS.map((el) => ( +
field.onChange(el.value)} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === "Enter") { + field.onChange(el.value); + } + }} + > +
+
{el.label}
+ {el.tooltip && ( +
+ + + +
+ )} +
+
+ ))} +
+
+ )} + /> + +
+ + +
+ +
+ ); +}; From 185cc4efba94153c634355c6d02ea82b0f168a59 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 22 Jul 2025 01:50:28 +0400 Subject: [PATCH 02/24] Update VaultPlatformModal copy.tsx --- .../components/VaultPlatformModal copy.tsx | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx index 3d9755a28..eee9055df 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx @@ -17,11 +17,30 @@ type Props = { }; enum VaultMappingType { - Namespace = "namespace", - KeyVault = "key-vault" + KeyVault = "key-vault", + Namespace = "namespace" } const MAPPING_TYPE_MENU_ITEMS = [ + { + value: VaultMappingType.KeyVault, + label: "Key Vaults", + tooltip: ( +
+ When using key vaults for mapping, each key vault within Vault will be created in Infisical + as a project. Each secret path inside the key vault, will be created as an environment + inside the corresponding project. When using Key Vaults as the project mapping type, a + default environment called "Production" will be created for each project, which + will contain the secrets from the key vault. +
+
Key Vault → Project
+
Default Environment (Production)
+
Secret Path → Secret Folder
+
Secret data → Secrets
+
+
+ ) + }, { value: VaultMappingType.Namespace, label: "Namespaces", @@ -38,25 +57,6 @@ const MAPPING_TYPE_MENU_ITEMS = [ ) - }, - { - value: VaultMappingType.KeyVault, - label: "Key Vaults", - tooltip: ( -
- When using key vaults for mapping, each key vault within Vault will be created in Infisical - as a project. Each secret path inside the key vault, will be created as an environment - inside the corresponding project. When using Key Vaults as the mapping type, a default - environment called "Production" will be created for each project, which will - contain the secrets from the key vault. -
-
Key Vault → Project
-
Default Environment (Production)
-
Secret Path → Secret Folder
-
Secret data → Secrets
-
-
- ) } ]; @@ -65,7 +65,7 @@ export const VaultPlatformModal = ({ onClose }: Props) => { vaultUrl: z.string().min(1), vaultNamespace: z.string().min(1), vaultAccessToken: z.string().min(1), - mappingType: z.nativeEnum(VaultMappingType) + mappingType: z.nativeEnum(VaultMappingType).default(VaultMappingType.KeyVault) }); type TFormData = z.infer; @@ -111,8 +111,7 @@ export const VaultPlatformModal = ({ onClose }: Props) => {

The Vault migration currently supports importing static secrets from Vault - Dedicated/Self-Hosted. Namespaces are treated as projects, Secret Engines are treated as - environments, and secret paths are treated as folders. + Dedicated/Self-Hosted.

Currently only KV Secret Engine V2 is supported for Vault migrations.
@@ -164,11 +163,12 @@ export const VaultPlatformModal = ({ onClose }: Props) => { ( From bfd8b648717aebca6f2c6c596fb61a05e53f234a Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 22 Jul 2025 02:15:21 +0400 Subject: [PATCH 03/24] requested changes --- .../src/server/routes/v3/external-migration-router.ts | 6 +++--- frontend/src/hooks/api/migration/mutations.tsx | 7 +++++-- .../components/SelectImportFromPlatformModal.tsx | 2 +- ...aultPlatformModal copy.tsx => VaultPlatformModal.tsx} | 9 ++------- 4 files changed, 11 insertions(+), 13 deletions(-) rename frontend/src/pages/organization/SettingsPage/components/ImportTab/components/{VaultPlatformModal copy.tsx => VaultPlatformModal.tsx} (97%) diff --git a/backend/src/server/routes/v3/external-migration-router.ts b/backend/src/server/routes/v3/external-migration-router.ts index d7aca92e2..0681a8bc9 100644 --- a/backend/src/server/routes/v3/external-migration-router.ts +++ b/backend/src/server/routes/v3/external-migration-router.ts @@ -2,7 +2,7 @@ import fastifyMultipart from "@fastify/multipart"; import { z } from "zod"; import { BadRequestError } from "@app/lib/errors"; -import { readLimit } from "@app/server/config/rateLimiter"; +import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; import { VaultMappingType } from "@app/services/external-migration/external-migration-types"; @@ -17,7 +17,7 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider bodyLimit: MB25_IN_BYTES, url: "/env-key", config: { - rateLimit: readLimit + rateLimit: writeLimit }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { @@ -59,7 +59,7 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider method: "POST", url: "/vault", config: { - rateLimit: readLimit + rateLimit: writeLimit }, schema: { body: z.object({ diff --git a/frontend/src/hooks/api/migration/mutations.tsx b/frontend/src/hooks/api/migration/mutations.tsx index 51c0f4910..86ab2377c 100644 --- a/frontend/src/hooks/api/migration/mutations.tsx +++ b/frontend/src/hooks/api/migration/mutations.tsx @@ -45,16 +45,19 @@ export const useImportVault = () => { mutationFn: async ({ vaultAccessToken, vaultNamespace, - vaultUrl + vaultUrl, + mappingType }: { vaultAccessToken: string; vaultNamespace: string; vaultUrl: string; + mappingType: string; }) => { await apiRequest.post("/api/v3/external-migration/vault/", { vaultAccessToken, vaultNamespace, - vaultUrl + vaultUrl, + mappingType }); } }); diff --git a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx index f596a957d..6da5a44c9 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx @@ -6,7 +6,7 @@ import { AnimatePresence, motion } from "framer-motion"; import { Modal, ModalContent } from "@app/components/v2"; import { EnvKeyPlatformModal } from "./EnvKeyPlatformModal"; -import { VaultPlatformModal } from "./VaultPlatformModal copy"; +import { VaultPlatformModal } from "./VaultPlatformModal"; type Props = { isOpen?: boolean; diff --git a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal.tsx similarity index 97% rename from frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx rename to frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal.tsx index eee9055df..6037933ae 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal copy.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal.tsx @@ -69,8 +69,6 @@ export const VaultPlatformModal = ({ onClose }: Props) => { }); type TFormData = z.infer; - const fileUploadRef = useRef(null); - const { mutateAsync: importVault } = useImportVault(); const { @@ -87,7 +85,8 @@ export const VaultPlatformModal = ({ onClose }: Props) => { await importVault({ vaultAccessToken: data.vaultAccessToken, vaultNamespace: data.vaultNamespace, - vaultUrl: data.vaultUrl + vaultUrl: data.vaultUrl, + mappingType: data.mappingType }); createNotification({ title: "Import started", @@ -97,10 +96,6 @@ export const VaultPlatformModal = ({ onClose }: Props) => { onClose(); reset(); - - if (fileUploadRef.current) { - fileUploadRef.current.value = ""; - } } catch { reset(); } From 464e32b0e95d5b434106a52f443bdc0361836bea Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 22 Jul 2025 13:04:00 +0400 Subject: [PATCH 04/24] Update VaultPlatformModal.tsx --- .../components/ImportTab/components/VaultPlatformModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal.tsx index 6037933ae..eddeade31 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ImportTab/components/VaultPlatformModal.tsx @@ -1,4 +1,3 @@ -import { useRef } from "react"; import { Controller, useForm } from "react-hook-form"; import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; From e76e0f7bcc62b6374550f892fe14e923accd4daa Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 22 Jul 2025 17:14:45 -0700 Subject: [PATCH 05/24] improvement: improve dashboard filter behavior and design --- .../OverviewPage/OverviewPage.tsx | 103 +++++++++-------- .../SecretDashboardPage.tsx | 36 ++++-- .../components/ActionBar/ActionBar.tsx | 106 +++++++++++------- 3 files changed, 145 insertions(+), 100 deletions(-) diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index f58edbd33..70863c2a9 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -11,12 +11,12 @@ import { faArrowRightToBracket, faArrowUp, faFileImport, + faFilter, faFingerprint, faFolder, faFolderBlank, faFolderPlus, faKey, - faList, faPlus, faRotate } from "@fortawesome/free-solid-svg-icons"; @@ -96,7 +96,12 @@ import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; import { TSecretRotationV2 } from "@app/hooks/api/secretRotationsV2"; -import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/types"; +import { + SecretType, + SecretV3RawSanitized, + TSecretFolder, + WorkspaceEnv +} from "@app/hooks/api/types"; import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { useDynamicSecretOverview, @@ -144,11 +149,11 @@ type Filter = { }; const DEFAULT_FILTER_STATE = { - [RowType.Folder]: true, - [RowType.DynamicSecret]: true, - [RowType.Secret]: true, - [RowType.Import]: true, - [RowType.SecretRotation]: true + [RowType.Folder]: false, + [RowType.DynamicSecret]: false, + [RowType.Secret]: false, + [RowType.Import]: false, + [RowType.SecretRotation]: false }; const DEFAULT_COLLAPSED_HEADER_HEIGHT = 120; @@ -265,11 +270,8 @@ export const OverviewPage = () => { ) ); - const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs); - - useEffect(() => { - setVisibleEnvs(userAvailableEnvs); - }, [userAvailableEnvs]); + const [filteredEnvs, setFilteredEnvs] = useState([]); + const visibleEnvs = filteredEnvs.length ? filteredEnvs : userAvailableEnvs; const { secretImports, @@ -282,6 +284,7 @@ export const OverviewPage = () => { environments: (userAvailableEnvs || []).map(({ slug }) => slug) }); + const isFilteredByResources = Object.values(filter).some(Boolean); const { isPending: isOverviewLoading, data: overview } = useGetProjectSecretsOverview( { projectId: workspaceId, @@ -289,11 +292,11 @@ export const OverviewPage = () => { secretPath, orderDirection, orderBy, - includeFolders: filter.folder, - includeDynamicSecrets: filter.dynamic, - includeSecrets: filter.secret, - includeImports: filter.import, - includeSecretRotations: filter.rotation, + includeFolders: isFilteredByResources ? filter.folder : true, + includeDynamicSecrets: isFilteredByResources ? filter.dynamic : true, + includeSecrets: isFilteredByResources ? filter.secret : true, + includeImports: isFilteredByResources ? filter.import : true, + includeSecretRotations: isFilteredByResources ? filter.rotation : true, search: debouncedSearchFilter, limit, offset @@ -529,10 +532,10 @@ export const OverviewPage = () => { }; const handleEnvSelect = (envId: string) => { - if (visibleEnvs.map((env) => env.id).includes(envId)) { - setVisibleEnvs(visibleEnvs.filter((env) => env.id !== envId)); + if (filteredEnvs.map((env) => env.id).includes(envId)) { + setFilteredEnvs(filteredEnvs.filter((env) => env.id !== envId)); } else { - setVisibleEnvs(visibleEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId))); + setFilteredEnvs(filteredEnvs.concat(userAvailableEnvs.filter((env) => env.id === envId))); } }; @@ -792,11 +795,11 @@ export const OverviewPage = () => { envNames: string[], envs: { environment: string; importedBy: ProjectSecretsImportedBy[] }[] ): ProjectSecretsImportedBy[] => { - const filteredEnvs = envs.filter((env) => envNames.includes(env.environment)); + const environments = envs.filter((env) => envNames.includes(env.environment)); - if (filteredEnvs.length === 0) return []; + if (environments.length === 0) return []; - const allImportedBy = filteredEnvs.flatMap((env) => env.importedBy); + const allImportedBy = environments.flatMap((env) => env.importedBy); const groupedBySlug: Record = {}; allImportedBy.forEach((item) => { @@ -902,9 +905,7 @@ export const OverviewPage = () => { const isTableEmpty = totalCount === 0; - const isTableFiltered = - Boolean(Object.values(filter).filter((enabled) => !enabled).length) || - userAvailableEnvs.length !== visibleEnvs.length; + const isTableFiltered = isFilteredByResources || filteredEnvs.length > 0; if (!isProjectV3) return ( @@ -969,26 +970,42 @@ export const OverviewPage = () => {
+ {isTableFiltered && ( + + )} {userAvailableEnvs.length > 0 && ( - + } > - - - - + Filters + {/*
- Choose visible environments + Filter by Environment {userAvailableEnvs.map((availableEnv) => { const { id: envId, name } = availableEnv; - const isEnvSelected = visibleEnvs.map((env) => env.id).includes(envId); + const isEnvSelected = filteredEnvs.map((env) => env.id).includes(envId); return ( { @@ -1082,7 +1099,6 @@ export const OverviewPage = () => { handleEnvSelect(envId); }} key={envId} - disabled={visibleEnvs?.length === 1} icon={isEnvSelected && } iconPos="right" > @@ -1405,13 +1421,6 @@ export const OverviewPage = () => { className="bg-mineshaft-700" /> )} - {userAvailableEnvs.length > 0 && visibleEnvs.length === 0 && ( - - - - - - )} {userAvailableEnvs.length === 0 && ( @@ -1439,12 +1448,12 @@ export const OverviewPage = () => { )} - {isTableEmpty && !isOverviewLoading && visibleEnvs.length > 0 && ( + {isTableEmpty && !isOverviewLoading && isTableFiltered && ( { searchFilter: (routerQueryParams.search as string) || "", // these should always be on by default for the UI, they will be disabled for the query below based off permissions include: { - [RowType.Folder]: true, - [RowType.Import]: true, - [RowType.DynamicSecret]: true, - [RowType.Secret]: true, - [RowType.SecretRotation]: true + [RowType.Folder]: false, + [RowType.Import]: false, + [RowType.DynamicSecret]: false, + [RowType.Secret]: false, + [RowType.SecretRotation]: false } }; @@ -242,6 +242,7 @@ const Page = () => { } }, [currentWorkspace, environment]); + const isResourceTypeFiltered = Object.values(filter.include).some(Boolean); const { data, isPending: isDetailsLoading, @@ -255,12 +256,14 @@ const Page = () => { orderBy, search: debouncedSearchFilter, orderDirection, - includeImports: canReadSecretImports && filter.include.import, - includeFolders: filter.include.folder, + includeImports: canReadSecretImports && (isResourceTypeFiltered ? filter.include.import : true), + includeFolders: isResourceTypeFiltered ? filter.include.folder : true, viewSecretValue: canReadSecretValue, - includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic, - includeSecrets: canReadSecret && filter.include.secret, - includeSecretRotations: canReadSecretRotations && filter.include.rotation, + includeDynamicSecrets: + canReadDynamicSecret && (isResourceTypeFiltered ? filter.include.dynamic : true), + includeSecrets: canReadSecret && (isResourceTypeFiltered ? filter.include.secret : true), + includeSecretRotations: + canReadSecretRotations && (isResourceTypeFiltered ? filter.include.rotation : true), tags: filter.tags }); @@ -769,6 +772,19 @@ const Page = () => { isPITEnabled={isPITEnabled} hasPathPolicies={hasPathPolicies} onRequestAccess={(params) => handlePopUpOpen("requestAccess", params)} + onClearFilters={() => + setFilter((prev) => ({ + ...prev, + tags: {}, + include: { + secret: false, + import: false, + dynamic: false, + rotation: false, + folder: false + } + })) + } />
void; hasPathPolicies: boolean; + onClearFilters: () => void; }; export const ActionBar = ({ @@ -159,7 +160,8 @@ export const ActionBar = ({ isPITEnabled = false, usedBySecretSyncs, onRequestAccess, - hasPathPolicies + hasPathPolicies, + onClearFilters }: Props) => { const { handlePopUpOpen, handlePopUpToggle, handlePopUpClose, popUp } = usePopUp([ "addFolder", @@ -661,6 +663,9 @@ export const ActionBar = ({ } }; + const isTableFiltered = + Object.values(filter.tags).some(Boolean) || Object.values(filter.include).some(Boolean); + return ( <>
@@ -676,18 +681,22 @@ export const ActionBar = ({
- !include).length) && - "border-primary/50 text-primary" + "flex h-[2.5rem]", + isTableFiltered && "border-primary/40 bg-primary/10" )} + leftIcon={ + + } > - - + Filters + Filter By @@ -762,51 +771,62 @@ export const ActionBar = ({ Secrets
- - } - > - Tags - - - - Apply Tags to Filter Secrets - - {tags.map(({ id, slug, color }) => ( - { - evt.preventDefault(); - onToggleTagFilter(slug); - }} - key={id} - icon={filter?.tags[slug] && } - iconPos="right" - > -
-
- {slug} -
- - ))} - - + {Boolean(tags.length) && ( + + } + > + Tags + + + + Apply Tags to Filter Secrets + + {tags.map(({ id, slug, color }) => ( + { + evt.preventDefault(); + onToggleTagFilter(slug); + }} + key={id} + icon={filter?.tags[slug] && } + iconPos="right" + > +
+
+ {slug} +
+ + ))} + + + )}
+ {isTableFiltered && ( + + )} +
{isProtectedBranch && ( - + )}
-
From e96e7b835d93f1865dd0b292bd7a220f2ea301e7 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Wed, 23 Jul 2025 12:43:48 -0700 Subject: [PATCH 06/24] improvements: address feedback --- .../src/server/routes/v1/dashboard-router.ts | 8 +----- .../OverviewPage/OverviewPage.tsx | 27 +++++-------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index c466be087..68be44136 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -270,11 +270,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { } } }); - - remainingLimit -= imports.length; - adjustedOffset = 0; - } else { - adjustedOffset = Math.max(0, adjustedOffset - totalImportCount); } } @@ -317,7 +312,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { } } - if (!includeDynamicSecrets && !includeSecrets) + if (!includeDynamicSecrets && !includeSecrets && !includeSecretRotations) return { folders, totalFolderCount, @@ -547,7 +542,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => { (totalFolderCount ?? 0) + (totalDynamicSecretCount ?? 0) + (totalSecretCount ?? 0) + - (totalImportCount ?? 0) + (totalSecretRotationCount ?? 0) }; } diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 70863c2a9..88f71f8e0 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -10,7 +10,6 @@ import { faArrowRight, faArrowRightToBracket, faArrowUp, - faFileImport, faFilter, faFingerprint, faFolder, @@ -152,7 +151,6 @@ const DEFAULT_FILTER_STATE = { [RowType.Folder]: false, [RowType.DynamicSecret]: false, [RowType.Secret]: false, - [RowType.Import]: false, [RowType.SecretRotation]: false }; @@ -295,7 +293,7 @@ export const OverviewPage = () => { includeFolders: isFilteredByResources ? filter.folder : true, includeDynamicSecrets: isFilteredByResources ? filter.dynamic : true, includeSecrets: isFilteredByResources ? filter.secret : true, - includeImports: isFilteredByResources ? filter.import : true, + includeImports: true, includeSecretRotations: isFilteredByResources ? filter.rotation : true, search: debouncedSearchFilter, limit, @@ -1019,20 +1017,8 @@ export const OverviewPage = () => { Create an environment */} - Filter project resources - { - e.preventDefault(); - handleToggleRowType(RowType.Import); - }} - icon={filter[RowType.Import] && } - iconPos="right" - > -
- - Imports -
-
+ Filter by Resource + { e.preventDefault(); @@ -1448,17 +1434,18 @@ export const OverviewPage = () => { )} - {isTableEmpty && !isOverviewLoading && isTableFiltered && ( + {isTableEmpty && !isOverviewLoading && visibleEnvs.length > 0 && (
@@ -94,7 +101,7 @@ export const LogsSection = withPermission( secretPath: logFilter.secretPath || undefined, secretKey: logFilter.secretKey || undefined, eventMetadata: logFilter?.eventMetadata, - projectId: logFilter?.project?.id, + projectId: project?.id || logFilter?.project?.id, actorType: presets?.actorType, limit: 15, eventType: logFilter?.eventType, @@ -119,7 +126,7 @@ export const LogsSection = withPermission( return (
-
+
{showFilters && ( { {t("common.head-title", { title: t("settings.members.title") })} - + {!isLoading && !serverDetails?.redisConfigured && (

Announcements

diff --git a/frontend/src/pages/project/AuditLogsPage/AuditLogsPage.tsx b/frontend/src/pages/project/AuditLogsPage/AuditLogsPage.tsx new file mode 100644 index 000000000..0c83b0520 --- /dev/null +++ b/frontend/src/pages/project/AuditLogsPage/AuditLogsPage.tsx @@ -0,0 +1,27 @@ +import { Helmet } from "react-helmet"; + +import { PageHeader } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { LogsSection } from "@app/pages/organization/AuditLogsPage/components"; + +export const AuditLogsPage = () => { + const { currentWorkspace } = useWorkspace(); + + return ( +
+ + Project Audit Logs + + +
+
+ + +
+
+
+ ); +}; diff --git a/frontend/src/pages/project/AuditLogsPage/route-cert-manager.tsx b/frontend/src/pages/project/AuditLogsPage/route-cert-manager.tsx new file mode 100644 index 000000000..e87c655e0 --- /dev/null +++ b/frontend/src/pages/project/AuditLogsPage/route-cert-manager.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AuditLogsPage } from "./AuditLogsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs" +)({ + component: AuditLogsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Audit Logs" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/AuditLogsPage/route-kms.tsx b/frontend/src/pages/project/AuditLogsPage/route-kms.tsx new file mode 100644 index 000000000..7f6883d80 --- /dev/null +++ b/frontend/src/pages/project/AuditLogsPage/route-kms.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AuditLogsPage } from "./AuditLogsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs" +)({ + component: AuditLogsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Audit Logs" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/AuditLogsPage/route-secret-manager.tsx b/frontend/src/pages/project/AuditLogsPage/route-secret-manager.tsx new file mode 100644 index 000000000..3483f9270 --- /dev/null +++ b/frontend/src/pages/project/AuditLogsPage/route-secret-manager.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AuditLogsPage } from "./AuditLogsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs" +)({ + component: AuditLogsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Audit Logs" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/AuditLogsPage/route-secret-scanning.tsx b/frontend/src/pages/project/AuditLogsPage/route-secret-scanning.tsx new file mode 100644 index 000000000..28d00c5af --- /dev/null +++ b/frontend/src/pages/project/AuditLogsPage/route-secret-scanning.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AuditLogsPage } from "./AuditLogsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs" +)({ + component: AuditLogsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Audit Logs" + } + ] + }; + } +}); diff --git a/frontend/src/pages/project/AuditLogsPage/route-ssh.tsx b/frontend/src/pages/project/AuditLogsPage/route-ssh.tsx new file mode 100644 index 000000000..267e613b7 --- /dev/null +++ b/frontend/src/pages/project/AuditLogsPage/route-ssh.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from "@tanstack/react-router"; + +import { AuditLogsPage } from "./AuditLogsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs" +)({ + component: AuditLogsPage, + beforeLoad: ({ context }) => { + return { + breadcrumbs: [ + ...context.breadcrumbs, + { + label: "Audit Logs" + } + ] + }; + } +}); diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index c9c666a09..0bdf2202d 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -81,10 +81,15 @@ import { Route as secretManagerIntegrationsRouteBitbucketOauthRedirectImport } f import { Route as secretManagerIntegrationsRouteAzureKeyVaultOauthRedirectImport } from './pages/secret-manager/integrations/route-azure-key-vault-oauth-redirect' import { Route as secretManagerIntegrationsRouteAzureAppConfigurationsOauthRedirectImport } from './pages/secret-manager/integrations/route-azure-app-configurations-oauth-redirect' import { Route as organizationSettingsPageOauthCallbackPageRouteImport } from './pages/organization/SettingsPage/OauthCallbackPage/route' +import { Route as projectAuditLogsPageRouteSshImport } from './pages/project/AuditLogsPage/route-ssh' import { Route as projectAccessControlPageRouteSshImport } from './pages/project/AccessControlPage/route-ssh' +import { Route as projectAuditLogsPageRouteSecretScanningImport } from './pages/project/AuditLogsPage/route-secret-scanning' import { Route as projectAccessControlPageRouteSecretScanningImport } from './pages/project/AccessControlPage/route-secret-scanning' +import { Route as projectAuditLogsPageRouteSecretManagerImport } from './pages/project/AuditLogsPage/route-secret-manager' import { Route as projectAccessControlPageRouteSecretManagerImport } from './pages/project/AccessControlPage/route-secret-manager' +import { Route as projectAuditLogsPageRouteKmsImport } from './pages/project/AuditLogsPage/route-kms' import { Route as projectAccessControlPageRouteKmsImport } from './pages/project/AccessControlPage/route-kms' +import { Route as projectAuditLogsPageRouteCertManagerImport } from './pages/project/AuditLogsPage/route-cert-manager' import { Route as projectAccessControlPageRouteCertManagerImport } from './pages/project/AccessControlPage/route-cert-manager' import { Route as sshSettingsPageRouteImport } from './pages/ssh/SettingsPage/route' import { Route as sshSshHostsPageRouteImport } from './pages/ssh/SshHostsPage/route' @@ -903,6 +908,13 @@ const organizationSettingsPageOauthCallbackPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute, } as any) +const projectAuditLogsPageRouteSshRoute = + projectAuditLogsPageRouteSshImport.update({ + id: '/audit-logs', + path: '/audit-logs', + getParentRoute: () => sshLayoutRoute, + } as any) + const projectAccessControlPageRouteSshRoute = projectAccessControlPageRouteSshImport.update({ id: '/access-management', @@ -919,6 +931,13 @@ const AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecret } as any, ) +const projectAuditLogsPageRouteSecretScanningRoute = + projectAuditLogsPageRouteSecretScanningImport.update({ + id: '/audit-logs', + path: '/audit-logs', + getParentRoute: () => secretScanningLayoutRoute, + } as any) + const projectAccessControlPageRouteSecretScanningRoute = projectAccessControlPageRouteSecretScanningImport.update({ id: '/access-management', @@ -935,6 +954,13 @@ const AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecr } as any, ) +const projectAuditLogsPageRouteSecretManagerRoute = + projectAuditLogsPageRouteSecretManagerImport.update({ + id: '/audit-logs', + path: '/audit-logs', + getParentRoute: () => secretManagerLayoutRoute, + } as any) + const projectAccessControlPageRouteSecretManagerRoute = projectAccessControlPageRouteSecretManagerImport.update({ id: '/access-management', @@ -942,6 +968,13 @@ const projectAccessControlPageRouteSecretManagerRoute = getParentRoute: () => secretManagerLayoutRoute, } as any) +const projectAuditLogsPageRouteKmsRoute = + projectAuditLogsPageRouteKmsImport.update({ + id: '/audit-logs', + path: '/audit-logs', + getParentRoute: () => kmsLayoutRoute, + } as any) + const projectAccessControlPageRouteKmsRoute = projectAccessControlPageRouteKmsImport.update({ id: '/access-management', @@ -967,6 +1000,13 @@ const AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertMa } as any, ) +const projectAuditLogsPageRouteCertManagerRoute = + projectAuditLogsPageRouteCertManagerImport.update({ + id: '/audit-logs', + path: '/audit-logs', + getParentRoute: () => certManagerLayoutRoute, + } as any) + const projectAccessControlPageRouteCertManagerRoute = projectAccessControlPageRouteCertManagerImport.update({ id: '/access-management', @@ -2722,6 +2762,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteCertManagerImport parentRoute: typeof certManagerLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs': { + id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs' + path: '/audit-logs' + fullPath: '/projects/cert-management/$projectId/audit-logs' + preLoaderRoute: typeof projectAuditLogsPageRouteCertManagerImport + parentRoute: typeof certManagerLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates': { id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates' path: '/certificate-templates' @@ -2743,6 +2790,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteKmsImport parentRoute: typeof kmsLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs': { + id: '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs' + path: '/audit-logs' + fullPath: '/projects/kms/$projectId/audit-logs' + preLoaderRoute: typeof projectAuditLogsPageRouteKmsImport + parentRoute: typeof kmsLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management': { id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management' path: '/access-management' @@ -2750,6 +2804,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteSecretManagerImport parentRoute: typeof secretManagerLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs': { + id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs' + path: '/audit-logs' + fullPath: '/projects/secret-management/$projectId/audit-logs' + preLoaderRoute: typeof projectAuditLogsPageRouteSecretManagerImport + parentRoute: typeof secretManagerLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations': { id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations' path: '/integrations' @@ -2764,6 +2825,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteSecretScanningImport parentRoute: typeof secretScanningLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs': { + id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs' + path: '/audit-logs' + fullPath: '/projects/secret-scanning/$projectId/audit-logs' + preLoaderRoute: typeof projectAuditLogsPageRouteSecretScanningImport + parentRoute: typeof secretScanningLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources': { id: '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources' path: '/data-sources' @@ -2778,6 +2846,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof projectAccessControlPageRouteSshImport parentRoute: typeof sshLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs': { + id: '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs' + path: '/audit-logs' + fullPath: '/projects/ssh/$projectId/audit-logs' + preLoaderRoute: typeof projectAuditLogsPageRouteSshImport + parentRoute: typeof sshLayoutImport + } '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/': { id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/' path: '/' @@ -3781,6 +3856,7 @@ interface certManagerLayoutRouteChildren { certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute + projectAuditLogsPageRouteCertManagerRoute: typeof projectAuditLogsPageRouteCertManagerRoute AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren certManagerCertAuthDetailsByIDPageRouteRoute: typeof certManagerCertAuthDetailsByIDPageRouteRoute @@ -3799,6 +3875,8 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = { certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute, projectAccessControlPageRouteCertManagerRoute: projectAccessControlPageRouteCertManagerRoute, + projectAuditLogsPageRouteCertManagerRoute: + projectAuditLogsPageRouteCertManagerRoute, AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRoute: AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRoute: @@ -3839,6 +3917,7 @@ interface kmsLayoutRouteChildren { kmsOverviewPageRouteRoute: typeof kmsOverviewPageRouteRoute kmsSettingsPageRouteRoute: typeof kmsSettingsPageRouteRoute projectAccessControlPageRouteKmsRoute: typeof projectAccessControlPageRouteKmsRoute + projectAuditLogsPageRouteKmsRoute: typeof projectAuditLogsPageRouteKmsRoute projectGroupDetailsByIDPageRouteKmsRoute: typeof projectGroupDetailsByIDPageRouteKmsRoute projectIdentityDetailsByIDPageRouteKmsRoute: typeof projectIdentityDetailsByIDPageRouteKmsRoute projectMemberDetailsByIDPageRouteKmsRoute: typeof projectMemberDetailsByIDPageRouteKmsRoute @@ -3850,6 +3929,7 @@ const kmsLayoutRouteChildren: kmsLayoutRouteChildren = { kmsOverviewPageRouteRoute: kmsOverviewPageRouteRoute, kmsSettingsPageRouteRoute: kmsSettingsPageRouteRoute, projectAccessControlPageRouteKmsRoute: projectAccessControlPageRouteKmsRoute, + projectAuditLogsPageRouteKmsRoute: projectAuditLogsPageRouteKmsRoute, projectGroupDetailsByIDPageRouteKmsRoute: projectGroupDetailsByIDPageRouteKmsRoute, projectIdentityDetailsByIDPageRouteKmsRoute: @@ -4166,6 +4246,7 @@ interface secretManagerLayoutRouteChildren { secretManagerSecretRotationPageRouteRoute: typeof secretManagerSecretRotationPageRouteRoute secretManagerSettingsPageRouteRoute: typeof secretManagerSettingsPageRouteRoute projectAccessControlPageRouteSecretManagerRoute: typeof projectAccessControlPageRouteSecretManagerRoute + projectAuditLogsPageRouteSecretManagerRoute: typeof projectAuditLogsPageRouteSecretManagerRoute AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren secretManagerSecretDashboardPageRouteRoute: typeof secretManagerSecretDashboardPageRouteRoute projectGroupDetailsByIDPageRouteSecretManagerRoute: typeof projectGroupDetailsByIDPageRouteSecretManagerRoute @@ -4186,6 +4267,8 @@ const secretManagerLayoutRouteChildren: secretManagerLayoutRouteChildren = { secretManagerSettingsPageRouteRoute: secretManagerSettingsPageRouteRoute, projectAccessControlPageRouteSecretManagerRoute: projectAccessControlPageRouteSecretManagerRoute, + projectAuditLogsPageRouteSecretManagerRoute: + projectAuditLogsPageRouteSecretManagerRoute, AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRoute: AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren, secretManagerSecretDashboardPageRouteRoute: @@ -4241,6 +4324,7 @@ interface secretScanningLayoutRouteChildren { secretScanningSecretScanningFindingsPageRouteRoute: typeof secretScanningSecretScanningFindingsPageRouteRoute secretScanningSettingsPageRouteRoute: typeof secretScanningSettingsPageRouteRoute projectAccessControlPageRouteSecretScanningRoute: typeof projectAccessControlPageRouteSecretScanningRoute + projectAuditLogsPageRouteSecretScanningRoute: typeof projectAuditLogsPageRouteSecretScanningRoute AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren projectGroupDetailsByIDPageRouteSecretScanningRoute: typeof projectGroupDetailsByIDPageRouteSecretScanningRoute projectIdentityDetailsByIDPageRouteSecretScanningRoute: typeof projectIdentityDetailsByIDPageRouteSecretScanningRoute @@ -4254,6 +4338,8 @@ const secretScanningLayoutRouteChildren: secretScanningLayoutRouteChildren = { secretScanningSettingsPageRouteRoute: secretScanningSettingsPageRouteRoute, projectAccessControlPageRouteSecretScanningRoute: projectAccessControlPageRouteSecretScanningRoute, + projectAuditLogsPageRouteSecretScanningRoute: + projectAuditLogsPageRouteSecretScanningRoute, AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRoute: AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren, projectGroupDetailsByIDPageRouteSecretScanningRoute: @@ -4289,6 +4375,7 @@ interface sshLayoutRouteChildren { sshSshHostsPageRouteRoute: typeof sshSshHostsPageRouteRoute sshSettingsPageRouteRoute: typeof sshSettingsPageRouteRoute projectAccessControlPageRouteSshRoute: typeof projectAccessControlPageRouteSshRoute + projectAuditLogsPageRouteSshRoute: typeof projectAuditLogsPageRouteSshRoute sshSshCaByIDPageRouteRoute: typeof sshSshCaByIDPageRouteRoute sshSshHostGroupDetailsByIDPageRouteRoute: typeof sshSshHostGroupDetailsByIDPageRouteRoute projectGroupDetailsByIDPageRouteSshRoute: typeof projectGroupDetailsByIDPageRouteSshRoute @@ -4303,6 +4390,7 @@ const sshLayoutRouteChildren: sshLayoutRouteChildren = { sshSshHostsPageRouteRoute: sshSshHostsPageRouteRoute, sshSettingsPageRouteRoute: sshSettingsPageRouteRoute, projectAccessControlPageRouteSshRoute: projectAccessControlPageRouteSshRoute, + projectAuditLogsPageRouteSshRoute: projectAuditLogsPageRouteSshRoute, sshSshCaByIDPageRouteRoute: sshSshCaByIDPageRouteRoute, sshSshHostGroupDetailsByIDPageRouteRoute: sshSshHostGroupDetailsByIDPageRouteRoute, @@ -4642,14 +4730,19 @@ export interface FileRoutesByFullPath { '/projects/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute '/projects/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute '/projects/cert-management/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/projects/cert-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute '/projects/cert-management/$projectId/certificate-templates': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren '/projects/cert-management/$projectId/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren '/projects/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute + '/projects/kms/$projectId/audit-logs': typeof projectAuditLogsPageRouteKmsRoute '/projects/secret-management/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute + '/projects/secret-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute '/projects/secret-management/$projectId/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren '/projects/secret-scanning/$projectId/access-management': typeof projectAccessControlPageRouteSecretScanningRoute + '/projects/secret-scanning/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute '/projects/secret-scanning/$projectId/data-sources': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren '/projects/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute + '/projects/ssh/$projectId/audit-logs': typeof projectAuditLogsPageRouteSshRoute '/projects/cert-management/$projectId/certificate-templates/': typeof certManagerPkiTemplateListPageRouteRoute '/projects/cert-management/$projectId/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute '/projects/secret-management/$projectId/integrations/': typeof secretManagerIntegrationsListPageRouteRoute @@ -4852,10 +4945,15 @@ export interface FileRoutesByTo { '/projects/ssh/$projectId/overview': typeof sshSshHostsPageRouteRoute '/projects/ssh/$projectId/settings': typeof sshSettingsPageRouteRoute '/projects/cert-management/$projectId/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/projects/cert-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute '/projects/kms/$projectId/access-management': typeof projectAccessControlPageRouteKmsRoute + '/projects/kms/$projectId/audit-logs': typeof projectAuditLogsPageRouteKmsRoute '/projects/secret-management/$projectId/access-management': typeof projectAccessControlPageRouteSecretManagerRoute + '/projects/secret-management/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute '/projects/secret-scanning/$projectId/access-management': typeof projectAccessControlPageRouteSecretScanningRoute + '/projects/secret-scanning/$projectId/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute '/projects/ssh/$projectId/access-management': typeof projectAccessControlPageRouteSshRoute + '/projects/ssh/$projectId/audit-logs': typeof projectAuditLogsPageRouteSshRoute '/projects/cert-management/$projectId/certificate-templates': typeof certManagerPkiTemplateListPageRouteRoute '/projects/cert-management/$projectId/subscribers': typeof certManagerPkiSubscribersPageRouteRoute '/projects/secret-management/$projectId/integrations': typeof secretManagerIntegrationsListPageRouteRoute @@ -5075,14 +5173,19 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/overview': typeof sshSshHostsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/settings': typeof sshSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management': typeof projectAccessControlPageRouteCertManagerRoute + '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs': typeof projectAuditLogsPageRouteCertManagerRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutCertificateTemplatesRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertManagerLayoutSubscribersRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management': typeof projectAccessControlPageRouteKmsRoute + '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs': typeof projectAuditLogsPageRouteKmsRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management': typeof projectAccessControlPageRouteSecretManagerRoute + '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs': typeof projectAuditLogsPageRouteSecretManagerRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretManagementProjectIdSecretManagerLayoutIntegrationsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management': typeof projectAccessControlPageRouteSecretScanningRoute + '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs': typeof projectAuditLogsPageRouteSecretScanningRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources': typeof AuthenticateInjectOrgDetailsOrgLayoutProjectsSecretScanningProjectIdSecretScanningLayoutDataSourcesRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management': typeof projectAccessControlPageRouteSshRoute + '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs': typeof projectAuditLogsPageRouteSshRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/': typeof certManagerPkiTemplateListPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/': typeof certManagerPkiSubscribersPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/': typeof secretManagerIntegrationsListPageRouteRoute @@ -5295,14 +5398,19 @@ export interface FileRouteTypes { | '/projects/ssh/$projectId/overview' | '/projects/ssh/$projectId/settings' | '/projects/cert-management/$projectId/access-management' + | '/projects/cert-management/$projectId/audit-logs' | '/projects/cert-management/$projectId/certificate-templates' | '/projects/cert-management/$projectId/subscribers' | '/projects/kms/$projectId/access-management' + | '/projects/kms/$projectId/audit-logs' | '/projects/secret-management/$projectId/access-management' + | '/projects/secret-management/$projectId/audit-logs' | '/projects/secret-management/$projectId/integrations' | '/projects/secret-scanning/$projectId/access-management' + | '/projects/secret-scanning/$projectId/audit-logs' | '/projects/secret-scanning/$projectId/data-sources' | '/projects/ssh/$projectId/access-management' + | '/projects/ssh/$projectId/audit-logs' | '/projects/cert-management/$projectId/certificate-templates/' | '/projects/cert-management/$projectId/subscribers/' | '/projects/secret-management/$projectId/integrations/' @@ -5504,10 +5612,15 @@ export interface FileRouteTypes { | '/projects/ssh/$projectId/overview' | '/projects/ssh/$projectId/settings' | '/projects/cert-management/$projectId/access-management' + | '/projects/cert-management/$projectId/audit-logs' | '/projects/kms/$projectId/access-management' + | '/projects/kms/$projectId/audit-logs' | '/projects/secret-management/$projectId/access-management' + | '/projects/secret-management/$projectId/audit-logs' | '/projects/secret-scanning/$projectId/access-management' + | '/projects/secret-scanning/$projectId/audit-logs' | '/projects/ssh/$projectId/access-management' + | '/projects/ssh/$projectId/audit-logs' | '/projects/cert-management/$projectId/certificate-templates' | '/projects/cert-management/$projectId/subscribers' | '/projects/secret-management/$projectId/integrations' @@ -5725,14 +5838,19 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/overview' | '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/settings' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers' | '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources' | '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management' + | '/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers/' | '/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations/' @@ -6331,6 +6449,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/ca/$caName", @@ -6349,6 +6468,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/overview", "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs", "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/identities/$identityId", "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/members/$membershipId", @@ -6365,6 +6485,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/secrets/$envSlug", "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/groups/$groupId", @@ -6381,6 +6502,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/findings", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId", @@ -6397,6 +6519,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/overview", "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/access-management", + "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs", "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ca/$caId", "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/ssh-host-groups/$sshHostGroupId", "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/groups/$groupId", @@ -6485,6 +6608,10 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-cert-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" }, + "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/audit-logs": { + "filePath": "project/AuditLogsPage/route-cert-manager.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" + }, "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout", @@ -6504,10 +6631,18 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-kms.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout" }, + "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/audit-logs": { + "filePath": "project/AuditLogsPage/route-kms.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout" + }, "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/access-management": { "filePath": "project/AccessControlPage/route-secret-manager.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout" }, + "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/audit-logs": { + "filePath": "project/AuditLogsPage/route-secret-manager.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout" + }, "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/integrations": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout", @@ -6596,6 +6731,10 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-secret-scanning.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout" }, + "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/audit-logs": { + "filePath": "project/AuditLogsPage/route-secret-scanning.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout" + }, "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/data-sources": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout", @@ -6608,6 +6747,10 @@ export const routeTree = rootRoute "filePath": "project/AccessControlPage/route-ssh.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout" }, + "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout/audit-logs": { + "filePath": "project/AuditLogsPage/route-ssh.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/projects/ssh/$projectId/_ssh-layout" + }, "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates/": { "filePath": "cert-manager/PkiTemplateListPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-templates" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 353aa7a5d..2e63be383 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -63,6 +63,7 @@ const secretManagerRoutes = route("/projects/secret-management/$projectId", [ ) ]) ]), + route("/audit-logs", "project/AuditLogsPage/route-secret-manager.tsx"), route("/access-management", "project/AccessControlPage/route-secret-manager.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-manager.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-manager.tsx"), @@ -312,6 +313,7 @@ const certManagerRoutes = route("/projects/cert-management/$projectId", [ route("/ca/$caName", "cert-manager/CertAuthDetailsByIDPage/route.tsx"), route("/pki-collections/$collectionId", "cert-manager/PkiCollectionDetailsByIDPage/routes.tsx"), route("/settings", "cert-manager/SettingsPage/route.tsx"), + route("/audit-logs", "project/AuditLogsPage/route-cert-manager.tsx"), route("/access-management", "project/AccessControlPage/route-cert-manager.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-cert-manager.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-cert-manager.tsx"), @@ -325,6 +327,7 @@ const kmsRoutes = route("/projects/kms/$projectId", [ route("/overview", "kms/OverviewPage/route.tsx"), route("/kmip", "kms/KmipPage/route.tsx"), route("/settings", "kms/SettingsPage/route.tsx"), + route("/audit-logs", "project/AuditLogsPage/route-kms.tsx"), route("/access-management", "project/AccessControlPage/route-kms.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-kms.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-kms.tsx"), @@ -341,6 +344,7 @@ const sshRoutes = route("/projects/ssh/$projectId", [ route("/ca/$caId", "ssh/SshCaByIDPage/route.tsx"), route("/ssh-host-groups/$sshHostGroupId", "ssh/SshHostGroupDetailsByIDPage/route.tsx"), route("/settings", "ssh/SettingsPage/route.tsx"), + route("/audit-logs", "project/AuditLogsPage/route-ssh.tsx"), route("/access-management", "project/AccessControlPage/route-ssh.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-ssh.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-ssh.tsx"), @@ -357,6 +361,7 @@ const secretScanningRoutes = route("/projects/secret-scanning/$projectId", [ ]), route("/findings", "secret-scanning/SecretScanningFindingsPage/route.tsx"), route("/settings", "secret-scanning/SettingsPage/route.tsx"), + route("/audit-logs", "project/AuditLogsPage/route-secret-scanning.tsx"), route("/access-management", "project/AccessControlPage/route-secret-scanning.tsx"), route("/roles/$roleSlug", "project/RoleDetailsBySlugPage/route-secret-scanning.tsx"), route("/identities/$identityId", "project/IdentityDetailsByIDPage/route-secret-scanning.tsx"), From dd0c07fb95efd14ec27ff75271b470068cb98331 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Wed, 23 Jul 2025 18:18:59 -0700 Subject: [PATCH 10/24] improvements: remove fixed css --- frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 88f71f8e0..f62b3cb8c 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -1445,7 +1445,6 @@ export const OverviewPage = () => { } icon={faFolderBlank} iconSize="3x" - className="fixed" >