Merge branch 'main' into ENG-2625

This commit is contained in:
x
2025-04-23 17:59:34 -04:00
65 changed files with 545 additions and 434 deletions
@@ -267,7 +267,6 @@ export const secretReplicationServiceFactory = ({
const sourceLocalSecrets = await secretV2BridgeDAL.find({ folderId: folder.id, type: SecretType.Shared }); const sourceLocalSecrets = await secretV2BridgeDAL.find({ folderId: folder.id, type: SecretType.Shared });
const sourceSecretImports = await secretImportDAL.find({ folderId: folder.id }); const sourceSecretImports = await secretImportDAL.find({ folderId: folder.id });
const sourceImportedSecrets = await fnSecretsV2FromImports({ const sourceImportedSecrets = await fnSecretsV2FromImports({
projectId,
secretImports: sourceSecretImports, secretImports: sourceSecretImports,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
folderDAL, folderDAL,
+2 -1
View File
@@ -1089,7 +1089,8 @@ export const registerRoutes = async (
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
kmsService, kmsService,
snapshotService, snapshotService,
resourceMetadataDAL resourceMetadataDAL,
keyStore
}); });
const secretApprovalRequestService = secretApprovalRequestServiceFactory({ const secretApprovalRequestService = secretApprovalRequestServiceFactory({
@@ -29,7 +29,8 @@ import { SanitizedProjectSchema } from "../sanitizedSchemas";
const projectWithEnv = SanitizedProjectSchema.extend({ const projectWithEnv = SanitizedProjectSchema.extend({
_id: z.string(), _id: z.string(),
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array() environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array(),
kmsSecretManagerKeyId: z.string().nullable().optional()
}); });
export const registerProjectRouter = async (server: FastifyZodProvider) => { export const registerProjectRouter = async (server: FastifyZodProvider) => {
@@ -435,12 +435,16 @@ export const identityKubernetesAuthServiceFactory = ({
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId }); const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId });
if (!identityKubernetesAuth) {
throw new NotFoundError({ message: `Failed to find Kubernetes Auth for identity with ID ${identityId}` });
}
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) {
throw new BadRequestError({ throw new BadRequestError({
message: "The identity does not have Kubernetes Auth attached" message: "The identity does not have Kubernetes Auth attached"
}); });
} }
const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
actor, actor,
@@ -50,7 +50,7 @@ const getIntegrationSecretsV2 = async (
} }
// process secrets in current folder // process secrets in current folder
const secrets = await secretV2BridgeDAL.findByFolderId({ folderId: dto.folderId, projectId: dto.projectId }); const secrets = await secretV2BridgeDAL.findByFolderId({ folderId: dto.folderId });
secrets.forEach((secret) => { secrets.forEach((secret) => {
const secretKey = secret.key; const secretKey = secret.key;
@@ -63,7 +63,6 @@ const getIntegrationSecretsV2 = async (
// if no imports then return secrets in the current folder // if no imports then return secrets in the current folder
if (!secretImports.length) return content; if (!secretImports.length) return content;
const importedSecrets = await fnSecretsV2FromImports({ const importedSecrets = await fnSecretsV2FromImports({
projectId: dto.projectId,
decryptor: dto.decryptor, decryptor: dto.decryptor,
folderDAL, folderDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
@@ -159,8 +159,7 @@ export const fnSecretsV2FromImports = async ({
decryptor, decryptor,
expandSecretReferences, expandSecretReferences,
hasSecretAccess, hasSecretAccess,
viewSecretValue, viewSecretValue
projectId
}: { }: {
secretImports: (Omit<TSecretImports, "importEnv"> & { secretImports: (Omit<TSecretImports, "importEnv"> & {
importEnv: { id: string; slug: string; name: string }; importEnv: { id: string; slug: string; name: string };
@@ -177,7 +176,6 @@ export const fnSecretsV2FromImports = async ({
environment: string; environment: string;
}) => Promise<string | undefined>; }) => Promise<string | undefined>;
hasSecretAccess: (environment: string, secretPath: string, secretName: string, secretTagSlugs: string[]) => boolean; hasSecretAccess: (environment: string, secretPath: string, secretName: string, secretTagSlugs: string[]) => boolean;
projectId: string;
}) => { }) => {
const cyclicDetector = new Set(); const cyclicDetector = new Set();
const stack: { const stack: {
@@ -218,8 +216,7 @@ export const fnSecretsV2FromImports = async ({
type: SecretType.Shared type: SecretType.Shared
}, },
{ {
sort: [["id", "asc"]], sort: [["id", "asc"]]
useCache: { projectId }
} }
); );
const importedSecretsGroupByFolderId = groupBy(importedSecrets, (i) => i.folderId); const importedSecretsGroupByFolderId = groupBy(importedSecrets, (i) => i.folderId);
@@ -698,7 +698,6 @@ export const secretImportServiceFactory = ({
projectId projectId
}); });
const importedSecrets = await fnSecretsV2FromImports({ const importedSecrets = await fnSecretsV2FromImports({
projectId,
secretImports, secretImports,
folderDAL, folderDAL,
viewSecretValue: true, viewSecretValue: true,
@@ -214,7 +214,7 @@ export const secretSyncQueueFactory = ({
canExpandValue: () => true canExpandValue: () => true
}); });
const secrets = await secretV2BridgeDAL.findByFolderId({ folderId, projectId }); const secrets = await secretV2BridgeDAL.findByFolderId({ folderId });
await Promise.allSettled( await Promise.allSettled(
secrets.map(async (secret) => { secrets.map(async (secret) => {
@@ -244,7 +244,6 @@ export const secretSyncQueueFactory = ({
if (secretImports.length) { if (secretImports.length) {
const importedSecrets = await fnSecretsV2FromImports({ const importedSecrets = await fnSecretsV2FromImports({
projectId,
decryptor: decryptSecretValue, decryptor: decryptSecretValue,
folderDAL, folderDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
@@ -1,3 +1,4 @@
import { MongoAbility } from "@casl/ability";
import { Knex } from "knex"; import { Knex } from "knex";
import { validate as uuidValidate } from "uuid"; import { validate as uuidValidate } from "uuid";
@@ -15,46 +16,29 @@ import {
TFindFilter, TFindFilter,
TFindOpt TFindOpt
} from "@app/lib/knex"; } from "@app/lib/knex";
import { BufferKeysToString, OrderByDirection } from "@app/lib/types"; import { OrderByDirection } from "@app/lib/types";
import { SecretsOrderBy } from "@app/services/secret/secret-types"; import { SecretsOrderBy } from "@app/services/secret/secret-types";
import type { TFindSecretsByFolderIdsFilter } from "@app/services/secret-v2-bridge/secret-v2-bridge-types"; import type {
TFindSecretsByFolderIdsFilter,
TGetSecretsDTO
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
export const SecretDalCacheKeys = { export const SecretServiceCacheKeys = {
get productKey() { get productKey() {
const { INFISICAL_PLATFORM_VERSION } = getConfig(); const { INFISICAL_PLATFORM_VERSION } = getConfig();
return `${ProjectType.SecretManager}:${INFISICAL_PLATFORM_VERSION || 0}`; return `${ProjectType.SecretManager}:${INFISICAL_PLATFORM_VERSION || 0}`;
}, },
getSecretDalVersion: (projectId: string) => { getSecretDalVersion: (projectId: string) => {
return `${SecretDalCacheKeys.productKey}:${projectId}:${TableName.SecretV2}-dal-version`; return `${SecretServiceCacheKeys.productKey}:${projectId}:${TableName.SecretV2}-dal-version`;
}, },
findByFolderIds: ( getSecretsOfServiceLayer: (
projectId: string, projectId: string,
version: number, version: number,
{ useCache, tx, ...cacheKey }: Parameters<TSecretV2BridgeDALFactory["findByFolderIds"]>[0] dto: TGetSecretsDTO & { permissionRules: MongoAbility["rules"] }
) => { ) => {
return `${SecretDalCacheKeys.productKey}:${projectId}:${ return `${SecretServiceCacheKeys.productKey}:${projectId}:${
TableName.SecretV2 TableName.SecretV2
}-dal:v${version}:find-by-folder-ids:${generateCacheKeyFromData(cacheKey)}`; }-dal:v${version}:get-secrets-service-layer:${dto.actorId}-${generateCacheKeyFromData(dto)}`;
},
findByFolderId: (
projectId: string,
version: number,
{ useCache, tx, ...cacheKey }: Parameters<TSecretV2BridgeDALFactory["findByFolderId"]>[0]
) => {
return `${SecretDalCacheKeys.productKey}:${projectId}:${
TableName.SecretV2
}-dal:v${version}:find-by-folder-id:${generateCacheKeyFromData(cacheKey)}`;
},
find: (projectId: string, version: number, ...args: Parameters<TSecretV2BridgeDALFactory["find"]>) => {
const [filter, opts] = args;
delete opts?.tx;
delete opts?.useCache;
return `${SecretDalCacheKeys.productKey}:${projectId}:${
TableName.SecretV2
}-dal:v${version}:find:${generateCacheKeyFromData({
filter,
opts
})}`;
} }
}; };
@@ -64,14 +48,14 @@ interface TSecretV2DalArg {
keyStore: TKeyStoreFactory; keyStore: TKeyStoreFactory;
} }
const SECRET_DAL_TTL = 5 * 60; export const SECRET_DAL_TTL = 5 * 60;
const SECRET_DAL_VERSION_TTL = 15 * 60; export const SECRET_DAL_VERSION_TTL = 15 * 60;
const MAX_SECRET_CACHE_BYTES = 25 * 1024 * 1024; export const MAX_SECRET_CACHE_BYTES = 25 * 1024 * 1024;
export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => { export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
const secretOrm = ormify(db, TableName.SecretV2); const secretOrm = ormify(db, TableName.SecretV2);
const invalidateSecretCacheByProjectId = async (projectId: string) => { const invalidateSecretCacheByProjectId = async (projectId: string) => {
const secretDalVersionKey = SecretDalCacheKeys.getSecretDalVersion(projectId); const secretDalVersionKey = SecretServiceCacheKeys.getSecretDalVersion(projectId);
await keyStore.incrementBy(secretDalVersionKey, 1); await keyStore.incrementBy(secretDalVersionKey, 1);
await keyStore.setExpiry(secretDalVersionKey, SECRET_DAL_VERSION_TTL); await keyStore.setExpiry(secretDalVersionKey, SECRET_DAL_VERSION_TTL);
}; };
@@ -128,35 +112,9 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
} }
}; };
const find = async ( const find = async (filter: TFindFilter<TSecretsV2>, opts: TFindOpt<TSecretsV2> = {}) => {
filter: TFindFilter<TSecretsV2>, const { offset, limit, sort, tx } = opts;
opts: TFindOpt<TSecretsV2> & { useCache?: { projectId: string } } = {}
) => {
const { offset, limit, sort, tx, useCache } = opts;
try { try {
let secretDalVersion = 0;
if (useCache) {
const cachedSecretDalVersion = await keyStore.getItem(
SecretDalCacheKeys.getSecretDalVersion(useCache.projectId)
);
secretDalVersion = Number(cachedSecretDalVersion || 0);
const cacheKey = SecretDalCacheKeys.find(useCache.projectId, secretDalVersion, filter, opts);
const cachedSecrets = await keyStore.getItem(cacheKey);
if (cachedSecrets) {
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
const unsanitizedSecrets = JSON.parse(cachedSecrets) as BufferKeysToString<(typeof data)[number]>[];
const sanitizedSecrets = unsanitizedSecrets.map((el) => {
const encryptedValue = el.encryptedValue ? Buffer.from(el.encryptedValue, "base64") : null;
const encryptedComment = el.encryptedComment ? Buffer.from(el.encryptedComment, "base64") : null;
const createdAt = new Date(el.createdAt);
const updatedAt = new Date(el.updatedAt);
return { ...el, encryptedComment, encryptedValue, createdAt, updatedAt };
});
return sanitizedSecrets;
}
}
const query = (tx || db)(TableName.SecretV2) const query = (tx || db)(TableName.SecretV2)
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter(filter)) .where(buildFindFilter(filter))
@@ -225,22 +183,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
] ]
}); });
if (useCache) {
const cachedSecrets = data.map((el) => {
const encryptedValue = el.encryptedValue ? el.encryptedValue.toString("base64") : null;
const encryptedComment = el.encryptedComment ? el.encryptedComment.toString("base64") : null;
return { ...el, encryptedValue, encryptedComment };
});
const cache = JSON.stringify(cachedSecrets);
if (Buffer.byteLength(cache, "utf8") < MAX_SECRET_CACHE_BYTES) {
await keyStore.setItemWithExpiry(
SecretDalCacheKeys.find(useCache.projectId, secretDalVersion, filter, opts),
SECRET_DAL_TTL,
cache
);
}
}
return data; return data;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: `${TableName.SecretV2}: Find` }); throw new DatabaseError({ error, name: `${TableName.SecretV2}: Find` });
@@ -345,15 +287,9 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
} }
}; };
const findByFolderId = async (dto: { const findByFolderId = async (dto: { folderId: string; userId?: string; tx?: Knex }) => {
folderId: string;
userId?: string;
tx?: Knex;
projectId: string;
useCache?: boolean;
}) => {
try { try {
const { folderId, tx, projectId } = dto; const { folderId, tx } = dto;
let { userId } = dto; let { userId } = dto;
// check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo // check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo
if (userId && !uuidValidate(userId)) { if (userId && !uuidValidate(userId)) {
@@ -361,27 +297,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
userId = undefined; userId = undefined;
} }
const cachedSecretDalVersion = await keyStore.getItem(SecretDalCacheKeys.getSecretDalVersion(projectId));
const secretDalVersion = Number(cachedSecretDalVersion || 0);
if (dto.useCache) {
const cacheKey = SecretDalCacheKeys.findByFolderId(projectId, secretDalVersion, dto);
const cachedSecrets = await keyStore.getItem(cacheKey);
if (cachedSecrets) {
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
const unsanitizedSecrets = JSON.parse(cachedSecrets) as BufferKeysToString<(typeof data)[number]>[];
const sanitizedSecrets = unsanitizedSecrets.map((el) => {
const encryptedValue = el.encryptedValue ? Buffer.from(el.encryptedValue, "base64") : null;
const encryptedComment = el.encryptedComment ? Buffer.from(el.encryptedComment, "base64") : null;
const createdAt = new Date(el.createdAt);
const updatedAt = new Date(el.updatedAt);
return { ...el, encryptedComment, encryptedValue, createdAt, updatedAt };
});
return sanitizedSecrets;
}
}
const secs = await (tx || db.replicaNode())(TableName.SecretV2) const secs = await (tx || db.replicaNode())(TableName.SecretV2)
.where({ folderId }) .where({ folderId })
.where((bd) => { .where((bd) => {
@@ -437,22 +352,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
} }
] ]
}); });
if (dto.useCache) {
const newCachedSecrets = data.map((el) => {
const encryptedValue = el.encryptedValue ? el.encryptedValue.toString("base64") : null;
const encryptedComment = el.encryptedComment ? el.encryptedComment.toString("base64") : null;
return { ...el, encryptedValue, encryptedComment };
});
const cache = JSON.stringify(newCachedSecrets);
if (Buffer.byteLength(cache, "utf8") < MAX_SECRET_CACHE_BYTES) {
await keyStore.setItemWithExpiry(
SecretDalCacheKeys.findByFolderId(projectId, secretDalVersion, dto),
SECRET_DAL_TTL,
cache
);
}
}
return data; return data;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "get all secret" }); throw new DatabaseError({ error, name: "get all secret" });
@@ -542,11 +441,9 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
folderIds: string[]; folderIds: string[];
userId?: string; userId?: string;
tx?: Knex; tx?: Knex;
projectId: string;
filters?: TFindSecretsByFolderIdsFilter; filters?: TFindSecretsByFolderIdsFilter;
useCache?: boolean;
}) => { }) => {
const { folderIds, tx, filters, useCache, projectId } = dto; const { folderIds, tx, filters } = dto;
let { userId } = dto; let { userId } = dto;
try { try {
// check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo) // check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo)
@@ -555,26 +452,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
userId = undefined; userId = undefined;
} }
const cachedSecretDalVersion = await keyStore.getItem(SecretDalCacheKeys.getSecretDalVersion(projectId));
const secretDalVersion = Number(cachedSecretDalVersion || 0);
if (useCache) {
const cacheKey = SecretDalCacheKeys.findByFolderIds(projectId, secretDalVersion, dto);
const cachedSecrets = await keyStore.getItem(cacheKey);
if (cachedSecrets) {
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
const unsanitizedSecrets = JSON.parse(cachedSecrets) as BufferKeysToString<(typeof data)[number]>[];
const sanitizedSecrets = unsanitizedSecrets.map((el) => {
const encryptedValue = el.encryptedValue ? Buffer.from(el.encryptedValue, "base64") : null;
const encryptedComment = el.encryptedComment ? Buffer.from(el.encryptedComment, "base64") : null;
const createdAt = new Date(el.createdAt);
const updatedAt = new Date(el.updatedAt);
return { ...el, encryptedComment, encryptedValue, createdAt, updatedAt };
});
return sanitizedSecrets;
}
}
const query = (tx || db.replicaNode())(TableName.SecretV2) const query = (tx || db.replicaNode())(TableName.SecretV2)
.whereIn(`${TableName.SecretV2}.folderId`, folderIds) .whereIn(`${TableName.SecretV2}.folderId`, folderIds)
.where((bd) => { .where((bd) => {
@@ -700,22 +577,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
} }
] ]
}); });
if (useCache) {
const cachedSecrets = data.map((el) => {
const encryptedValue = el.encryptedValue ? el.encryptedValue.toString("base64") : null;
const encryptedComment = el.encryptedComment ? el.encryptedComment.toString("base64") : null;
return { ...el, encryptedValue, encryptedComment };
});
const cache = JSON.stringify(cachedSecrets);
if (Buffer.byteLength(cache, "utf8") < MAX_SECRET_CACHE_BYTES) {
await keyStore.setItemWithExpiry(
SecretDalCacheKeys.findByFolderIds(projectId, secretDalVersion, dto),
SECRET_DAL_TTL,
cache
);
}
}
return data; return data;
} catch (error) { } catch (error) {
@@ -509,7 +509,7 @@ export const expandSecretReferencesFactory = ({
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) return { value: "", tags: [] }; if (!folder) return { value: "", tags: [] };
const secrets = await secretDAL.findByFolderId({ folderId: folder.id, projectId, useCache: true }); const secrets = await secretDAL.findByFolderId({ folderId: folder.id });
const decryptedSecret = secrets.reduce<Record<string, { value: string; tags: string[] }>>((prev, secret) => { const decryptedSecret = secrets.reduce<Record<string, { value: string; tags: string[] }>>((prev, secret) => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@@ -25,6 +25,7 @@ import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-app
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal"; import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { DatabaseErrorCode } from "@app/lib/error-codes"; import { DatabaseErrorCode } from "@app/lib/error-codes";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { diff, groupBy } from "@app/lib/fn"; import { diff, groupBy } from "@app/lib/fn";
@@ -43,7 +44,12 @@ import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns"; import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import {
MAX_SECRET_CACHE_BYTES,
SECRET_DAL_TTL,
SecretServiceCacheKeys,
TSecretV2BridgeDALFactory
} from "./secret-v2-bridge-dal";
import { import {
buildHierarchy, buildHierarchy,
expandSecretReferencesFactory, expandSecretReferencesFactory,
@@ -105,6 +111,7 @@ type TSecretV2BridgeServiceFactoryDep = {
>; >;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">; snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">; resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
keyStore: Pick<TKeyStoreFactory, "getItem" | "setExpiry" | "setItemWithExpiry" | "deleteItem">;
}; };
export type TSecretV2BridgeServiceFactory = ReturnType<typeof secretV2BridgeServiceFactory>; export type TSecretV2BridgeServiceFactory = ReturnType<typeof secretV2BridgeServiceFactory>;
@@ -127,7 +134,8 @@ export const secretV2BridgeServiceFactory = ({
secretApprovalRequestDAL, secretApprovalRequestDAL,
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
kmsService, kmsService,
resourceMetadataDAL resourceMetadataDAL,
keyStore
}: TSecretV2BridgeServiceFactoryDep) => { }: TSecretV2BridgeServiceFactoryDep) => {
const $validateSecretReferences = async ( const $validateSecretReferences = async (
projectId: string, projectId: string,
@@ -800,12 +808,10 @@ export const secretV2BridgeServiceFactory = ({
const groupedFolderMappings = groupBy(folderMappings, (folderMapping) => folderMapping.folderId); const groupedFolderMappings = groupBy(folderMappings, (folderMapping) => folderMapping.folderId);
const secrets = await secretDAL.findByFolderIds({ const secrets = await secretDAL.findByFolderIds({
projectId,
folderIds: folderMappings.map((folderMapping) => folderMapping.folderId), folderIds: folderMappings.map((folderMapping) => folderMapping.folderId),
userId, userId,
tx: undefined, tx: undefined,
filters, filters
useCache: true
}); });
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
@@ -909,21 +915,22 @@ export const secretV2BridgeServiceFactory = ({
return decryptedSecrets; return decryptedSecrets;
}; };
const getSecrets = async ({ const getSecrets = async (dto: TGetSecretsDTO) => {
actorId, const {
path, actorId,
environment, path,
projectId, environment,
actor, projectId,
actorOrgId, actor,
viewSecretValue, actorOrgId,
actorAuthMethod, viewSecretValue,
includeImports, actorAuthMethod,
recursive, includeImports,
expandSecretReferences: shouldExpandSecretReferences, recursive,
throwOnMissingReadValuePermission = true, expandSecretReferences: shouldExpandSecretReferences,
...params throwOnMissingReadValuePermission = true,
}: TGetSecretsDTO) => { ...params
} = dto;
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -934,6 +941,42 @@ export const secretV2BridgeServiceFactory = ({
}); });
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret);
const cachedSecretDalVersion = await keyStore.getItem(SecretServiceCacheKeys.getSecretDalVersion(projectId));
const secretDalVersion = Number(cachedSecretDalVersion || 0);
const cacheKey = SecretServiceCacheKeys.getSecretsOfServiceLayer(projectId, secretDalVersion, {
...dto,
permissionRules: permission.rules
});
const { decryptor: secretManagerDecryptor, encryptor: secretManagerEncryptor } =
await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId
});
const encryptedCachedSecrets = await keyStore.getItem(cacheKey);
if (encryptedCachedSecrets) {
try {
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
const cachedSecrets = secretManagerDecryptor({ cipherTextBlob: Buffer.from(encryptedCachedSecrets, "base64") });
const { secrets, imports = [] } = JSON.parse(cachedSecrets.toString("utf8")) as {
secrets: typeof decryptedSecrets;
imports: typeof importedSecrets;
};
return {
secrets: secrets.map((el) => ({
...el,
createdAt: new Date(el.createdAt),
updatedAt: new Date(el.updatedAt)
})),
imports
};
} catch (err) {
logger.error(err, "Secret service layer cache miss");
await keyStore.deleteItem(cacheKey);
}
}
let paths: { folderId: string; path: string }[] = []; let paths: { folderId: string; path: string }[] = [];
if (recursive) { if (recursive) {
@@ -958,17 +1001,10 @@ export const secretV2BridgeServiceFactory = ({
const groupedPaths = groupBy(paths, (p) => p.folderId); const groupedPaths = groupBy(paths, (p) => p.folderId);
const secrets = await secretDAL.findByFolderIds({ const secrets = await secretDAL.findByFolderIds({
projectId,
folderIds: paths.map((p) => p.folderId), folderIds: paths.map((p) => p.folderId),
userId: actorId, userId: actorId,
tx: undefined, tx: undefined,
filters: params, filters: params
useCache: true
});
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId
}); });
// scott: if any of this changes it also needs to be mirrored in secret rotation for getting dashboard secrets // scott: if any of this changes it also needs to be mirrored in secret rotation for getting dashboard secrets
@@ -1086,15 +1122,19 @@ export const secretV2BridgeServiceFactory = ({
} }
if (!includeImports) { if (!includeImports) {
return { const payload = { secrets: decryptedSecrets, imports: [] };
secrets: decryptedSecrets const encryptedUpdatedCachedSecrets = secretManagerEncryptor({
}; plainText: Buffer.from(JSON.stringify(payload))
}).cipherTextBlob;
if (encryptedUpdatedCachedSecrets.byteLength < MAX_SECRET_CACHE_BYTES) {
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL, encryptedUpdatedCachedSecrets.toString("base64"));
}
return payload;
} }
const secretImports = await secretImportDAL.findByFolderIds(paths.map((p) => p.folderId)); const secretImports = await secretImportDAL.findByFolderIds(paths.map((p) => p.folderId));
const allowedImports = secretImports.filter(({ isReplication }) => !isReplication); const allowedImports = secretImports.filter(({ isReplication }) => !isReplication);
const importedSecrets = await fnSecretsV2FromImports({ const importedSecrets = await fnSecretsV2FromImports({
projectId,
viewSecretValue, viewSecretValue,
secretImports: allowedImports, secretImports: allowedImports,
secretDAL, secretDAL,
@@ -1129,10 +1169,14 @@ export const secretV2BridgeServiceFactory = ({
} }
}); });
return { const payload = { secrets: decryptedSecrets, imports: importedSecrets };
secrets: decryptedSecrets, const encryptedUpdatedCachedSecrets = secretManagerEncryptor({
imports: importedSecrets plainText: Buffer.from(JSON.stringify(payload))
}; }).cipherTextBlob;
if (encryptedUpdatedCachedSecrets.byteLength < MAX_SECRET_CACHE_BYTES) {
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL, encryptedUpdatedCachedSecrets.toString("base64"));
}
return payload;
}; };
const getSecretById = async ({ actorId, actor, actorOrgId, actorAuthMethod, secretId }: TGetASecretByIdDTO) => { const getSecretById = async ({ actorId, actor, actorOrgId, actorAuthMethod, secretId }: TGetASecretByIdDTO) => {
@@ -1312,7 +1356,6 @@ export const secretV2BridgeServiceFactory = ({
if (!secret && includeImports) { if (!secret && includeImports) {
const secretImports = await secretImportDAL.find({ folderId, isReplication: false }); const secretImports = await secretImportDAL.find({ folderId, isReplication: false });
const importedSecrets = await fnSecretsV2FromImports({ const importedSecrets = await fnSecretsV2FromImports({
projectId,
secretImports, secretImports,
viewSecretValue, viewSecretValue,
secretDAL, secretDAL,
@@ -2729,7 +2772,7 @@ export const secretV2BridgeServiceFactory = ({
generatePaths(folderMap).map(({ folderId, path }) => [folderId, path === "/" ? path : path.substring(1)]) generatePaths(folderMap).map(({ folderId, path }) => [folderId, path === "/" ? path : path.substring(1)])
); );
const secrets = await secretDAL.findByFolderIds({ folderIds: folders.map((f) => f.id), projectId, useCache: true }); const secrets = await secretDAL.findByFolderIds({ folderIds: folders.map((f) => f.id) });
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
+1 -2
View File
@@ -367,7 +367,7 @@ export const secretQueueFactory = ({
canExpandValue: () => true canExpandValue: () => true
}); });
// process secrets in current folder // process secrets in current folder
const secrets = await secretV2BridgeDAL.findByFolderId({ folderId: dto.folderId, projectId: dto.projectId }); const secrets = await secretV2BridgeDAL.findByFolderId({ folderId: dto.folderId });
await Promise.allSettled( await Promise.allSettled(
secrets.map(async (secret) => { secrets.map(async (secret) => {
@@ -397,7 +397,6 @@ export const secretQueueFactory = ({
// if no imports then return secrets in the current folder // if no imports then return secrets in the current folder
if (!secretImports.length) return content; if (!secretImports.length) return content;
const importedSecrets = await fnSecretsV2FromImports({ const importedSecrets = await fnSecretsV2FromImports({
projectId: dto.projectId,
decryptor: dto.decryptor, decryptor: dto.decryptor,
folderDAL, folderDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
+22 -11
View File
@@ -28,21 +28,32 @@ You can use it across various environments, whether it's local development, CI/C
``` ```
</Tab> </Tab>
<Tab title="Windows"> <Tab title="Windows">
Use [Scoop](https://scoop.sh/) package manager
```bash <Accordion title="Scoop package manager">
scoop bucket add org https://github.com/Infisical/scoop-infisical.git Use [Scoop](https://scoop.sh/) package manager
```
```bash ```bash
scoop install infisical scoop bucket add org https://github.com/Infisical/scoop-infisical.git
``` ```
### Updates ```bash
scoop install infisical
```
```bash ### Updates
scoop update infisical
``` ```bash
scoop update infisical
```
</Accordion>
<Accordion title="Winget package manager">
Use [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/) package manager
```bash
winget install infisical
```
</Accordion>
</Tab> </Tab>
<Tab title="NPM"> <Tab title="NPM">
File diff suppressed because one or more lines are too long
+8
View File
@@ -281,6 +281,14 @@ export const ROUTE_PATHS = Object.freeze({
"/cert-manager/$projectId/overview", "/cert-manager/$projectId/overview",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview" "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview"
), ),
CertificateAuthoritiesPage: setRoute(
"/cert-manager/$projectId/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities"
),
AlertingPage: setRoute(
"/cert-manager/$projectId/alerting",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting"
),
PkiCollectionDetailsByIDPage: setRoute( PkiCollectionDetailsByIDPage: setRoute(
"/cert-manager/$projectId/pki-collections/$collectionId", "/cert-manager/$projectId/pki-collections/$collectionId",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId" "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/pki-collections/$collectionId"
@@ -92,18 +92,46 @@ export const ProjectLayout = () => {
</Link> </Link>
)} )}
{isCertManager && ( {isCertManager && (
<Link <>
to={`/${ProjectType.CertificateManager}/$projectId/overview` as const} <Link
params={{ to={`/${ProjectType.CertificateManager}/$projectId/overview` as const}
projectId: currentWorkspace.id params={{
}} projectId: currentWorkspace.id
> }}
{({ isActive }) => ( >
<MenuItem isSelected={isActive} icon="lock-closed"> {({ isActive }) => (
Overview <MenuItem isSelected={isActive} icon="certificate">
</MenuItem> Certificates
)} </MenuItem>
</Link> )}
</Link>
<Link
to={
`/${ProjectType.CertificateManager}/$projectId/certificate-authorities` as const
}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="certificate-authority">
Certificate Authorities
</MenuItem>
)}
</Link>
<Link
to={`/${ProjectType.CertificateManager}/$projectId/alerting` as const}
params={{
projectId: currentWorkspace.id
}}
>
{({ isActive }) => (
<MenuItem isSelected={isActive} icon="notification-bell">
Alerting
</MenuItem>
)}
</Link>
</>
)} )}
{isCmek && ( {isCmek && (
<Link <Link
@@ -0,0 +1,29 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { PkiAlertsSection } from "./components";
export const AlertingPage = () => {
const { t } = useTranslation();
return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet>
<title>{t("common.head-title", { title: "Alerting" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Alerting" />
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.PkiAlerts}
>
<PkiAlertsSection />
</ProjectPermissionCan>
</div>
</div>
);
};
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AlertingPage } from "./AlertingPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting"
)({
component: AlertingPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Alerting"
}
]
};
}
});
@@ -20,9 +20,9 @@ import { useDeleteCa, useGetCaById } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/workspace/types"; import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { CaInstallCertModal } from "../CertificatesPage/components/CaTab/components/CaInstallCertModal"; import { CaInstallCertModal } from "../CertificateAuthoritiesPage/components/CaInstallCertModal";
import { CaModal } from "../CertificatesPage/components/CaTab/components/CaModal"; import { CaModal } from "../CertificateAuthoritiesPage/components/CaModal";
import { CertificateTemplatesSection } from "../CertificatesPage/components/CertificatesTab/components/CertificateTemplatesSection"; import { CertificateTemplatesSection } from "../CertificatesPage/components/CertificateTemplatesSection";
import { import {
CaCertificatesSection, CaCertificatesSection,
CaCrlsSection, CaCrlsSection,
@@ -13,7 +13,7 @@ export const Route = createFileRoute(
{ {
label: "Certificate Authorities", label: "Certificate Authorities",
link: linkOptions({ link: linkOptions({
to: "/cert-manager/$projectId/overview", to: "/cert-manager/$projectId/certificate-authorities",
params: { params: {
projectId: params.projectId projectId: params.projectId
} }
@@ -0,0 +1,29 @@
import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { CaSection } from "./components";
export const CertificateAuthoritiesPage = () => {
const { t } = useTranslation();
return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet>
<title>{t("common.head-title", { title: "Certificate Authorities" })}</title>
</Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Certificate Authorities" />
<ProjectPermissionCan
renderGuardBanner
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.CertificateAuthorities}
>
<CaSection />
</ProjectPermissionCan>
</div>
</div>
);
};
@@ -2,7 +2,7 @@ import { Modal, ModalContent } from "@app/components/v2";
import { useGetCaCert } from "@app/hooks/api"; import { useGetCaCert } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "../../CertificatesTab/components/CertificateContent"; import { CertificateContent } from "../../CertificatesPage/components/CertificateContent";
type Props = { type Props = {
popUp: UsePopUpState<["caCert"]>; popUp: UsePopUpState<["caCert"]>;
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { CertificateAuthoritiesPage } from "./CertificateAuthoritiesPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities"
)({
component: CertificateAuthoritiesPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Certificate Authorities"
}
]
};
}
});
@@ -2,63 +2,49 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import { CaTab, CertificatesTab, PkiAlertsTab } from "./components"; import { PkiCollectionSection } from "../AlertingPage/components";
import { CertificatesSection } from "./components";
enum TabSections {
Ca = "certificate-authorities",
Certificates = "certificates",
Alerting = "alerting"
}
export const CertificatesPage = () => { export const CertificatesPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { permission } = useProjectPermission();
const canAccessPkiColl = permission.can(
ProjectPermissionActions.Read,
ProjectPermissionSub.PkiCollections
);
const canAccessCerts = permission.can(
ProjectPermissionActions.Read,
ProjectPermissionSub.Certificates
);
return ( return (
<div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
<Helmet> <Helmet>
<title>{t("common.head-title", { title: "Certificates" })}</title> <title>{t("common.head-title", { title: "Certificates" })}</title>
</Helmet> </Helmet>
<div className="mx-auto mb-6 w-full max-w-7xl"> <div className="mx-auto mb-6 w-full max-w-7xl">
<PageHeader title="Overview" /> <PageHeader title="Certificates" />
<Tabs defaultValue={TabSections.Certificates}> {/* If both are false, the section does not render. This is to prevent duplicate banners. */}
<TabList> {(canAccessCerts || canAccessPkiColl) && (
<Tab value={TabSections.Certificates}>Certificates</Tab> <ProjectPermissionCan
<Tab value={TabSections.Ca}>Certificate Authorities</Tab> renderGuardBanner
<Tab value={TabSections.Alerting}>Alerting</Tab> I={ProjectPermissionActions.Read}
</TabList> a={ProjectPermissionSub.PkiCollections}
<TabPanel value={TabSections.Certificates}> >
<ProjectPermissionCan <PkiCollectionSection />
renderGuardBanner </ProjectPermissionCan>
passThrough={false} )}
I={ProjectPermissionActions.Read} <ProjectPermissionCan
a={ProjectPermissionSub.Certificates} renderGuardBanner
> I={ProjectPermissionActions.Read}
<CertificatesTab /> a={ProjectPermissionSub.Certificates}
</ProjectPermissionCan> >
</TabPanel> <CertificatesSection />
<TabPanel value={TabSections.Ca}> </ProjectPermissionCan>
<ProjectPermissionCan
renderGuardBanner
passThrough={false}
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.CertificateAuthorities}
>
<CaTab />
</ProjectPermissionCan>
</TabPanel>
<TabPanel value={TabSections.Alerting}>
<ProjectPermissionCan
renderGuardBanner
passThrough={false}
I={ProjectPermissionActions.Read}
a={ProjectPermissionSub.PkiAlerts}
>
<PkiAlertsTab />
</ProjectPermissionCan>
</TabPanel>
</Tabs>
</div> </div>
</div> </div>
); );
@@ -1,17 +0,0 @@
import { motion } from "framer-motion";
import { CaSection } from "./components";
export const CaTab = () => {
return (
<motion.div
key="panel-certificate-authorities"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<CaSection />
</motion.div>
);
};
@@ -1 +0,0 @@
export { CaTab } from "./CaTab";
@@ -1,21 +0,0 @@
import { motion } from "framer-motion";
import { PkiCollectionSection } from "../PkiAlertsTab/components";
// import { CertificateTemplatesSection } from "./components/CertificateTemplatesSection";
import { CertificatesSection } from "./components";
export const CertificatesTab = () => {
return (
<motion.div
key="panel-certificates"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<PkiCollectionSection />
{/* <CertificateTemplatesSection /> */}
<CertificatesSection />
</motion.div>
);
};
@@ -1 +0,0 @@
export { CertificatesSection } from "./CertificatesSection";
@@ -1 +0,0 @@
export { CertificatesTab } from "./CertificatesTab";
@@ -1,17 +0,0 @@
import { motion } from "framer-motion";
import { PkiAlertsSection } from "./components";
export const PkiAlertsTab = () => {
return (
<motion.div
key="panel-alerts"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<PkiAlertsSection />
</motion.div>
);
};
@@ -1 +0,0 @@
export { PkiAlertsTab } from "./PkiAlertsTab";
@@ -1,3 +1 @@
export { CaTab } from "./CaTab"; export { CertificatesSection } from "./CertificatesSection";
export { CertificatesTab } from "./CertificatesTab";
export { PkiAlertsTab } from "./PkiAlertsTab";
@@ -22,7 +22,7 @@ import { PkiItemType } from "@app/hooks/api/pkiCollections/constants";
import { ProjectType } from "@app/hooks/api/workspace/types"; import { ProjectType } from "@app/hooks/api/workspace/types";
import { usePopUp } from "@app/hooks/usePopUp"; import { usePopUp } from "@app/hooks/usePopUp";
import { PkiCollectionModal } from "../CertificatesPage/components/PkiAlertsTab/components/PkiCollectionModal"; import { PkiCollectionModal } from "../AlertingPage/components/PkiCollectionModal";
import { PkiCollectionDetailsSection, PkiCollectionItemsSection } from "./components"; import { PkiCollectionDetailsSection, PkiCollectionItemsSection } from "./components";
export const PkiCollectionPage = () => { export const PkiCollectionPage = () => {
@@ -1,6 +1,6 @@
import { Modal, ModalContent } from "@app/components/v2"; import { Modal, ModalContent } from "@app/components/v2";
import { KmipClientCertificate } from "@app/hooks/api/kmip/types"; import { KmipClientCertificate } from "@app/hooks/api/kmip/types";
import { CertificateContent } from "@app/pages/cert-manager/CertificatesPage/components/CertificatesTab/components/CertificateContent"; import { CertificateContent } from "@app/pages/cert-manager/CertificatesPage/components/CertificateContent";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@@ -0,0 +1,152 @@
import { faAws, faGoogle } from "@fortawesome/free-brands-svg-icons";
import { faCheck, faCopy, faEllipsis } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions/OrgPermissionCan";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2/Dropdown";
import { IconButton } from "@app/components/v2/IconButton";
import { Td, Tr } from "@app/components/v2/Table";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context/OrgPermissionContext";
import { useToggle } from "@app/hooks";
import { ExternalKmsProvider, KmsListEntry } from "@app/hooks/api/kms/types";
import { SubscriptionPlan } from "@app/hooks/api/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
kms: KmsListEntry;
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["editExternalKms", "removeExternalKms", "upgradePlan"]>,
data?: {
kmsId?: string;
name?: string;
provider?: string;
}
) => void;
subscription: SubscriptionPlan;
};
export const ExternalKmsItem = ({ kms, handlePopUpOpen, subscription }: Props) => {
const [isKmsIdCopied, { timedToggle: toggleKmsIdCopied }] = useToggle(false);
const [isKmsAliasCopied, { timedToggle: toggleKmsAliasCopied }] = useToggle(false);
return (
<Tr key={kms.id}>
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis hover:overflow-auto hover:break-all">
{kms.externalKms.provider === ExternalKmsProvider.Aws && <FontAwesomeIcon icon={faAws} />}
{kms.externalKms.provider === ExternalKmsProvider.Gcp && (
<FontAwesomeIcon icon={faGoogle} />
)}
<div className="ml-2">{kms.externalKms.provider.toUpperCase()}</div>
</Td>
<Td>
<div className="group flex items-center gap-2">
{kms.name}
<IconButton
size="xs"
ariaLabel="copy icon"
colorSchema="secondary"
className="relative rounded-md opacity-0 group-hover:opacity-100"
onClick={() => {
if (isKmsAliasCopied) {
return;
}
navigator.clipboard.writeText(kms.name);
createNotification({
text: "KMS alias copied to clipboard",
type: "success"
});
toggleKmsAliasCopied(2000);
}}
>
<FontAwesomeIcon icon={isKmsAliasCopied ? faCheck : faCopy} />
</IconButton>
</div>
</Td>
<Td>
<div className="group flex items-center gap-2">
{kms.id}
<IconButton
size="xs"
ariaLabel="copy icon"
colorSchema="secondary"
className="relative rounded-md opacity-0 group-hover:opacity-100"
onClick={() => {
if (isKmsIdCopied) {
return;
}
navigator.clipboard.writeText(kms.id);
createNotification({
text: "KMS ID copied to clipboard",
type: "success"
});
toggleKmsIdCopied(2000);
}}
>
<FontAwesomeIcon icon={isKmsIdCopied ? faCheck : faCopy} />
</IconButton>
</div>
</Td>
<Td>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="flex justify-end hover:text-primary-400 data-[state=open]:text-primary-400">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan I={OrgPermissionActions.Edit} an={OrgPermissionSubjects.Kms}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
if (subscription && !subscription?.externalKms) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("editExternalKms", {
kmsId: kms.id
});
}}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan I={OrgPermissionActions.Delete} an={OrgPermissionSubjects.Kms}>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("removeExternalKms", {
name: kms.name,
kmsId: kms.id,
provider: kms.externalKms.provider
});
}}
>
Delete
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
);
};
@@ -1,7 +1,5 @@
import { faAws, faGoogle } from "@fortawesome/free-brands-svg-icons"; import { faLock, faPlus } from "@fortawesome/free-solid-svg-icons";
import { faEllipsis, faLock, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -9,10 +7,6 @@ import { OrgPermissionCan } from "@app/components/permissions";
import { import {
Button, Button,
DeleteActionModal, DeleteActionModal,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState, EmptyState,
Table, Table,
TableContainer, TableContainer,
@@ -31,9 +25,9 @@ import {
import { withPermission } from "@app/hoc"; import { withPermission } from "@app/hoc";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useGetExternalKmsList, useRemoveExternalKms } from "@app/hooks/api"; import { useGetExternalKmsList, useRemoveExternalKms } from "@app/hooks/api";
import { ExternalKmsProvider } from "@app/hooks/api/kms/types";
import { AddExternalKmsForm } from "./AddExternalKmsForm"; import { AddExternalKmsForm } from "./AddExternalKmsForm";
import { ExternalKmsItem } from "./ExternalKmsItem";
import { UpdateExternalKmsForm } from "./UpdateExternalKmsForm"; import { UpdateExternalKmsForm } from "./UpdateExternalKmsForm";
export const OrgEncryptionTab = withPermission( export const OrgEncryptionTab = withPermission(
@@ -102,6 +96,7 @@ export const OrgEncryptionTab = withPermission(
<Tr> <Tr>
<Td>Provider</Td> <Td>Provider</Td>
<Td>Alias</Td> <Td>Alias</Td>
<Td>ID</Td>
</Tr> </Tr>
</THead> </THead>
<TBody> <TBody>
@@ -115,78 +110,12 @@ export const OrgEncryptionTab = withPermission(
)} )}
{!isExternalKmsListLoading && {!isExternalKmsListLoading &&
externalKmsList?.map((kms) => ( externalKmsList?.map((kms) => (
<Tr key={kms.id}> <ExternalKmsItem
<Td className="flex max-w-xs items-center overflow-hidden text-ellipsis hover:overflow-auto hover:break-all"> key={kms.id}
{kms.externalKms.provider === ExternalKmsProvider.Aws && ( kms={kms}
<FontAwesomeIcon icon={faAws} /> handlePopUpOpen={handlePopUpOpen}
)} subscription={subscription}
{kms.externalKms.provider === ExternalKmsProvider.Gcp && ( />
<FontAwesomeIcon icon={faGoogle} />
)}
<div className="ml-2">{kms.externalKms.provider.toUpperCase()}</div>
</Td>
<Td>{kms.name}</Td>
<Td>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="flex justify-end hover:text-primary-400 data-[state=open]:text-primary-400">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<OrgPermissionCan
I={OrgPermissionActions.Edit}
an={OrgPermissionSubjects.Kms}
>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
if (subscription && !subscription?.externalKms) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("editExternalKms", {
kmsId: kms.id
});
}}
>
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionActions.Delete}
an={OrgPermissionSubjects.Kms}
>
{(isAllowed) => (
<DropdownMenuItem
disabled={!isAllowed}
className={twMerge(
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("removeExternalKms", {
name: kms.name,
kmsId: kms.id,
provider: kms.externalKms.provider
});
}}
>
Delete
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
))} ))}
</TBody> </TBody>
</Table> </Table>
+57
View File
@@ -92,6 +92,8 @@ import { Route as kmsOverviewPageRouteImport } from './pages/kms/OverviewPage/ro
import { Route as kmsKmipPageRouteImport } from './pages/kms/KmipPage/route' import { Route as kmsKmipPageRouteImport } from './pages/kms/KmipPage/route'
import { Route as certManagerSettingsPageRouteImport } from './pages/cert-manager/SettingsPage/route' import { Route as certManagerSettingsPageRouteImport } from './pages/cert-manager/SettingsPage/route'
import { Route as certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route' import { Route as certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route'
import { Route as certManagerCertificateAuthoritiesPageRouteImport } from './pages/cert-manager/CertificateAuthoritiesPage/route'
import { Route as certManagerAlertingPageRouteImport } from './pages/cert-manager/AlertingPage/route'
import { Route as projectRoleDetailsBySlugPageRouteSshImport } from './pages/project/RoleDetailsBySlugPage/route-ssh' import { Route as projectRoleDetailsBySlugPageRouteSshImport } from './pages/project/RoleDetailsBySlugPage/route-ssh'
import { Route as projectMemberDetailsByIDPageRouteSshImport } from './pages/project/MemberDetailsByIDPage/route-ssh' import { Route as projectMemberDetailsByIDPageRouteSshImport } from './pages/project/MemberDetailsByIDPage/route-ssh'
import { Route as projectIdentityDetailsByIDPageRouteSshImport } from './pages/project/IdentityDetailsByIDPage/route-ssh' import { Route as projectIdentityDetailsByIDPageRouteSshImport } from './pages/project/IdentityDetailsByIDPage/route-ssh'
@@ -894,6 +896,20 @@ const certManagerCertificatesPageRouteRoute =
getParentRoute: () => certManagerLayoutRoute, getParentRoute: () => certManagerLayoutRoute,
} as any) } as any)
const certManagerCertificateAuthoritiesPageRouteRoute =
certManagerCertificateAuthoritiesPageRouteImport.update({
id: '/certificate-authorities',
path: '/certificate-authorities',
getParentRoute: () => certManagerLayoutRoute,
} as any)
const certManagerAlertingPageRouteRoute =
certManagerAlertingPageRouteImport.update({
id: '/alerting',
path: '/alerting',
getParentRoute: () => certManagerLayoutRoute,
} as any)
const projectRoleDetailsBySlugPageRouteSshRoute = const projectRoleDetailsBySlugPageRouteSshRoute =
projectRoleDetailsBySlugPageRouteSshImport.update({ projectRoleDetailsBySlugPageRouteSshImport.update({
id: '/roles/$roleSlug', id: '/roles/$roleSlug',
@@ -2104,6 +2120,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof sshLayoutImport preLoaderRoute: typeof sshLayoutImport
parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutSshProjectIdImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutSshProjectIdImport
} }
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting'
path: '/alerting'
fullPath: '/cert-manager/$projectId/alerting'
preLoaderRoute: typeof certManagerAlertingPageRouteImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities'
path: '/certificate-authorities'
fullPath: '/cert-manager/$projectId/certificate-authorities'
preLoaderRoute: typeof certManagerCertificateAuthoritiesPageRouteImport
parentRoute: typeof certManagerLayoutImport
}
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': { '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': {
id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview' id: '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview'
path: '/overview' path: '/overview'
@@ -3119,6 +3149,8 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren =
) )
interface certManagerLayoutRouteChildren { interface certManagerLayoutRouteChildren {
certManagerAlertingPageRouteRoute: typeof certManagerAlertingPageRouteRoute
certManagerCertificateAuthoritiesPageRouteRoute: typeof certManagerCertificateAuthoritiesPageRouteRoute
certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute
certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute
projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute
@@ -3130,6 +3162,9 @@ interface certManagerLayoutRouteChildren {
} }
const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = { const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = {
certManagerAlertingPageRouteRoute: certManagerAlertingPageRouteRoute,
certManagerCertificateAuthoritiesPageRouteRoute:
certManagerCertificateAuthoritiesPageRouteRoute,
certManagerCertificatesPageRouteRoute: certManagerCertificatesPageRouteRoute, certManagerCertificatesPageRouteRoute: certManagerCertificatesPageRouteRoute,
certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute, certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute,
projectAccessControlPageRouteCertManagerRoute: projectAccessControlPageRouteCertManagerRoute:
@@ -3794,6 +3829,8 @@ export interface FileRoutesByFullPath {
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute '/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute '/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute '/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute '/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute '/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute '/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
@@ -3969,6 +4006,8 @@ export interface FileRoutesByTo {
'/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute '/organization/secret-manager/overview': typeof organizationSecretManagerOverviewPageRouteRoute
'/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute '/organization/secret-sharing/settings': typeof organizationSecretSharingSettingsPageRouteRoute
'/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute '/organization/ssh/overview': typeof organizationSshOverviewPageRouteRoute
'/cert-manager/$projectId/alerting': typeof certManagerAlertingPageRouteRoute
'/cert-manager/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute '/cert-manager/$projectId/overview': typeof certManagerCertificatesPageRouteRoute
'/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute '/cert-manager/$projectId/settings': typeof certManagerSettingsPageRouteRoute
'/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute '/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute
@@ -4160,6 +4199,8 @@ export interface FileRoutesById {
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout': typeof kmsLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout': typeof secretManagerLayoutRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout': typeof secretManagerLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout': typeof sshLayoutRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout': typeof sshLayoutRouteWithChildren
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting': typeof certManagerAlertingPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': typeof certManagerCertificatesPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview': typeof certManagerCertificatesPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': typeof kmsKmipPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip': typeof kmsKmipPageRouteRoute
@@ -4344,6 +4385,8 @@ export interface FileRouteTypes {
| '/organization/secret-manager/overview' | '/organization/secret-manager/overview'
| '/organization/secret-sharing/settings' | '/organization/secret-sharing/settings'
| '/organization/ssh/overview' | '/organization/ssh/overview'
| '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview' | '/cert-manager/$projectId/overview'
| '/cert-manager/$projectId/settings' | '/cert-manager/$projectId/settings'
| '/kms/$projectId/kmip' | '/kms/$projectId/kmip'
@@ -4518,6 +4561,8 @@ export interface FileRouteTypes {
| '/organization/secret-manager/overview' | '/organization/secret-manager/overview'
| '/organization/secret-sharing/settings' | '/organization/secret-sharing/settings'
| '/organization/ssh/overview' | '/organization/ssh/overview'
| '/cert-manager/$projectId/alerting'
| '/cert-manager/$projectId/certificate-authorities'
| '/cert-manager/$projectId/overview' | '/cert-manager/$projectId/overview'
| '/cert-manager/$projectId/settings' | '/cert-manager/$projectId/settings'
| '/kms/$projectId/kmip' | '/kms/$projectId/kmip'
@@ -4707,6 +4752,8 @@ export interface FileRouteTypes {
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout'
| '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout'
| '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview'
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings'
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout/kmip'
@@ -5222,6 +5269,8 @@ export const routeTree = rootRoute
"filePath": "cert-manager/layout.tsx", "filePath": "cert-manager/layout.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId", "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId",
"children": [ "children": [
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/settings",
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management", "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/access-management",
@@ -5277,6 +5326,14 @@ export const routeTree = rootRoute
"/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/roles/$roleSlug" "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout/roles/$roleSlug"
] ]
}, },
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/alerting": {
"filePath": "cert-manager/AlertingPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/certificate-authorities": {
"filePath": "cert-manager/CertificateAuthoritiesPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
},
"/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview": { "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout/overview": {
"filePath": "cert-manager/CertificatesPage/route.tsx", "filePath": "cert-manager/CertificatesPage/route.tsx",
"parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout" "parent": "/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId/_cert-manager-layout"
+2
View File
@@ -282,6 +282,8 @@ const secretManagerIntegrationsRedirect = route("/integrations", [
const certManagerRoutes = route("/cert-manager/$projectId", [ const certManagerRoutes = route("/cert-manager/$projectId", [
layout("cert-manager-layout", "cert-manager/layout.tsx", [ layout("cert-manager-layout", "cert-manager/layout.tsx", [
route("/overview", "cert-manager/CertificatesPage/route.tsx"), route("/overview", "cert-manager/CertificatesPage/route.tsx"),
route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"),
route("/alerting", "cert-manager/AlertingPage/route.tsx"),
route("/ca/$caId", "cert-manager/CertAuthDetailsByIDPage/route.tsx"), route("/ca/$caId", "cert-manager/CertAuthDetailsByIDPage/route.tsx"),
route("/pki-collections/$collectionId", "cert-manager/PkiCollectionDetailsByIDPage/routes.tsx"), route("/pki-collections/$collectionId", "cert-manager/PkiCollectionDetailsByIDPage/routes.tsx"),
route("/settings", "cert-manager/SettingsPage/route.tsx"), route("/settings", "cert-manager/SettingsPage/route.tsx"),