mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 18:27:36 +00:00
feat(server): service for dynamic secret lease and queue service for revocation
This commit is contained in:
@@ -59,6 +59,18 @@ export class BadRequestError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class DisableRotationErrors extends Error {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
error: unknown;
|
||||||
|
|
||||||
|
constructor({ name, error, message }: { message: string; name?: string; error?: unknown }) {
|
||||||
|
super(message);
|
||||||
|
this.name = name || "DisableRotationErrors";
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ScimRequestError extends Error {
|
export class ScimRequestError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export enum QueueName {
|
|||||||
SecretWebhook = "secret-webhook",
|
SecretWebhook = "secret-webhook",
|
||||||
SecretFullRepoScan = "secret-full-repo-scan",
|
SecretFullRepoScan = "secret-full-repo-scan",
|
||||||
SecretPushEventScan = "secret-push-event-scan",
|
SecretPushEventScan = "secret-push-event-scan",
|
||||||
UpgradeProjectToGhost = "upgrade-project-to-ghost"
|
UpgradeProjectToGhost = "upgrade-project-to-ghost",
|
||||||
|
DynamicSecretRevocation = "dynamic-secret-revocation"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueJobs {
|
export enum QueueJobs {
|
||||||
@@ -30,7 +31,9 @@ export enum QueueJobs {
|
|||||||
TelemetryInstanceStats = "telemetry-self-hosted-stats",
|
TelemetryInstanceStats = "telemetry-self-hosted-stats",
|
||||||
IntegrationSync = "secret-integration-pull",
|
IntegrationSync = "secret-integration-pull",
|
||||||
SecretScan = "secret-scan",
|
SecretScan = "secret-scan",
|
||||||
UpgradeProjectToGhost = "upgrade-project-to-ghost-job"
|
UpgradeProjectToGhost = "upgrade-project-to-ghost-job",
|
||||||
|
DynamicSecretRevocation = "dynamic-secret-revocation",
|
||||||
|
DynamicSecretPruning = "dynamic-secret-pruning"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TQueueJobTypes = {
|
export type TQueueJobTypes = {
|
||||||
@@ -86,6 +89,19 @@ export type TQueueJobTypes = {
|
|||||||
name: QueueJobs.TelemetryInstanceStats;
|
name: QueueJobs.TelemetryInstanceStats;
|
||||||
payload: undefined;
|
payload: undefined;
|
||||||
};
|
};
|
||||||
|
[QueueName.DynamicSecretRevocation]:
|
||||||
|
| {
|
||||||
|
name: QueueJobs.DynamicSecretRevocation;
|
||||||
|
payload: {
|
||||||
|
leaseId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: QueueJobs.DynamicSecretPruning;
|
||||||
|
payload: {
|
||||||
|
dynamicSecretCfgId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TDynamicSecretLeaseDALFactory = ReturnType<typeof dynamicSecretLeaseDALFactory>;
|
||||||
|
|
||||||
|
export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.DynamicSecretLease);
|
||||||
|
return orm;
|
||||||
|
};
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue";
|
||||||
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
|
||||||
|
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||||
|
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||||
|
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||||
|
|
||||||
|
type TDynamicSecretLeaseQueueServiceFactoryDep = {
|
||||||
|
queueService: TQueueServiceFactory;
|
||||||
|
dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "findById" | "deleteById" | "find">;
|
||||||
|
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findById" | "deleteById">;
|
||||||
|
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDynamicSecretLeaseQueueServiceFactory = ReturnType<typeof dynamicSecretLeaseQueueServiceFactory>;
|
||||||
|
|
||||||
|
export const dynamicSecretLeaseQueueServiceFactory = ({
|
||||||
|
queueService,
|
||||||
|
dynamicSecretDAL,
|
||||||
|
dynamicSecretProviders,
|
||||||
|
dynamicSecretLeaseDAL
|
||||||
|
}: TDynamicSecretLeaseQueueServiceFactoryDep) => {
|
||||||
|
const pruneDynamicSecret = async (dynamicSecretCfgId: string) => {
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.DynamicSecretRevocation,
|
||||||
|
QueueJobs.DynamicSecretPruning,
|
||||||
|
{ dynamicSecretCfgId },
|
||||||
|
{
|
||||||
|
jobId: dynamicSecretCfgId,
|
||||||
|
removeOnFail: {
|
||||||
|
count: 3
|
||||||
|
},
|
||||||
|
removeOnComplete: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLeaseRevocation = async (leaseId: string, expiry: number) => {
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.DynamicSecretRevocation,
|
||||||
|
QueueJobs.DynamicSecretRevocation,
|
||||||
|
{ leaseId },
|
||||||
|
{
|
||||||
|
jobId: leaseId,
|
||||||
|
delay: expiry,
|
||||||
|
removeOnFail: {
|
||||||
|
count: 3
|
||||||
|
},
|
||||||
|
removeOnComplete: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsetLeaseRevocation = async (leaseId: string) => {
|
||||||
|
await queueService.stopJobById(QueueName.DynamicSecretRevocation, leaseId);
|
||||||
|
};
|
||||||
|
|
||||||
|
queueService.start(QueueName.DynamicSecretRevocation, async (job) => {
|
||||||
|
try {
|
||||||
|
if (job.name === QueueJobs.DynamicSecretRevocation) {
|
||||||
|
const { leaseId } = job.data as { leaseId: string };
|
||||||
|
logger.info("Dynamic secret lease revocation started: ", leaseId, job.id);
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
|
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findById(dynamicSecretLease.dynamicSecretId);
|
||||||
|
if (!dynamicSecretCfg) throw new DisableRotationErrors({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId);
|
||||||
|
await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.name === QueueJobs.DynamicSecretPruning) {
|
||||||
|
const { dynamicSecretCfgId } = job.data as { dynamicSecretCfgId: string };
|
||||||
|
logger.info("Dynamic secret pruning started: ", dynamicSecretCfgId, job.id);
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findById(dynamicSecretCfgId);
|
||||||
|
if (!dynamicSecretCfg) throw new DisableRotationErrors({ message: "Dynamic secret not found" });
|
||||||
|
if (!dynamicSecretCfg.isDeleting) throw new DisableRotationErrors({ message: "Document not deleted" });
|
||||||
|
|
||||||
|
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId });
|
||||||
|
if (dynamicSecretLeases.length) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
await Promise.allSettled(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
||||||
|
await Promise.allSettled(
|
||||||
|
dynamicSecretLeases.map(({ externalEntityId }) =>
|
||||||
|
selectedProvider.revoke(decryptedStoredInput, externalEntityId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await dynamicSecretDAL.deleteById(dynamicSecretCfgId);
|
||||||
|
}
|
||||||
|
logger.info("Finished dynamic secret job", job.id);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DisableRotationErrors) {
|
||||||
|
if (job.id) {
|
||||||
|
await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, job.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
queueService.listen(QueueName.AuditLogPrune, "failed", (err) => {
|
||||||
|
logger.error(err, `${QueueName.AuditLogPrune}: log pruning failed`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
pruneDynamicSecret,
|
||||||
|
setLeaseRevocation,
|
||||||
|
unsetLeaseRevocation
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||||
|
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||||
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||||
|
import { TDynamicSecretLeaseQueueServiceFactory } from "./dynamic-secret-lease-queue";
|
||||||
|
import {
|
||||||
|
TCreateDynamicSecretLeaseDTO,
|
||||||
|
TDeleteDynamicSecretLeaseDTO,
|
||||||
|
TListDynamicSecretLeasesDTO,
|
||||||
|
TRenewDynamicSecretLeaseDTO
|
||||||
|
} from "./dynamic-secret-lease-types";
|
||||||
|
|
||||||
|
type TDynamicSecretLeaseServiceFactoryDep = {
|
||||||
|
dynamicSecretLeaseDAL: TDynamicSecretLeaseDALFactory;
|
||||||
|
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findOne">;
|
||||||
|
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||||
|
dynamicSecretQueueService: TDynamicSecretLeaseQueueServiceFactory;
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDynamicSecretLeaseServiceFactory = ReturnType<typeof dynamicSecretLeaseServiceFactory>;
|
||||||
|
|
||||||
|
export const dynamicSecretLeaseServiceFactory = ({
|
||||||
|
dynamicSecretLeaseDAL,
|
||||||
|
dynamicSecretProviders,
|
||||||
|
dynamicSecretDAL,
|
||||||
|
folderDAL,
|
||||||
|
permissionService,
|
||||||
|
dynamicSecretQueueService
|
||||||
|
}: TDynamicSecretLeaseServiceFactoryDep) => {
|
||||||
|
const create = async ({
|
||||||
|
environment,
|
||||||
|
path,
|
||||||
|
slug,
|
||||||
|
projectId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
ttl
|
||||||
|
}: TCreateDynamicSecretLeaseDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ slug, folderId: folder.id });
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
||||||
|
const { maxTTL } = dynamicSecretCfg;
|
||||||
|
const expireAt = new Date(new Date().getTime() + ms(selectedTTL));
|
||||||
|
if (maxTTL) {
|
||||||
|
const maxExpiryDate = new Date(new Date().getTime() + ms(maxTTL));
|
||||||
|
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max TTL" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entityId, data } = await selectedProvider.create(decryptedStoredInput, expireAt.getTime());
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.create({
|
||||||
|
expireAt,
|
||||||
|
version: 1,
|
||||||
|
dynamicSecretId: dynamicSecretCfg.id,
|
||||||
|
externalEntityId: entityId
|
||||||
|
});
|
||||||
|
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date()));
|
||||||
|
return { lease: dynamicSecretLease, data };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renewLease = async ({
|
||||||
|
ttl,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
projectId,
|
||||||
|
path,
|
||||||
|
environment,
|
||||||
|
leaseId
|
||||||
|
}: TRenewDynamicSecretLeaseDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
|
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({
|
||||||
|
id: dynamicSecretLease.dynamicSecretId,
|
||||||
|
folderId: folder.id
|
||||||
|
});
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
||||||
|
const { maxTTL } = dynamicSecretCfg;
|
||||||
|
const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL));
|
||||||
|
if (maxTTL) {
|
||||||
|
const maxExpiryDate = new Date(dynamicSecretLease.createdAt.getTime() + ms(maxTTL));
|
||||||
|
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max ttl" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entityId } = await selectedProvider.renew(
|
||||||
|
decryptedStoredInput,
|
||||||
|
dynamicSecretLease.externalEntityId,
|
||||||
|
expireAt.getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);
|
||||||
|
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(new Date()) - Number(expireAt));
|
||||||
|
const updatedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
||||||
|
expireAt,
|
||||||
|
externalEntityId: entityId
|
||||||
|
});
|
||||||
|
return updatedDynamicSecretLease;
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeLease = async ({
|
||||||
|
leaseId,
|
||||||
|
environment,
|
||||||
|
path,
|
||||||
|
projectId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TDeleteDynamicSecretLeaseDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
|
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({
|
||||||
|
id: dynamicSecretLease.dynamicSecretId,
|
||||||
|
folderId: folder.id
|
||||||
|
});
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId);
|
||||||
|
|
||||||
|
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);
|
||||||
|
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id);
|
||||||
|
return deletedDynamicSecretLease;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listLeases = async ({
|
||||||
|
path,
|
||||||
|
slug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorOrgId,
|
||||||
|
environment,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TListDynamicSecretLeasesDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ slug, folderId: folder.id });
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||||
|
return dynamicSecretLeases;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
create,
|
||||||
|
listLeases,
|
||||||
|
revokeLease,
|
||||||
|
renewLease
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TCreateDynamicSecretLeaseDTO = {
|
||||||
|
slug: string;
|
||||||
|
path: string;
|
||||||
|
environment: string;
|
||||||
|
ttl?: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TListDynamicSecretLeasesDTO = {
|
||||||
|
slug: string;
|
||||||
|
path: string;
|
||||||
|
environment: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TDeleteDynamicSecretLeaseDTO = {
|
||||||
|
leaseId: string;
|
||||||
|
path: string;
|
||||||
|
environment: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TRenewDynamicSecretLeaseDTO = {
|
||||||
|
leaseId: string;
|
||||||
|
path: string;
|
||||||
|
environment: string;
|
||||||
|
ttl?: string;
|
||||||
|
} & TProjectPermission;
|
||||||
Reference in New Issue
Block a user