mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 15:31:19 +00:00
feat(secret-ref): implemented backend changes for multi env and folder in service token
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Request, Response } from "express";
|
||||
import picomatch from "picomatch";
|
||||
import { ISecret, Secret, ServiceTokenData } from "../../models";
|
||||
import { IAction, SecretVersion } from "../../ee/models";
|
||||
import {
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
SECRET_PERSONAL,
|
||||
SECRET_PERSONAL
|
||||
} from "../../variables";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { EventService } from "../../services";
|
||||
@@ -21,7 +22,7 @@ import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||
import {
|
||||
userHasNoAbility,
|
||||
userHasWorkspaceAccess,
|
||||
userHasWriteOnlyAbility,
|
||||
userHasWriteOnlyAbility
|
||||
} from "../../ee/helpers/checkMembershipPermissions";
|
||||
import Tag from "../../models/tag";
|
||||
import _ from "lodash";
|
||||
@@ -30,7 +31,7 @@ import Folder from "../../models/folder";
|
||||
import {
|
||||
getFolderByPath,
|
||||
getFolderIdFromServiceToken,
|
||||
searchByFolderId,
|
||||
searchByFolderId
|
||||
} from "../../services/FolderService";
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
environment,
|
||||
requests,
|
||||
secretPath,
|
||||
secretPath
|
||||
}: {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
@@ -63,7 +64,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// get secret blind index salt
|
||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
@@ -73,22 +74,22 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
||||
const { scopes: tkScopes } = req.authData.authPayload;
|
||||
const validScope = tkScopes.find(
|
||||
(scope) =>
|
||||
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
|
||||
scope.environment === environment
|
||||
);
|
||||
|
||||
// in service token when not giving secretpath folderid must be root
|
||||
// this is to avoid giving folderid when service tokens are used
|
||||
if (
|
||||
(!secretPath && folderId !== "root") ||
|
||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
||||
) {
|
||||
if ((!secretPath && folderId !== "root") || (secretPath && !validScope)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
if (secretPath) {
|
||||
folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
}
|
||||
|
||||
for await (const request of requests) {
|
||||
@@ -97,12 +98,10 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
let secretBlindIndex = "";
|
||||
switch (request.method) {
|
||||
case "POST":
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt(
|
||||
{
|
||||
secretName: request.secret.secretName,
|
||||
salt,
|
||||
}
|
||||
);
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName: request.secret.secretName,
|
||||
salt
|
||||
});
|
||||
|
||||
createSecrets.push({
|
||||
...request.secret,
|
||||
@@ -113,16 +112,14 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
folder: folderId,
|
||||
secretBlindIndex,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
break;
|
||||
case "PATCH":
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt(
|
||||
{
|
||||
secretName: request.secret.secretName,
|
||||
salt,
|
||||
}
|
||||
);
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName: request.secret.secretName,
|
||||
salt
|
||||
});
|
||||
|
||||
updateSecrets.push({
|
||||
...request.secret,
|
||||
@@ -130,7 +127,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
secretBlindIndex,
|
||||
folder: folderId,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
break;
|
||||
case "DELETE":
|
||||
@@ -150,9 +147,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
...n._doc,
|
||||
_id: new Types.ObjectId(),
|
||||
secret: n._id,
|
||||
isDeleted: false,
|
||||
isDeleted: false
|
||||
};
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
const addAction = (await EELogService.createAction({
|
||||
@@ -161,7 +158,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: createdSecrets.map((n) => n._id),
|
||||
secretIds: createdSecrets.map((n) => n._id)
|
||||
})) as IAction;
|
||||
actions.push(addAction);
|
||||
|
||||
@@ -175,8 +172,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -195,7 +192,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
listedSecretsObj = req.secrets.reduce(
|
||||
(obj: any, secret: ISecret) => ({
|
||||
...obj,
|
||||
[secret._id.toString()]: secret,
|
||||
[secret._id.toString()]: secret
|
||||
}),
|
||||
{}
|
||||
);
|
||||
@@ -204,16 +201,16 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
updateOne: {
|
||||
filter: {
|
||||
_id: new Types.ObjectId(u._id),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
},
|
||||
update: {
|
||||
$inc: {
|
||||
version: 1,
|
||||
version: 1
|
||||
},
|
||||
...u,
|
||||
_id: new Types.ObjectId(u._id),
|
||||
},
|
||||
},
|
||||
_id: new Types.ObjectId(u._id)
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
await Secret.bulkWrite(updateOperations);
|
||||
@@ -240,25 +237,25 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags: u.tags,
|
||||
folder: u.folder,
|
||||
folder: u.folder
|
||||
})
|
||||
);
|
||||
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions,
|
||||
secretVersions
|
||||
});
|
||||
|
||||
updatedSecrets = await Secret.find({
|
||||
_id: {
|
||||
$in: updateSecrets.map((u) => new Types.ObjectId(u._id)),
|
||||
},
|
||||
$in: updateSecrets.map((u) => new Types.ObjectId(u._id))
|
||||
}
|
||||
});
|
||||
|
||||
const updateAction = (await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: updatedSecrets.map((u) => u._id),
|
||||
secretIds: updatedSecrets.map((u) => u._id)
|
||||
})) as IAction;
|
||||
actions.push(updateAction);
|
||||
|
||||
@@ -272,8 +269,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -282,19 +279,19 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
if (deleteSecrets.length > 0) {
|
||||
await Secret.deleteMany({
|
||||
_id: {
|
||||
$in: deleteSecrets,
|
||||
},
|
||||
$in: deleteSecrets
|
||||
}
|
||||
});
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: deleteSecrets,
|
||||
secretIds: deleteSecrets
|
||||
});
|
||||
|
||||
const deleteAction = (await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: deleteSecrets,
|
||||
secretIds: deleteSecrets
|
||||
})) as IAction;
|
||||
actions.push(deleteAction);
|
||||
|
||||
@@ -307,8 +304,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -320,22 +317,22 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions,
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
}
|
||||
|
||||
// // trigger event - push secrets
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
}),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folderId,
|
||||
folderId
|
||||
});
|
||||
|
||||
const resObj: { [key: string]: ISecret[] | string[] } = {};
|
||||
@@ -418,7 +415,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
secretPath
|
||||
}: {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
@@ -435,8 +432,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
);
|
||||
if (!hasAccess) {
|
||||
throw UnauthorizedRequestError({
|
||||
message:
|
||||
"You do not have the necessary permission(s) perform this action",
|
||||
message: "You do not have the necessary permission(s) perform this action"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -449,28 +445,28 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
// case: create 1 secret
|
||||
listOfSecretsToCreate = [req.body.secrets];
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
||||
const { scopes: tkScopes } = req.authData.authPayload;
|
||||
const validScope = tkScopes.find(
|
||||
(scope) =>
|
||||
picomatch.isMatch(secretPath || "/", scope.secretPath, { strictSlashes: false }) &&
|
||||
scope.environment === environment
|
||||
);
|
||||
|
||||
// in service token when not giving secretpath folderid must be root
|
||||
// this is to avoid giving folderid when service tokens are used
|
||||
if (
|
||||
(!secretPath && folderId !== "root") ||
|
||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
||||
) {
|
||||
if ((!secretPath && folderId !== "root") || (secretPath && !validScope)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
if (secretPath) {
|
||||
folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
}
|
||||
|
||||
// get secret blind index salt
|
||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
type secretsToCreateType = {
|
||||
@@ -502,15 +498,14 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags,
|
||||
tags
|
||||
}: secretsToCreateType) => {
|
||||
let secretBlindIndex;
|
||||
if (secretName) {
|
||||
secretBlindIndex =
|
||||
await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName,
|
||||
salt,
|
||||
});
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName,
|
||||
salt
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -532,22 +527,22 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags,
|
||||
tags
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const newlyCreatedSecrets: ISecret[] = (
|
||||
await Secret.insertMany(secretsToInsert)
|
||||
).map((insertedSecret) => insertedSecret.toObject());
|
||||
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map(
|
||||
(insertedSecret) => insertedSecret.toObject()
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
// trigger event - push secrets
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
}),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
@@ -567,7 +562,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueTag
|
||||
}) =>
|
||||
new SecretVersion({
|
||||
secret: _id,
|
||||
@@ -586,9 +581,9 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretValueTag,
|
||||
folder: folderId,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
})
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
const addAction = await EELogService.createAction({
|
||||
@@ -597,7 +592,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: newlyCreatedSecrets.map((n) => n._id),
|
||||
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@@ -609,14 +604,14 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions: [addAction],
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folderId,
|
||||
folderId
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@@ -624,7 +619,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
postHogClient.capture({
|
||||
event: "secrets added",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: listOfSecretsToCreate.length,
|
||||
@@ -632,13 +627,13 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
channel: channel,
|
||||
folderId,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: newlyCreatedSecrets,
|
||||
secrets: newlyCreatedSecrets
|
||||
});
|
||||
};
|
||||
|
||||
@@ -696,10 +691,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
const environment = req.query.environment as string;
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
if (
|
||||
(!folders && folderId && folderId !== "root") ||
|
||||
(!folders && secretPath)
|
||||
) {
|
||||
if ((!folders && folderId && folderId !== "root") || (!folders && secretPath)) {
|
||||
res.send({ secrets: [] });
|
||||
return;
|
||||
}
|
||||
@@ -712,13 +704,17 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
||||
const { scopes: tkScopes } = req.authData.authPayload;
|
||||
const validScope = tkScopes.find(
|
||||
(scope) =>
|
||||
picomatch.isMatch((secretPath as string) || "/", scope.secretPath, {
|
||||
strictSlashes: false
|
||||
}) && scope.environment === environment
|
||||
);
|
||||
|
||||
// in service token when not giving secretpath folderid must be root
|
||||
// this is to avoid giving folderid when service tokens are used
|
||||
if (
|
||||
(!secretPath && folderId !== "root") ||
|
||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
||||
) {
|
||||
if ((!secretPath && folderId !== "root") || (secretPath && !validScope)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
@@ -738,8 +734,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// query tags table to get all tags ids for the tag names for the given workspace
|
||||
let tagIds = [];
|
||||
const tagNamesList =
|
||||
typeof tagSlugs === "string" && tagSlugs !== "" ? tagSlugs.split(",") : [];
|
||||
const tagNamesList = typeof tagSlugs === "string" && tagSlugs !== "" ? tagSlugs.split(",") : [];
|
||||
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
||||
const workspaceFromDB = await Tag.find({ workspace: workspaceId });
|
||||
tagIds = _.map(tagNamesList, (tagName: string) => {
|
||||
@@ -762,8 +757,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
);
|
||||
if (hasNoAccess) {
|
||||
throw UnauthorizedRequestError({
|
||||
message:
|
||||
"You do not have the necessary permission(s) perform this action",
|
||||
message: "You do not have the necessary permission(s) perform this action"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -773,8 +767,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
folder: folderId,
|
||||
$or: [
|
||||
{ user: req.user._id }, // personal secrets for this user
|
||||
{ user: { $exists: false } }, // shared secrets from workspace
|
||||
],
|
||||
{ user: { $exists: false } } // shared secrets from workspace
|
||||
]
|
||||
};
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
@@ -801,8 +795,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
$or: [
|
||||
{ user: userId }, // personal secrets for this user
|
||||
{ user: { $exists: false } }, // shared secrets from workspace
|
||||
],
|
||||
{ user: { $exists: false } } // shared secrets from workspace
|
||||
]
|
||||
};
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
@@ -820,7 +814,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
folder: folderId,
|
||||
user: { $exists: false }, // shared secrets only from workspace
|
||||
user: { $exists: false } // shared secrets only from workspace
|
||||
};
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
@@ -838,7 +832,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
secretIds: secrets.map((n: any) => n._id),
|
||||
secretIds: secrets.map((n: any) => n._id)
|
||||
});
|
||||
|
||||
readAction &&
|
||||
@@ -849,7 +843,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
actions: [readAction],
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@@ -857,7 +851,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
postHogClient.capture({
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
@@ -865,13 +859,13 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
channel,
|
||||
folderId,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets,
|
||||
secrets
|
||||
});
|
||||
};
|
||||
|
||||
@@ -925,9 +919,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
const channel = req.headers?.["user-agent"]?.toLowerCase().includes("mozilla")
|
||||
? "web"
|
||||
: "cli";
|
||||
const channel = req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli";
|
||||
|
||||
interface PatchSecret {
|
||||
id: string;
|
||||
@@ -943,51 +935,47 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
const updateOperationsToPerform = req.body.secrets.map(
|
||||
(secret: PatchSecret) => {
|
||||
const {
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags,
|
||||
} = secret;
|
||||
const updateOperationsToPerform = req.body.secrets.map((secret: PatchSecret) => {
|
||||
const {
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
} = secret;
|
||||
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { _id: new Types.ObjectId(secret.id) },
|
||||
update: {
|
||||
$inc: {
|
||||
version: 1,
|
||||
},
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags,
|
||||
...(secretCommentCiphertext !== undefined &&
|
||||
secretCommentIV &&
|
||||
secretCommentTag
|
||||
? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
}
|
||||
: {}),
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { _id: new Types.ObjectId(secret.id) },
|
||||
update: {
|
||||
$inc: {
|
||||
version: 1
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags,
|
||||
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
|
||||
? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
await Secret.bulkWrite(updateOperationsToPerform);
|
||||
|
||||
@@ -1009,7 +997,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags,
|
||||
tags
|
||||
} = secretModificationsBySecretId[secret._id.toString()];
|
||||
|
||||
return {
|
||||
@@ -1018,9 +1006,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
workspace: secret.workspace,
|
||||
type: secret.type,
|
||||
environment: secret.environment,
|
||||
secretKeyCiphertext: secretKeyCiphertext
|
||||
? secretKeyCiphertext
|
||||
: secret.secretKeyCiphertext,
|
||||
secretKeyCiphertext: secretKeyCiphertext ? secretKeyCiphertext : secret.secretKeyCiphertext,
|
||||
secretKeyIV: secretKeyIV ? secretKeyIV : secret.secretKeyIV,
|
||||
secretKeyTag: secretKeyTag ? secretKeyTag : secret.secretKeyTag,
|
||||
secretValueCiphertext: secretValueCiphertext
|
||||
@@ -1031,17 +1017,13 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext: secretCommentCiphertext
|
||||
? secretCommentCiphertext
|
||||
: secret.secretCommentCiphertext,
|
||||
secretCommentIV: secretCommentIV
|
||||
? secretCommentIV
|
||||
: secret.secretCommentIV,
|
||||
secretCommentTag: secretCommentTag
|
||||
? secretCommentTag
|
||||
: secret.secretCommentTag,
|
||||
secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
|
||||
secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
|
||||
tags: tags ? tags : secret.tags,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
};
|
||||
}),
|
||||
})
|
||||
};
|
||||
|
||||
await EESecretService.addSecretVersions(secretVersions);
|
||||
@@ -1062,8 +1044,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
setTimeout(async () => {
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
}),
|
||||
workspaceId: new Types.ObjectId(key)
|
||||
})
|
||||
});
|
||||
}, 10000);
|
||||
|
||||
@@ -1073,7 +1055,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@@ -1085,7 +1067,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [updateAction],
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
@@ -1101,15 +1083,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
postHogClient.capture({
|
||||
event: "secrets modified",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
workspaceId: key,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1117,9 +1099,9 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
secrets: await Secret.find({
|
||||
_id: {
|
||||
$in: req.secrets.map((secret: ISecret) => secret._id),
|
||||
},
|
||||
}),
|
||||
$in: req.secrets.map((secret: ISecret) => secret._id)
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1179,12 +1161,12 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
await Secret.deleteMany({
|
||||
_id: {
|
||||
$in: toDelete,
|
||||
},
|
||||
$in: toDelete
|
||||
}
|
||||
});
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: toDelete,
|
||||
secretIds: toDelete
|
||||
});
|
||||
|
||||
// group secrets into workspaces so deleted secrets can
|
||||
@@ -1202,8 +1184,8 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
// trigger event - push secrets
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
}),
|
||||
workspaceId: new Types.ObjectId(key)
|
||||
})
|
||||
});
|
||||
const deleteAction = await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
@@ -1211,7 +1193,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@@ -1223,7 +1205,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [deleteAction],
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
@@ -1237,20 +1219,20 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
postHogClient.capture({
|
||||
event: "secrets deleted",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
workspaceId: key,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: req.secrets,
|
||||
secrets: req.secrets
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,10 +2,7 @@ import { Request, Response } from "express";
|
||||
import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import { ServiceAccount, ServiceTokenData, User } from "../../models";
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
} from "../../variables";
|
||||
import { AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT } from "../../variables";
|
||||
import { getSaltRounds } from "../../config";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import Folder from "../../models/folder";
|
||||
@@ -46,14 +43,13 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
||||
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData))
|
||||
throw BadRequestError({
|
||||
message: "Failed accepted client validation for service token data",
|
||||
message: "Failed accepted client validation for service token data"
|
||||
});
|
||||
|
||||
const serviceTokenData = await ServiceTokenData.findById(
|
||||
req.authData.authPayload._id
|
||||
)
|
||||
const serviceTokenData = await ServiceTokenData.findById(req.authData.authPayload._id)
|
||||
.select("+encryptedKey +iv +tag")
|
||||
.populate("user").lean();
|
||||
.populate("user")
|
||||
.lean();
|
||||
|
||||
return res.status(200).json(serviceTokenData);
|
||||
};
|
||||
@@ -68,29 +64,7 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
let serviceTokenData;
|
||||
|
||||
const {
|
||||
name,
|
||||
workspaceId,
|
||||
environment,
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag,
|
||||
expiresIn,
|
||||
secretPath,
|
||||
permissions,
|
||||
} = req.body;
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
});
|
||||
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (folder == undefined) {
|
||||
throw BadRequestError({ message: "Path for service token does not exist" })
|
||||
}
|
||||
}
|
||||
const { name, workspaceId, encryptedKey, iv, tag, expiresIn, permissions, scopes } = req.body;
|
||||
|
||||
const secret = crypto.randomBytes(16).toString("hex");
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
@@ -103,10 +77,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
|
||||
let user, serviceAccount;
|
||||
|
||||
if (
|
||||
req.authData.authMode === AUTH_MODE_JWT &&
|
||||
req.authData.authPayload instanceof User
|
||||
) {
|
||||
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
|
||||
user = req.authData.authPayload._id;
|
||||
}
|
||||
|
||||
@@ -120,17 +91,16 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
serviceTokenData = await new ServiceTokenData({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
user,
|
||||
serviceAccount,
|
||||
scopes,
|
||||
lastUsed: new Date(),
|
||||
expiresAt,
|
||||
secretHash,
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag,
|
||||
secretPath,
|
||||
permissions,
|
||||
permissions
|
||||
}).save();
|
||||
|
||||
// return service token data without sensitive data
|
||||
@@ -142,7 +112,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
|
||||
return res.status(200).send({
|
||||
serviceToken,
|
||||
serviceTokenData,
|
||||
serviceTokenData
|
||||
});
|
||||
};
|
||||
|
||||
@@ -155,11 +125,9 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||
const { serviceTokenDataId } = req.params;
|
||||
|
||||
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(
|
||||
serviceTokenDataId
|
||||
);
|
||||
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
||||
|
||||
return res.status(200).send({
|
||||
serviceTokenData,
|
||||
serviceTokenData
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user