Merge pull request #3833 from akhilmhdh/feat/pg-queue

feat: migrated dynamic secret to pg queue and corrected service layer
This commit is contained in:
Akhil Mohan
2025-06-23 23:51:06 +05:30
committed by GitHub
13 changed files with 253 additions and 131 deletions
+1
View File
@@ -26,6 +26,7 @@ export const mockQueue = (): TQueueServiceFactory => {
getRepeatableJobs: async () => [], getRepeatableJobs: async () => [],
clearQueue: async () => {}, clearQueue: async () => {},
stopJobById: async () => {}, stopJobById: async () => {},
stopJobByIdPg: async () => {},
stopRepeatableJobByJobId: async () => true, stopRepeatableJobByJobId: async () => true,
stopRepeatableJobByKey: async () => true stopRepeatableJobByKey: async () => true
}; };
+2 -2
View File
@@ -10,8 +10,8 @@ import { TAuditLogServiceFactory, TCreateAuditLogDTO } from "@app/ee/services/au
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-types"; import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-types";
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-types"; import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-types";
import { TCertificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service"; import { TCertificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service";
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-types";
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service"; import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-types";
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service"; import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service"; import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
@@ -3,9 +3,43 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { DynamicSecretLeasesSchema, TableName } from "@app/db/schemas"; import { DynamicSecretLeasesSchema, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex"; import { ormify, selectAllTableCols, TOrmify } from "@app/lib/knex";
export type TDynamicSecretLeaseDALFactory = ReturnType<typeof dynamicSecretLeaseDALFactory>; export interface TDynamicSecretLeaseDALFactory extends Omit<TOrmify<TableName.DynamicSecretLease>, "findById"> {
countLeasesForDynamicSecret: (dynamicSecretId: string, tx?: Knex) => Promise<number>;
findById: (
id: string,
tx?: Knex
) => Promise<
| {
dynamicSecret: {
id: string;
name: string;
version: number;
type: string;
defaultTTL: string;
maxTTL: string | null | undefined;
encryptedInput: Buffer;
folderId: string;
status: string | null | undefined;
statusDetails: string | null | undefined;
createdAt: Date;
updatedAt: Date;
};
version: number;
id: string;
createdAt: Date;
updatedAt: Date;
externalEntityId: string;
expireAt: Date;
dynamicSecretId: string;
status?: string | null | undefined;
config?: unknown;
statusDetails?: string | null | undefined;
}
| undefined
>;
}
export const dynamicSecretLeaseDALFactory = (db: TDbClient) => { export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.DynamicSecretLease); const orm = ormify(db, TableName.DynamicSecretLease);
@@ -21,7 +21,12 @@ type TDynamicSecretLeaseQueueServiceFactoryDep = {
folderDAL: Pick<TSecretFolderDALFactory, "findById">; folderDAL: Pick<TSecretFolderDALFactory, "findById">;
}; };
export type TDynamicSecretLeaseQueueServiceFactory = ReturnType<typeof dynamicSecretLeaseQueueServiceFactory>; export type TDynamicSecretLeaseQueueServiceFactory = {
pruneDynamicSecret: (dynamicSecretCfgId: string) => Promise<void>;
setLeaseRevocation: (leaseId: string, expiryAt: Date) => Promise<void>;
unsetLeaseRevocation: (leaseId: string) => Promise<void>;
init: () => Promise<void>;
};
export const dynamicSecretLeaseQueueServiceFactory = ({ export const dynamicSecretLeaseQueueServiceFactory = ({
queueService, queueService,
@@ -30,55 +35,48 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
dynamicSecretLeaseDAL, dynamicSecretLeaseDAL,
kmsService, kmsService,
folderDAL folderDAL
}: TDynamicSecretLeaseQueueServiceFactoryDep) => { }: TDynamicSecretLeaseQueueServiceFactoryDep): TDynamicSecretLeaseQueueServiceFactory => {
const pruneDynamicSecret = async (dynamicSecretCfgId: string) => { const pruneDynamicSecret = async (dynamicSecretCfgId: string) => {
await queueService.queue( await queueService.queuePg<QueueName.DynamicSecretRevocation>(
QueueName.DynamicSecretRevocation,
QueueJobs.DynamicSecretPruning, QueueJobs.DynamicSecretPruning,
{ dynamicSecretCfgId }, { dynamicSecretCfgId },
{ {
jobId: dynamicSecretCfgId, singletonKey: dynamicSecretCfgId,
backoff: { retryLimit: 3,
type: "exponential", retryBackoff: true
delay: 3000
},
removeOnFail: {
count: 3
},
removeOnComplete: true
} }
); );
}; };
const setLeaseRevocation = async (leaseId: string, expiry: number) => { const setLeaseRevocation = async (leaseId: string, expiryAt: Date) => {
await queueService.queue( await queueService.queuePg<QueueName.DynamicSecretRevocation>(
QueueName.DynamicSecretRevocation,
QueueJobs.DynamicSecretRevocation, QueueJobs.DynamicSecretRevocation,
{ leaseId }, { leaseId },
{ {
jobId: leaseId, id: leaseId,
backoff: { singletonKey: leaseId,
type: "exponential", startAfter: expiryAt,
delay: 3000 retryLimit: 3,
}, retryBackoff: true,
delay: expiry, retentionDays: 2
removeOnFail: {
count: 3
},
removeOnComplete: true
} }
); );
}; };
const unsetLeaseRevocation = async (leaseId: string) => { const unsetLeaseRevocation = async (leaseId: string) => {
await queueService.stopJobById(QueueName.DynamicSecretRevocation, leaseId); await queueService.stopJobById(QueueName.DynamicSecretRevocation, leaseId);
await queueService.stopJobByIdPg(QueueName.DynamicSecretRevocation, leaseId);
}; };
queueService.start(QueueName.DynamicSecretRevocation, async (job) => { const $dynamicSecretQueueJob = async (
jobName: string,
jobId: string,
data: { leaseId: string } | { dynamicSecretCfgId: string }
): Promise<void> => {
try { try {
if (job.name === QueueJobs.DynamicSecretRevocation) { if (jobName === QueueJobs.DynamicSecretRevocation) {
const { leaseId } = job.data as { leaseId: string }; const { leaseId } = data as { leaseId: string };
logger.info("Dynamic secret lease revocation started: ", leaseId, job.id); logger.info("Dynamic secret lease revocation started: ", leaseId, jobId);
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId); const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" }); if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
@@ -107,9 +105,9 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
return; return;
} }
if (job.name === QueueJobs.DynamicSecretPruning) { if (jobName === QueueJobs.DynamicSecretPruning) {
const { dynamicSecretCfgId } = job.data as { dynamicSecretCfgId: string }; const { dynamicSecretCfgId } = data as { dynamicSecretCfgId: string };
logger.info("Dynamic secret pruning started: ", dynamicSecretCfgId, job.id); logger.info("Dynamic secret pruning started: ", dynamicSecretCfgId, jobId);
const dynamicSecretCfg = await dynamicSecretDAL.findById(dynamicSecretCfgId); const dynamicSecretCfg = await dynamicSecretDAL.findById(dynamicSecretCfgId);
if (!dynamicSecretCfg) throw new DisableRotationErrors({ message: "Dynamic secret not found" }); if (!dynamicSecretCfg) throw new DisableRotationErrors({ message: "Dynamic secret not found" });
if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting) if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting)
@@ -150,38 +148,68 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
await dynamicSecretDAL.deleteById(dynamicSecretCfgId); await dynamicSecretDAL.deleteById(dynamicSecretCfgId);
} }
logger.info("Finished dynamic secret job", job.id); logger.info("Finished dynamic secret job", jobId);
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
if (job?.name === QueueJobs.DynamicSecretPruning) { if (jobName === QueueJobs.DynamicSecretPruning) {
const { dynamicSecretCfgId } = job.data as { dynamicSecretCfgId: string }; const { dynamicSecretCfgId } = data as { dynamicSecretCfgId: string };
await dynamicSecretDAL.updateById(dynamicSecretCfgId, { await dynamicSecretDAL.updateById(dynamicSecretCfgId, {
status: DynamicSecretStatus.FailedDeletion, status: DynamicSecretStatus.FailedDeletion,
statusDetails: (error as Error)?.message?.slice(0, 255) statusDetails: (error as Error)?.message?.slice(0, 255)
}); });
} }
if (job?.name === QueueJobs.DynamicSecretRevocation) { if (jobName === QueueJobs.DynamicSecretRevocation) {
const { leaseId } = job.data as { leaseId: string }; const { leaseId } = data as { leaseId: string };
await dynamicSecretLeaseDAL.updateById(leaseId, { await dynamicSecretLeaseDAL.updateById(leaseId, {
status: DynamicSecretStatus.FailedDeletion, status: DynamicSecretStatus.FailedDeletion,
statusDetails: (error as Error)?.message?.slice(0, 255) statusDetails: (error as Error)?.message?.slice(0, 255)
}); });
} }
if (error instanceof DisableRotationErrors) { if (error instanceof DisableRotationErrors) {
if (job.id) { if (jobId) {
await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, job.id); await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, jobId);
await queueService.stopJobByIdPg(QueueName.DynamicSecretRevocation, jobId);
} }
} }
// propogate to next part // propogate to next part
throw error; throw error;
} }
};
queueService.start(QueueName.DynamicSecretRevocation, async (job) => {
await $dynamicSecretQueueJob(job.name, job.id as string, job.data);
}); });
const init = async () => {
await queueService.startPg<QueueName.DynamicSecretRevocation>(
QueueJobs.DynamicSecretRevocation,
async ([job]) => {
await $dynamicSecretQueueJob(job.name, job.id, job.data);
},
{
workerCount: 5,
pollingIntervalSeconds: 1
}
);
await queueService.startPg<QueueName.DynamicSecretRevocation>(
QueueJobs.DynamicSecretPruning,
async ([job]) => {
await $dynamicSecretQueueJob(job.name, job.id, job.data);
},
{
workerCount: 1,
pollingIntervalSeconds: 1
}
);
};
return { return {
pruneDynamicSecret, pruneDynamicSecret,
setLeaseRevocation, setLeaseRevocation,
unsetLeaseRevocation unsetLeaseRevocation,
init
}; };
}; };
@@ -26,12 +26,8 @@ import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
import { TDynamicSecretLeaseQueueServiceFactory } from "./dynamic-secret-lease-queue"; import { TDynamicSecretLeaseQueueServiceFactory } from "./dynamic-secret-lease-queue";
import { import {
DynamicSecretLeaseStatus, DynamicSecretLeaseStatus,
TCreateDynamicSecretLeaseDTO,
TDeleteDynamicSecretLeaseDTO,
TDetailsDynamicSecretLeaseDTO,
TDynamicSecretLeaseConfig, TDynamicSecretLeaseConfig,
TListDynamicSecretLeasesDTO, TDynamicSecretLeaseServiceFactory
TRenewDynamicSecretLeaseDTO
} from "./dynamic-secret-lease-types"; } from "./dynamic-secret-lease-types";
type TDynamicSecretLeaseServiceFactoryDep = { type TDynamicSecretLeaseServiceFactoryDep = {
@@ -48,8 +44,6 @@ type TDynamicSecretLeaseServiceFactoryDep = {
identityDAL: TIdentityDALFactory; identityDAL: TIdentityDALFactory;
}; };
export type TDynamicSecretLeaseServiceFactory = ReturnType<typeof dynamicSecretLeaseServiceFactory>;
export const dynamicSecretLeaseServiceFactory = ({ export const dynamicSecretLeaseServiceFactory = ({
dynamicSecretLeaseDAL, dynamicSecretLeaseDAL,
dynamicSecretProviders, dynamicSecretProviders,
@@ -62,14 +56,14 @@ export const dynamicSecretLeaseServiceFactory = ({
kmsService, kmsService,
userDAL, userDAL,
identityDAL identityDAL
}: TDynamicSecretLeaseServiceFactoryDep) => { }: TDynamicSecretLeaseServiceFactoryDep): TDynamicSecretLeaseServiceFactory => {
const extractEmailUsername = (email: string) => { const extractEmailUsername = (email: string) => {
const regex = new RE2(/^([^@]+)/); const regex = new RE2(/^([^@]+)/);
const match = email.match(regex); const match = email.match(regex);
return match ? match[1] : email; return match ? match[1] : email;
}; };
const create = async ({ const create: TDynamicSecretLeaseServiceFactory["create"] = async ({
environmentSlug, environmentSlug,
path, path,
name, name,
@@ -80,7 +74,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorAuthMethod, actorAuthMethod,
ttl, ttl,
config config
}: TCreateDynamicSecretLeaseDTO) => { }) => {
const appCfg = getConfig(); const appCfg = getConfig();
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -184,11 +178,11 @@ export const dynamicSecretLeaseServiceFactory = ({
config config
}); });
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date())); await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, expireAt);
return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data }; return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data };
}; };
const renewLease = async ({ const renewLease: TDynamicSecretLeaseServiceFactory["renewLease"] = async ({
ttl, ttl,
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
@@ -198,7 +192,7 @@ export const dynamicSecretLeaseServiceFactory = ({
path, path,
environmentSlug, environmentSlug,
leaseId leaseId
}: TRenewDynamicSecretLeaseDTO) => { }) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -278,7 +272,7 @@ export const dynamicSecretLeaseServiceFactory = ({
); );
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id); await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date())); await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, expireAt);
const updatedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, { const updatedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
expireAt, expireAt,
externalEntityId: entityId externalEntityId: entityId
@@ -286,7 +280,7 @@ export const dynamicSecretLeaseServiceFactory = ({
return updatedDynamicSecretLease; return updatedDynamicSecretLease;
}; };
const revokeLease = async ({ const revokeLease: TDynamicSecretLeaseServiceFactory["revokeLease"] = async ({
leaseId, leaseId,
environmentSlug, environmentSlug,
path, path,
@@ -296,7 +290,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId, actorOrgId,
actorAuthMethod, actorAuthMethod,
isForced isForced
}: TDeleteDynamicSecretLeaseDTO) => { }) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -376,7 +370,7 @@ export const dynamicSecretLeaseServiceFactory = ({
return deletedDynamicSecretLease; return deletedDynamicSecretLease;
}; };
const listLeases = async ({ const listLeases: TDynamicSecretLeaseServiceFactory["listLeases"] = async ({
path, path,
name, name,
actor, actor,
@@ -385,7 +379,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId, actorOrgId,
environmentSlug, environmentSlug,
actorAuthMethod actorAuthMethod
}: TListDynamicSecretLeasesDTO) => { }) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -424,7 +418,7 @@ export const dynamicSecretLeaseServiceFactory = ({
return dynamicSecretLeases; return dynamicSecretLeases;
}; };
const getLeaseDetails = async ({ const getLeaseDetails: TDynamicSecretLeaseServiceFactory["getLeaseDetails"] = async ({
projectSlug, projectSlug,
actorOrgId, actorOrgId,
path, path,
@@ -433,7 +427,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
leaseId, leaseId,
actorAuthMethod actorAuthMethod
}: TDetailsDynamicSecretLeaseDTO) => { }) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -1,4 +1,5 @@
import { TProjectPermission } from "@app/lib/types"; import { TDynamicSecretLeases } from "@app/db/schemas";
import { TDynamicSecretWithMetadata, TProjectPermission } from "@app/lib/types";
export enum DynamicSecretLeaseStatus { export enum DynamicSecretLeaseStatus {
FailedDeletion = "Failed to delete" FailedDeletion = "Failed to delete"
@@ -48,3 +49,40 @@ export type TDynamicSecretKubernetesLeaseConfig = {
}; };
export type TDynamicSecretLeaseConfig = TDynamicSecretKubernetesLeaseConfig; export type TDynamicSecretLeaseConfig = TDynamicSecretKubernetesLeaseConfig;
export type TDynamicSecretLeaseServiceFactory = {
create: (arg: TCreateDynamicSecretLeaseDTO) => Promise<{
lease: TDynamicSecretLeases;
dynamicSecret: TDynamicSecretWithMetadata;
data: unknown;
}>;
listLeases: (arg: TListDynamicSecretLeasesDTO) => Promise<TDynamicSecretLeases[]>;
revokeLease: (arg: TDeleteDynamicSecretLeaseDTO) => Promise<TDynamicSecretLeases>;
renewLease: (arg: TRenewDynamicSecretLeaseDTO) => Promise<TDynamicSecretLeases>;
getLeaseDetails: (arg: TDetailsDynamicSecretLeaseDTO) => Promise<{
dynamicSecret: {
id: string;
name: string;
version: number;
type: string;
defaultTTL: string;
maxTTL: string | null | undefined;
encryptedInput: Buffer;
folderId: string;
status: string | null | undefined;
statusDetails: string | null | undefined;
createdAt: Date;
updatedAt: Date;
};
version: number;
id: string;
createdAt: Date;
updatedAt: Date;
externalEntityId: string;
expireAt: Date;
dynamicSecretId: string;
status?: string | null | undefined;
config?: unknown;
statusDetails?: string | null | undefined;
}>;
};
@@ -10,17 +10,35 @@ import {
selectAllTableCols, selectAllTableCols,
sqlNestRelationships, sqlNestRelationships,
TFindFilter, TFindFilter,
TFindOpt TFindOpt,
TOrmify
} from "@app/lib/knex"; } from "@app/lib/knex";
import { OrderByDirection } from "@app/lib/types"; import { OrderByDirection, TDynamicSecretWithMetadata } from "@app/lib/types";
import { SecretsOrderBy } from "@app/services/secret/secret-types"; import { SecretsOrderBy } from "@app/services/secret/secret-types";
export type TDynamicSecretDALFactory = ReturnType<typeof dynamicSecretDALFactory>; export interface TDynamicSecretDALFactory extends Omit<TOrmify<TableName.DynamicSecret>, "findOne"> {
findOne: (filter: TFindFilter<TDynamicSecrets>, tx?: Knex) => Promise<TDynamicSecretWithMetadata>;
listDynamicSecretsByFolderIds: (
arg: {
folderIds: string[];
search?: string | undefined;
limit?: number | undefined;
offset?: number | undefined;
orderBy?: SecretsOrderBy | undefined;
orderDirection?: OrderByDirection | undefined;
},
tx?: Knex
) => Promise<Array<TDynamicSecretWithMetadata & { environment: string }>>;
findWithMetadata: (
filter: TFindFilter<TDynamicSecrets>,
arg?: TFindOpt<TDynamicSecrets>
) => Promise<TDynamicSecretWithMetadata[]>;
}
export const dynamicSecretDALFactory = (db: TDbClient) => { export const dynamicSecretDALFactory = (db: TDbClient): TDynamicSecretDALFactory => {
const orm = ormify(db, TableName.DynamicSecret); const orm = ormify(db, TableName.DynamicSecret);
const findOne = async (filter: TFindFilter<TDynamicSecrets>, tx?: Knex) => { const findOne: TDynamicSecretDALFactory["findOne"] = async (filter, tx) => {
const query = (tx || db.replicaNode())(TableName.DynamicSecret) const query = (tx || db.replicaNode())(TableName.DynamicSecret)
.leftJoin( .leftJoin(
TableName.ResourceMetadata, TableName.ResourceMetadata,
@@ -55,9 +73,9 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
return docs[0]; return docs[0];
}; };
const findWithMetadata = async ( const findWithMetadata: TDynamicSecretDALFactory["findWithMetadata"] = async (
filter: TFindFilter<TDynamicSecrets>, filter,
{ offset, limit, sort, tx }: TFindOpt<TDynamicSecrets> = {} { offset, limit, sort, tx } = {}
) => { ) => {
const query = (tx || db.replicaNode())(TableName.DynamicSecret) const query = (tx || db.replicaNode())(TableName.DynamicSecret)
.leftJoin( .leftJoin(
@@ -101,23 +119,9 @@ export const dynamicSecretDALFactory = (db: TDbClient) => {
}; };
// find dynamic secrets for multiple environments (folder IDs are cross env, thus need to rank for pagination) // find dynamic secrets for multiple environments (folder IDs are cross env, thus need to rank for pagination)
const listDynamicSecretsByFolderIds = async ( const listDynamicSecretsByFolderIds: TDynamicSecretDALFactory["listDynamicSecretsByFolderIds"] = async (
{ { folderIds, search, limit, offset = 0, orderBy = SecretsOrderBy.Name, orderDirection = OrderByDirection.ASC },
folderIds, tx
search,
limit,
offset = 0,
orderBy = SecretsOrderBy.Name,
orderDirection = OrderByDirection.ASC
}: {
folderIds: string[];
search?: string;
limit?: number;
offset?: number;
orderBy?: SecretsOrderBy;
orderDirection?: OrderByDirection;
},
tx?: Knex
) => { ) => {
try { try {
const query = (tx || db.replicaNode())(TableName.DynamicSecret) const query = (tx || db.replicaNode())(TableName.DynamicSecret)
@@ -8,7 +8,7 @@ import {
ProjectPermissionSub ProjectPermissionSub
} from "@app/ee/services/permission/project-permission"; } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrderByDirection, OrgServiceActor } from "@app/lib/types"; import { OrderByDirection } from "@app/lib/types";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -20,17 +20,7 @@ import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/
import { TGatewayDALFactory } from "../gateway/gateway-dal"; import { TGatewayDALFactory } from "../gateway/gateway-dal";
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission"; import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal"; import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
import { import { DynamicSecretStatus, TDynamicSecretServiceFactory } from "./dynamic-secret-types";
DynamicSecretStatus,
TCreateDynamicSecretDTO,
TDeleteDynamicSecretDTO,
TDetailsDynamicSecretDTO,
TGetDynamicSecretsCountDTO,
TListDynamicSecretsByFolderMappingsDTO,
TListDynamicSecretsDTO,
TListDynamicSecretsMultiEnvDTO,
TUpdateDynamicSecretDTO
} from "./dynamic-secret-types";
import { AzureEntraIDProvider } from "./providers/azure-entra-id"; import { AzureEntraIDProvider } from "./providers/azure-entra-id";
import { DynamicSecretProviders, TDynamicProviderFns } from "./providers/models"; import { DynamicSecretProviders, TDynamicProviderFns } from "./providers/models";
@@ -51,8 +41,6 @@ type TDynamicSecretServiceFactoryDep = {
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">; resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TDynamicSecretServiceFactory = ReturnType<typeof dynamicSecretServiceFactory>;
export const dynamicSecretServiceFactory = ({ export const dynamicSecretServiceFactory = ({
dynamicSecretDAL, dynamicSecretDAL,
dynamicSecretLeaseDAL, dynamicSecretLeaseDAL,
@@ -65,8 +53,8 @@ export const dynamicSecretServiceFactory = ({
kmsService, kmsService,
gatewayDAL, gatewayDAL,
resourceMetadataDAL resourceMetadataDAL
}: TDynamicSecretServiceFactoryDep) => { }: TDynamicSecretServiceFactoryDep): TDynamicSecretServiceFactory => {
const create = async ({ const create: TDynamicSecretServiceFactory["create"] = async ({
path, path,
actor, actor,
name, name,
@@ -80,7 +68,7 @@ export const dynamicSecretServiceFactory = ({
actorAuthMethod, actorAuthMethod,
metadata, metadata,
usernameTemplate usernameTemplate
}: TCreateDynamicSecretDTO) => { }) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -188,7 +176,7 @@ export const dynamicSecretServiceFactory = ({
return dynamicSecretCfg; return dynamicSecretCfg;
}; };
const updateByName = async ({ const updateByName: TDynamicSecretServiceFactory["updateByName"] = async ({
name, name,
maxTTL, maxTTL,
defaultTTL, defaultTTL,
@@ -203,7 +191,7 @@ export const dynamicSecretServiceFactory = ({
actorAuthMethod, actorAuthMethod,
metadata, metadata,
usernameTemplate usernameTemplate
}: TUpdateDynamicSecretDTO) => { }) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -345,7 +333,7 @@ export const dynamicSecretServiceFactory = ({
return updatedDynamicCfg; return updatedDynamicCfg;
}; };
const deleteByName = async ({ const deleteByName: TDynamicSecretServiceFactory["deleteByName"] = async ({
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
actorId, actorId,
@@ -355,7 +343,7 @@ export const dynamicSecretServiceFactory = ({
path, path,
environmentSlug, environmentSlug,
isForced isForced
}: TDeleteDynamicSecretDTO) => { }) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -413,7 +401,7 @@ export const dynamicSecretServiceFactory = ({
return deletedDynamicSecretCfg; return deletedDynamicSecretCfg;
}; };
const getDetails = async ({ const getDetails: TDynamicSecretServiceFactory["getDetails"] = async ({
name, name,
projectSlug, projectSlug,
path, path,
@@ -422,7 +410,7 @@ export const dynamicSecretServiceFactory = ({
actorOrgId, actorOrgId,
actorId, actorId,
actor actor
}: TDetailsDynamicSecretDTO) => { }) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` }); if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@@ -480,7 +468,7 @@ export const dynamicSecretServiceFactory = ({
}; };
// get unique dynamic secret count across multiple envs // get unique dynamic secret count across multiple envs
const getCountMultiEnv = async ({ const getCountMultiEnv: TDynamicSecretServiceFactory["getCountMultiEnv"] = async ({
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
actorId, actorId,
@@ -490,7 +478,7 @@ export const dynamicSecretServiceFactory = ({
environmentSlugs, environmentSlugs,
search, search,
isInternal isInternal
}: TListDynamicSecretsMultiEnvDTO) => { }) => {
if (!isInternal) { if (!isInternal) {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
@@ -526,7 +514,7 @@ export const dynamicSecretServiceFactory = ({
}; };
// get dynamic secret count for a single env // get dynamic secret count for a single env
const getDynamicSecretCount = async ({ const getDynamicSecretCount: TDynamicSecretServiceFactory["getDynamicSecretCount"] = async ({
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
actorId, actorId,
@@ -535,7 +523,7 @@ export const dynamicSecretServiceFactory = ({
environmentSlug, environmentSlug,
search, search,
projectId projectId
}: TGetDynamicSecretsCountDTO) => { }) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -561,7 +549,7 @@ export const dynamicSecretServiceFactory = ({
return Number(dynamicSecretCfg[0]?.count ?? 0); return Number(dynamicSecretCfg[0]?.count ?? 0);
}; };
const listDynamicSecretsByEnv = async ({ const listDynamicSecretsByEnv: TDynamicSecretServiceFactory["listDynamicSecretsByEnv"] = async ({
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
actorId, actorId,
@@ -575,7 +563,7 @@ export const dynamicSecretServiceFactory = ({
orderDirection = OrderByDirection.ASC, orderDirection = OrderByDirection.ASC,
search, search,
...params ...params
}: TListDynamicSecretsDTO) => { }) => {
let { projectId } = params; let { projectId } = params;
if (!projectId) { if (!projectId) {
@@ -619,9 +607,9 @@ export const dynamicSecretServiceFactory = ({
}); });
}; };
const listDynamicSecretsByFolderIds = async ( const listDynamicSecretsByFolderIds: TDynamicSecretServiceFactory["listDynamicSecretsByFolderIds"] = async (
{ folderMappings, filters, projectId }: TListDynamicSecretsByFolderMappingsDTO, { folderMappings, filters, projectId },
actor: OrgServiceActor actor
) => { ) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
@@ -657,7 +645,7 @@ export const dynamicSecretServiceFactory = ({
}; };
// get dynamic secrets for multiple envs // get dynamic secrets for multiple envs
const listDynamicSecretsByEnvs = async ({ const listDynamicSecretsByEnvs: TDynamicSecretServiceFactory["listDynamicSecretsByEnvs"] = async ({
actorAuthMethod, actorAuthMethod,
actorOrgId, actorOrgId,
actorId, actorId,
@@ -667,7 +655,7 @@ export const dynamicSecretServiceFactory = ({
projectId, projectId,
isInternal, isInternal,
...params ...params
}: TListDynamicSecretsMultiEnvDTO) => { }) => {
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
@@ -700,14 +688,10 @@ export const dynamicSecretServiceFactory = ({
}); });
}; };
const fetchAzureEntraIdUsers = async ({ const fetchAzureEntraIdUsers: TDynamicSecretServiceFactory["fetchAzureEntraIdUsers"] = async ({
tenantId, tenantId,
applicationId, applicationId,
clientSecret clientSecret
}: {
tenantId: string;
applicationId: string;
clientSecret: string;
}) => { }) => {
const azureEntraIdUsers = await AzureEntraIDProvider().fetchAzureEntraIdUsers( const azureEntraIdUsers = await AzureEntraIDProvider().fetchAzureEntraIdUsers(
tenantId, tenantId,
@@ -1,6 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { OrderByDirection, TProjectPermission } from "@app/lib/types"; import { TDynamicSecrets } from "@app/db/schemas";
import { OrderByDirection, OrgServiceActor, TDynamicSecretWithMetadata, TProjectPermission } from "@app/lib/types";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema"; import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretsOrderBy } from "@app/services/secret/secret-types"; import { SecretsOrderBy } from "@app/services/secret/secret-types";
@@ -83,3 +84,27 @@ export type TListDynamicSecretsMultiEnvDTO = Omit<
export type TGetDynamicSecretsCountDTO = Omit<TListDynamicSecretsDTO, "projectSlug" | "projectId"> & { export type TGetDynamicSecretsCountDTO = Omit<TListDynamicSecretsDTO, "projectSlug" | "projectId"> & {
projectId: string; projectId: string;
}; };
export type TDynamicSecretServiceFactory = {
create: (arg: TCreateDynamicSecretDTO) => Promise<TDynamicSecrets>;
updateByName: (arg: TUpdateDynamicSecretDTO) => Promise<TDynamicSecrets>;
deleteByName: (arg: TDeleteDynamicSecretDTO) => Promise<TDynamicSecrets>;
getDetails: (arg: TDetailsDynamicSecretDTO) => Promise<TDynamicSecretWithMetadata>;
listDynamicSecretsByEnv: (arg: TListDynamicSecretsDTO) => Promise<TDynamicSecretWithMetadata[]>;
listDynamicSecretsByEnvs: (
arg: TListDynamicSecretsMultiEnvDTO
) => Promise<Array<TDynamicSecretWithMetadata & { environment: string }>>;
getDynamicSecretCount: (arg: TGetDynamicSecretsCountDTO) => Promise<number>;
getCountMultiEnv: (arg: TListDynamicSecretsMultiEnvDTO) => Promise<number>;
fetchAzureEntraIdUsers: (arg: { tenantId: string; applicationId: string; clientSecret: string }) => Promise<
{
name: string;
id: string;
email: string;
}[]
>;
listDynamicSecretsByFolderIds: (
arg: TListDynamicSecretsByFolderMappingsDTO,
actor: OrgServiceActor
) => Promise<Array<TDynamicSecretWithMetadata & { environment: string; path: string }>>;
};
+2
View File
@@ -19,3 +19,5 @@ export const getMinExpiresIn = (exp1: string | number, exp2: string | number): s
return ms1 <= ms2 ? exp1 : exp2; return ms1 <= ms2 ? exp1 : exp2;
}; };
export const convertMsToSecond = (time: number) => time / 1000;
+5
View File
@@ -1,3 +1,4 @@
import { TDynamicSecrets } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
export type TGenericPermission = { export type TGenericPermission = {
@@ -84,3 +85,7 @@ export enum QueueWorkerProfile {
Standard = "standard", Standard = "standard",
SecretScanning = "secret-scanning" SecretScanning = "secret-scanning"
} }
export interface TDynamicSecretWithMetadata extends TDynamicSecrets {
metadata: { id: string; key: string; value: string }[];
}
+6
View File
@@ -377,6 +377,7 @@ export type TQueueServiceFactory = {
stopRepeatableJobByKey: <T extends QueueName>(name: T, repeatJobKey: string) => Promise<boolean>; stopRepeatableJobByKey: <T extends QueueName>(name: T, repeatJobKey: string) => Promise<boolean>;
clearQueue: (name: QueueName) => Promise<void>; clearQueue: (name: QueueName) => Promise<void>;
stopJobById: <T extends QueueName>(name: T, jobId: string) => Promise<void | undefined>; stopJobById: <T extends QueueName>(name: T, jobId: string) => Promise<void | undefined>;
stopJobByIdPg: <T extends QueueName>(name: T, jobId: string) => Promise<void | undefined>;
getRepeatableJobs: ( getRepeatableJobs: (
name: QueueName, name: QueueName,
startOffset?: number, startOffset?: number,
@@ -542,6 +543,10 @@ export const queueServiceFactory = (
return q.removeRepeatableByKey(repeatJobKey); return q.removeRepeatableByKey(repeatJobKey);
}; };
const stopJobByIdPg: TQueueServiceFactory["stopJobByIdPg"] = async (name, jobId) => {
await pgBoss.deleteJob(name, jobId);
};
const stopJobById: TQueueServiceFactory["stopJobById"] = async (name, jobId) => { const stopJobById: TQueueServiceFactory["stopJobById"] = async (name, jobId) => {
const q = queueContainer[name]; const q = queueContainer[name];
const job = await q.getJob(jobId); const job = await q.getJob(jobId);
@@ -568,6 +573,7 @@ export const queueServiceFactory = (
stopRepeatableJobByKey, stopRepeatableJobByKey,
clearQueue, clearQueue,
stopJobById, stopJobById,
stopJobByIdPg,
getRepeatableJobs, getRepeatableJobs,
startPg, startPg,
queuePg, queuePg,
+1
View File
@@ -1903,6 +1903,7 @@ export const registerRoutes = async (
await pkiSubscriberQueue.startDailyAutoRenewalJob(); await pkiSubscriberQueue.startDailyAutoRenewalJob();
await kmsService.startService(); await kmsService.startService();
await microsoftTeamsService.start(); await microsoftTeamsService.start();
await dynamicSecretQueueService.init();
// inject all services // inject all services
server.decorate<FastifyZodProvider["services"]>("services", { server.decorate<FastifyZodProvider["services"]>("services", {