misc: added audit log and overwrite feature

This commit is contained in:
Sheen Capadngan
2024-07-09 21:46:12 +08:00
parent d20ae39f32
commit 079e005f49
8 changed files with 136 additions and 30 deletions
@@ -45,6 +45,7 @@ export enum EventType {
CREATE_SECRETS = "create-secrets",
UPDATE_SECRET = "update-secret",
UPDATE_SECRETS = "update-secrets",
MOVE_SECRETS = "move-secrets",
DELETE_SECRET = "delete-secret",
DELETE_SECRETS = "delete-secrets",
GET_WORKSPACE_KEY = "get-workspace-key",
@@ -228,6 +229,17 @@ interface UpdateSecretBatchEvent {
};
}
interface MoveSecretsEvent {
type: EventType.MOVE_SECRETS;
metadata: {
sourceEnvironment: string;
sourceSecretPath: string;
destinationEnvironment: string;
destinationSecretPath: string;
secretIds: string[];
};
}
interface DeleteSecretEvent {
type: EventType.DELETE_SECRET;
metadata: {
@@ -1030,6 +1042,7 @@ export type Event =
| CreateSecretBatchEvent
| UpdateSecretEvent
| UpdateSecretBatchEvent
| MoveSecretsEvent
| DeleteSecretEvent
| DeleteSecretBatchEvent
| GetWorkspaceKeyEvent
+27 -9
View File
@@ -1338,27 +1338,45 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
sourceSecretPath: z.string().trim().default("/").transform(removeTrailingSlash),
destinationEnvironment: z.string().trim(),
destinationSecretPath: z.string().trim().default("/").transform(removeTrailingSlash),
secretIds: z.string().array()
secretIds: z.string().array(),
shouldOverwrite: z.boolean().default(false)
}),
response: {
200: z.union([
z.object({
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
}),
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
])
200: z.object({
isSourceUpdated: z.boolean(),
isDestinationUpdated: z.boolean()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
// TODO: publish audit log
return server.services.secret.moveSecrets({
const { projectId, isSourceUpdated, isDestinationUpdated } = await server.services.secret.moveSecrets({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
await server.services.auditLog.createAuditLog({
projectId,
...req.auditLogInfo,
event: {
type: EventType.MOVE_SECRETS,
metadata: {
sourceEnvironment: req.body.sourceEnvironment,
sourceSecretPath: req.body.sourceSecretPath,
destinationEnvironment: req.body.destinationEnvironment,
destinationSecretPath: req.body.destinationSecretPath,
secretIds: req.body.secretIds
}
}
});
return {
isSourceUpdated,
isDestinationUpdated
};
}
});
+26 -10
View File
@@ -1705,6 +1705,7 @@ export const secretServiceFactory = ({
destinationSecretPath,
secretIds,
projectSlug,
shouldOverwrite,
actor,
actorId,
actorAuthMethod,
@@ -1793,8 +1794,8 @@ export const secretServiceFactory = ({
})
}));
let isSourceFolderUpdated = false;
let isDestinationFolderUpdated = false;
let isSourceUpdated = false;
let isDestinationUpdated = false;
// Moving secrets is a two-step process.
await secretDAL.transaction(async (tx) => {
@@ -1831,7 +1832,7 @@ export const secretServiceFactory = ({
const locallyCreatedSecrets = decryptedSourceSecrets
.filter(({ secretBlindIndex }) => !destinationSecretsGroupedByBlindIndex[secretBlindIndex as string]?.[0])
.map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create
.map((el) => ({ ...el, operation: SecretOperations.Create }));
const locallyUpdatedSecrets = decryptedSourceSecrets
.filter(
@@ -1841,16 +1842,25 @@ export const secretServiceFactory = ({
(destinationSecretsGroupedByBlindIndex[secretBlindIndex as string]?.[0]?.secretKey !== secretKey ||
destinationSecretsGroupedByBlindIndex[secretBlindIndex as string]?.[0]?.secretValue !== secretValue)
)
.map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create
.map((el) => ({ ...el, operation: SecretOperations.Update }));
if (locallyUpdatedSecrets.length > 0 && !shouldOverwrite) {
const existingKeys = locallyUpdatedSecrets.map((s) => s.secretKey);
throw new BadRequestError({
message: `Failed to move secrets. The following secrets already exist in the destination: ${existingKeys.join(
","
)}`
});
}
const isEmpty = locallyCreatedSecrets.length + locallyUpdatedSecrets.length === 0;
if (isEmpty) {
throw new BadRequestError({
message: "No changes were made. Secrets already exist in the destination."
message: "Selected secrets already exist in the destination."
});
}
const destinationFolderPolicy = await secretApprovalPolicyService.getSecretApprovalPolicy(
project.id,
destinationFolder.environment.slug,
@@ -1974,7 +1984,7 @@ export const secretServiceFactory = ({
});
}
isDestinationFolderUpdated = true;
isDestinationUpdated = true;
}
// Next step is to delete the secrets from the source folder:
@@ -2042,11 +2052,11 @@ export const secretServiceFactory = ({
tx
);
isSourceFolderUpdated = true;
isSourceUpdated = true;
}
});
if (isDestinationFolderUpdated) {
if (isDestinationUpdated) {
await snapshotService.performSnapshot(destinationFolder.id);
await secretQueueService.syncSecrets({
projectId: project.id,
@@ -2057,7 +2067,7 @@ export const secretServiceFactory = ({
});
}
if (isSourceFolderUpdated) {
if (isSourceUpdated) {
await snapshotService.performSnapshot(sourceFolder.id);
await secretQueueService.syncSecrets({
projectId: project.id,
@@ -2067,6 +2077,12 @@ export const secretServiceFactory = ({
actor
});
}
return {
projectId: project.id,
isSourceUpdated,
isDestinationUpdated
};
};
return {
@@ -405,4 +405,5 @@ export type TMoveSecretsDTO = {
destinationEnvironment: string;
destinationSecretPath: string;
secretIds: string[];
shouldOverwrite: boolean;
} & Omit<TProjectPermission, "projectId">;