mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 05:29:33 +00:00
misc: added audit log and overwrite feature
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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">;
|
||||
|
||||
Reference in New Issue
Block a user