mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(folder-scoped-st): implemented backend api for folder scoped service tokens
This commit is contained in:
@@ -1,29 +1,25 @@
|
||||
import { Request, Response } from 'express';
|
||||
import crypto from 'crypto';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { Request, Response } from "express";
|
||||
import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import { User, ServiceAccount, ServiceTokenData } from "../../models";
|
||||
import { userHasWorkspaceAccess } from "../../ee/helpers/checkMembershipPermissions";
|
||||
import {
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData
|
||||
} from '../../models';
|
||||
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
|
||||
import {
|
||||
PERMISSION_READ_SECRETS,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN
|
||||
} from '../../variables';
|
||||
import { getSaltRounds } from '../../config';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
PERMISSION_READ_SECRETS,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
} from "../../variables";
|
||||
import { getSaltRounds } from "../../config";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
|
||||
/**
|
||||
* Return service token data associated with service token on request
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getServiceTokenData = async (req: Request, res: Response) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.summary = 'Return Infisical Token data'
|
||||
#swagger.description = 'Return Infisical Token data'
|
||||
|
||||
@@ -49,98 +45,110 @@ 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'
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData))
|
||||
throw BadRequestError({
|
||||
message: "Failed accepted client validation for service token data",
|
||||
});
|
||||
|
||||
const serviceTokenData = await ServiceTokenData
|
||||
.findById(req.authData.authPayload._id)
|
||||
.select('+encryptedKey +iv +tag')
|
||||
.populate('user');
|
||||
const serviceTokenData = await ServiceTokenData.findById(
|
||||
req.authData.authPayload._id
|
||||
)
|
||||
.select("+encryptedKey +iv +tag")
|
||||
.populate("user");
|
||||
|
||||
return res.status(200).json(serviceTokenData);
|
||||
}
|
||||
return res.status(200).json(serviceTokenData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new service token data for workspace with id [workspaceId] and
|
||||
* environment [environment].
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
let serviceTokenData;
|
||||
let serviceTokenData;
|
||||
|
||||
const {
|
||||
name,
|
||||
workspaceId,
|
||||
environment,
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag,
|
||||
expiresIn,
|
||||
permissions
|
||||
} = req.body;
|
||||
const {
|
||||
name,
|
||||
workspaceId,
|
||||
environment,
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag,
|
||||
expiresIn,
|
||||
secretPath,
|
||||
permissions,
|
||||
} = req.body;
|
||||
|
||||
const secret = crypto.randomBytes(16).toString('hex');
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
const secret = crypto.randomBytes(16).toString("hex");
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
|
||||
let expiresAt;
|
||||
if (expiresIn) {
|
||||
expiresAt = new Date()
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||
}
|
||||
let expiresAt;
|
||||
if (expiresIn) {
|
||||
expiresAt = new Date();
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||
}
|
||||
|
||||
let user, serviceAccount;
|
||||
|
||||
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
|
||||
user = req.authData.authPayload._id;
|
||||
}
|
||||
let user, serviceAccount;
|
||||
|
||||
if (req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && req.authData.authPayload instanceof ServiceAccount) {
|
||||
serviceAccount = req.authData.authPayload._id;
|
||||
}
|
||||
|
||||
serviceTokenData = await new ServiceTokenData({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
user,
|
||||
serviceAccount,
|
||||
lastUsed: new Date(),
|
||||
expiresAt,
|
||||
secretHash,
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag,
|
||||
permissions
|
||||
}).save();
|
||||
if (
|
||||
req.authData.authMode === AUTH_MODE_JWT &&
|
||||
req.authData.authPayload instanceof User
|
||||
) {
|
||||
user = req.authData.authPayload._id;
|
||||
}
|
||||
|
||||
// return service token data without sensitive data
|
||||
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
|
||||
if (
|
||||
req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
||||
req.authData.authPayload instanceof ServiceAccount
|
||||
) {
|
||||
serviceAccount = req.authData.authPayload._id;
|
||||
}
|
||||
|
||||
if (!serviceTokenData) throw new Error('Failed to find service token data');
|
||||
serviceTokenData = await new ServiceTokenData({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
user,
|
||||
serviceAccount,
|
||||
lastUsed: new Date(),
|
||||
expiresAt,
|
||||
secretHash,
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag,
|
||||
secretPath,
|
||||
permissions,
|
||||
}).save();
|
||||
|
||||
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
|
||||
// return service token data without sensitive data
|
||||
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
|
||||
|
||||
return res.status(200).send({
|
||||
serviceToken,
|
||||
serviceTokenData
|
||||
});
|
||||
}
|
||||
if (!serviceTokenData) throw new Error("Failed to find service token data");
|
||||
|
||||
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
|
||||
|
||||
return res.status(200).send({
|
||||
serviceToken,
|
||||
serviceTokenData,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete service token data with id [serviceTokenDataId].
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||
const { serviceTokenDataId } = req.params;
|
||||
const { serviceTokenDataId } = req.params;
|
||||
|
||||
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
||||
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(
|
||||
serviceTokenDataId
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
serviceTokenData
|
||||
});
|
||||
}
|
||||
return res.status(200).send({
|
||||
serviceTokenData,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,183 +1,184 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
SecretService,
|
||||
TelemetryService,
|
||||
EventService
|
||||
} from '../../services';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
import { getAuthDataPayloadIdObj } from '../../utils/auth';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { SecretService, EventService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
|
||||
/**
|
||||
* Get secrets for workspace with id [workspaceId] and environment
|
||||
* [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getSecrets = async (req: Request, res: Response) => {
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const secretPath = req.query.secretPath as string;
|
||||
|
||||
const secrets = await SecretService.getSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
authData: req.authData
|
||||
});
|
||||
const secrets = await SecretService.getSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secrets
|
||||
});
|
||||
}
|
||||
return res.status(200).send({
|
||||
secrets,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get secret with name [secretName]
|
||||
* @param req
|
||||
* @param res
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getSecretByName = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const type = req.query.type as 'shared' | 'personal' | undefined;
|
||||
const { secretName } = req.params;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const secretPath = req.query.secretPath as string;
|
||||
const type = req.query.type as "shared" | "personal" | undefined;
|
||||
|
||||
const secret = await SecretService.getSecret({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret
|
||||
});
|
||||
}
|
||||
const secret = await SecretService.getSecret({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create secret with name [secretName]
|
||||
* @param req
|
||||
* @param res
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const createSecret = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
} = req.body;
|
||||
|
||||
const secret = await SecretService.createSecret({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
...((secretCommentCiphertext && secretCommentIV && secretCommentTag) ? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
} : {})
|
||||
});
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretPath = "/",
|
||||
} = req.body;
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
})
|
||||
});
|
||||
const secret = await SecretService.createSecret({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretPath,
|
||||
...(secretCommentCiphertext && secretCommentIV && secretCommentTag
|
||||
? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
const secretWithoutBlindIndex = secret.toObject();
|
||||
delete secretWithoutBlindIndex.secretBlindIndex;
|
||||
|
||||
return res.status(200).send({
|
||||
secret: secretWithoutBlindIndex
|
||||
});
|
||||
}
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
});
|
||||
|
||||
const secretWithoutBlindIndex = secret.toObject();
|
||||
delete secretWithoutBlindIndex.secretBlindIndex;
|
||||
|
||||
return res.status(200).send({
|
||||
secret: secretWithoutBlindIndex,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update secret with name [secretName]
|
||||
* @param req
|
||||
* @param res
|
||||
* @param res
|
||||
*/
|
||||
export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
} = req.body;
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretPath = "/",
|
||||
} = req.body;
|
||||
|
||||
const secret = await SecretService.updateSecret({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
})
|
||||
});
|
||||
const secret = await SecretService.updateSecret({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretPath,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret
|
||||
});
|
||||
}
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete secret with name [secretName]
|
||||
* @param req
|
||||
* @param res
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type
|
||||
} = req.body;
|
||||
|
||||
const { secret, secrets } = await SecretService.deleteSecret({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData
|
||||
});
|
||||
const { secretName } = req.params;
|
||||
const { workspaceId, environment, type, secretPath = "/" } = req.body;
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
})
|
||||
});
|
||||
const { secret } = await SecretService.deleteSecret({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretPath,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret
|
||||
});
|
||||
}
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
CreateSecretParams,
|
||||
GetSecretsParams,
|
||||
GetSecretParams,
|
||||
UpdateSecretParams,
|
||||
DeleteSecretParams,
|
||||
} from '../interfaces/services/SecretService';
|
||||
import { Secret, ISecret, SecretBlindIndexData } from '../models';
|
||||
import { SecretVersion } from '../ee/models';
|
||||
} from "../interfaces/services/SecretService";
|
||||
import {
|
||||
Secret,
|
||||
ISecret,
|
||||
SecretBlindIndexData,
|
||||
ServiceTokenData,
|
||||
} from "../models";
|
||||
import { SecretVersion } from "../ee/models";
|
||||
import {
|
||||
BadRequestError,
|
||||
SecretNotFoundError,
|
||||
SecretBlindIndexDataNotFoundError,
|
||||
InternalServerError,
|
||||
} from '../utils/errors';
|
||||
} from "../utils/errors";
|
||||
import {
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED,
|
||||
@@ -24,20 +29,44 @@ import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
} from '../variables';
|
||||
import crypto from 'crypto';
|
||||
import * as argon2 from 'argon2';
|
||||
} from "../variables";
|
||||
import crypto from "crypto";
|
||||
import * as argon2 from "argon2";
|
||||
import {
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
decryptSymmetric128BitHexKeyUTF8,
|
||||
} from '../utils/crypto';
|
||||
import { getEncryptionKey, client, getRootEncryptionKey } from '../config';
|
||||
import { TelemetryService } from '../services';
|
||||
import { EESecretService, EELogService } from '../ee/services';
|
||||
} from "../utils/crypto";
|
||||
import { getEncryptionKey, client, getRootEncryptionKey } from "../config";
|
||||
import { TelemetryService } from "../services";
|
||||
import { EESecretService, EELogService } from "../ee/services";
|
||||
import {
|
||||
getAuthDataPayloadIdObj,
|
||||
getAuthDataPayloadUserObj,
|
||||
} from '../utils/auth';
|
||||
} from "../utils/auth";
|
||||
import Folder from "../models/folder";
|
||||
import { getFolderByPath } from "../services/FolderService";
|
||||
|
||||
export const getFolderIdFromServiceToken = async (
|
||||
workspaceId: Types.ObjectId | string,
|
||||
environment: string,
|
||||
secretPath: string
|
||||
) => {
|
||||
const folders = await Folder.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
});
|
||||
|
||||
if (!folders) {
|
||||
if (secretPath !== "/") throw new Error("Invalid path. Folders not found");
|
||||
} else {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (!folder) {
|
||||
throw new Error("Folder not found");
|
||||
}
|
||||
return folder.id;
|
||||
}
|
||||
return "root";
|
||||
};
|
||||
|
||||
/**
|
||||
* Create secret blind index data containing encrypted blind index [salt]
|
||||
@@ -46,12 +75,12 @@ import {
|
||||
* @param {Types.ObjectId} obj.workspaceId
|
||||
*/
|
||||
export const createSecretBlindIndexDataHelper = async ({
|
||||
workspaceId
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
// initialize random blind index salt for workspace
|
||||
const salt = crypto.randomBytes(16).toString('base64');
|
||||
const salt = crypto.randomBytes(16).toString("base64");
|
||||
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
@@ -99,7 +128,7 @@ export const createSecretBlindIndexDataHelper = async ({
|
||||
* @returns
|
||||
*/
|
||||
export const getSecretBlindIndexSaltHelper = async ({
|
||||
workspaceId
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
@@ -108,7 +137,7 @@ export const getSecretBlindIndexSaltHelper = async ({
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||
workspace: workspaceId,
|
||||
}).select('+algorithm +keyEncoding');
|
||||
}).select("+algorithm +keyEncoding");
|
||||
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
@@ -136,7 +165,7 @@ export const getSecretBlindIndexSaltHelper = async ({
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: 'Failed to obtain workspace salt needed for secret blind indexing',
|
||||
message: "Failed to obtain workspace salt needed for secret blind indexing",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -148,8 +177,8 @@ export const getSecretBlindIndexSaltHelper = async ({
|
||||
* @param {String} obj.salt - base64-salt
|
||||
*/
|
||||
export const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
secretName,
|
||||
salt
|
||||
secretName,
|
||||
salt,
|
||||
}: {
|
||||
secretName: string;
|
||||
salt: string;
|
||||
@@ -158,14 +187,14 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
const secretBlindIndex = (
|
||||
await argon2.hash(secretName, {
|
||||
type: argon2.argon2id,
|
||||
salt: Buffer.from(salt, 'base64'),
|
||||
salt: Buffer.from(salt, "base64"),
|
||||
saltLength: 16, // default 16 bytes
|
||||
memoryCost: 65536, // default pool of 64 MiB per thread.
|
||||
hashLength: 32,
|
||||
parallelism: 1,
|
||||
raw: true,
|
||||
})
|
||||
).toString('base64');
|
||||
).toString("base64");
|
||||
|
||||
return secretBlindIndex;
|
||||
};
|
||||
@@ -178,8 +207,8 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
*/
|
||||
export const generateSecretBlindIndexHelper = async ({
|
||||
secretName,
|
||||
workspaceId
|
||||
secretName,
|
||||
workspaceId,
|
||||
}: {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
@@ -190,7 +219,7 @@ export const generateSecretBlindIndexHelper = async ({
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||
workspace: workspaceId,
|
||||
}).select('+algorithm +keyEncoding');
|
||||
}).select("+algorithm +keyEncoding");
|
||||
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
@@ -231,9 +260,9 @@ export const generateSecretBlindIndexHelper = async ({
|
||||
|
||||
return secretBlindIndex;
|
||||
}
|
||||
|
||||
|
||||
throw InternalServerError({
|
||||
message: 'Failed to generate secret blind index'
|
||||
message: "Failed to generate secret blind index",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -262,23 +291,37 @@ export const createSecretHelper = async ({
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
folderId,
|
||||
secretPath = "/",
|
||||
}: CreateSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
});
|
||||
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
throw new Error("Folder Permission Denied");
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
|
||||
const exists = await Secret.exists({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
folder: folderId,
|
||||
type,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
});
|
||||
|
||||
if (exists)
|
||||
throw BadRequestError({
|
||||
message: 'Failed to create secret that already exists',
|
||||
message: "Failed to create secret that already exists",
|
||||
});
|
||||
|
||||
if (type === SECRET_PERSONAL) {
|
||||
@@ -287,6 +330,7 @@ export const createSecretHelper = async ({
|
||||
|
||||
const exists = await Secret.exists({
|
||||
secretBlindIndex,
|
||||
folder: folderId,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
type: SECRET_SHARED,
|
||||
});
|
||||
@@ -294,7 +338,7 @@ export const createSecretHelper = async ({
|
||||
if (!exists)
|
||||
throw BadRequestError({
|
||||
message:
|
||||
'Failed to create personal secret override for no corresponding shared secret',
|
||||
"Failed to create personal secret override for no corresponding shared secret",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -325,6 +369,7 @@ export const createSecretHelper = async ({
|
||||
version: secret.version,
|
||||
workspace: secret.workspace,
|
||||
type,
|
||||
folder: folderId,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
environment: secret.environment,
|
||||
isDeleted: false,
|
||||
@@ -372,7 +417,7 @@ export const createSecretHelper = async ({
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets added',
|
||||
event: "secrets added",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
}),
|
||||
@@ -380,6 +425,7 @@ export const createSecretHelper = async ({
|
||||
numberOfSecrets: 1,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
@@ -398,16 +444,30 @@ export const createSecretHelper = async ({
|
||||
* @returns
|
||||
*/
|
||||
export const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
authData
|
||||
workspaceId,
|
||||
environment,
|
||||
authData,
|
||||
secretPath = "/",
|
||||
}: GetSecretsParams) => {
|
||||
let secrets: ISecret[] = [];
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
throw new Error("Folder Permission Denied");
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
|
||||
// get personal secrets first
|
||||
secrets = await Secret.find({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
type: SECRET_PERSONAL,
|
||||
...getAuthDataPayloadUserObj(authData),
|
||||
});
|
||||
@@ -417,6 +477,7 @@ export const getSecretsHelper = async ({
|
||||
await Secret.find({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
type: SECRET_SHARED,
|
||||
secretBlindIndex: {
|
||||
$nin: secrets.map((secret) => secret.secretBlindIndex),
|
||||
@@ -445,7 +506,7 @@ export const getSecretsHelper = async ({
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets pulled',
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
}),
|
||||
@@ -453,6 +514,7 @@ export const getSecretsHelper = async ({
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
@@ -478,18 +540,32 @@ export const getSecretHelper = async ({
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
secretPath = "/",
|
||||
}: GetSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
});
|
||||
let secret: ISecret | null = null;
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
throw new Error("Folder Permission Denied");
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
|
||||
// try getting personal secret first (if exists)
|
||||
secret = await Secret.findOne({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
type: type ?? SECRET_PERSONAL,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
});
|
||||
@@ -501,6 +577,7 @@ export const getSecretHelper = async ({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
type: SECRET_SHARED,
|
||||
});
|
||||
}
|
||||
@@ -528,7 +605,7 @@ export const getSecretHelper = async ({
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets pull',
|
||||
event: "secrets pull",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
}),
|
||||
@@ -536,6 +613,7 @@ export const getSecretHelper = async ({
|
||||
numberOfSecrets: 1,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
@@ -568,6 +646,7 @@ export const updateSecretHelper = async ({
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretPath,
|
||||
}: UpdateSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
@@ -575,6 +654,18 @@ export const updateSecretHelper = async ({
|
||||
});
|
||||
|
||||
let secret: ISecret | null = null;
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
throw new Error("Folder Permission Denied");
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
|
||||
if (type === SECRET_SHARED) {
|
||||
// case: update shared secret
|
||||
@@ -583,6 +674,7 @@ export const updateSecretHelper = async ({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
type,
|
||||
},
|
||||
{
|
||||
@@ -604,6 +696,7 @@ export const updateSecretHelper = async ({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
folder: folderId,
|
||||
...getAuthDataPayloadUserObj(authData),
|
||||
},
|
||||
{
|
||||
@@ -624,6 +717,7 @@ export const updateSecretHelper = async ({
|
||||
secret: secret._id,
|
||||
version: secret.version,
|
||||
workspace: secret.workspace,
|
||||
folder: folderId,
|
||||
type,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
environment: secret.environment,
|
||||
@@ -672,7 +766,7 @@ export const updateSecretHelper = async ({
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets modified',
|
||||
event: "secrets modified",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
}),
|
||||
@@ -680,6 +774,7 @@ export const updateSecretHelper = async ({
|
||||
numberOfSecrets: 1,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
@@ -705,12 +800,26 @@ export const deleteSecretHelper = async ({
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
secretPath = "/",
|
||||
}: DeleteSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
});
|
||||
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
throw new Error("Folder Permission Denied");
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
|
||||
let secrets: ISecret[] = [];
|
||||
let secret: ISecret | null = null;
|
||||
|
||||
@@ -719,6 +828,7 @@ export const deleteSecretHelper = async ({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
});
|
||||
|
||||
secret = await Secret.findOneAndDelete({
|
||||
@@ -726,16 +836,19 @@ export const deleteSecretHelper = async ({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
folder: folderId,
|
||||
});
|
||||
|
||||
await Secret.deleteMany({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
});
|
||||
} else {
|
||||
secret = await Secret.findOneAndDelete({
|
||||
secretBlindIndex,
|
||||
folder: folderId,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
@@ -782,7 +895,7 @@ export const deleteSecretHelper = async ({
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
event: "secrets deleted",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
}),
|
||||
@@ -790,6 +903,7 @@ export const deleteSecretHelper = async ({
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
@@ -800,4 +914,4 @@ export const deleteSecretHelper = async ({
|
||||
secrets,
|
||||
secret,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ export interface CreateSecretParams {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
folderId?: string;
|
||||
type: "shared" | "personal";
|
||||
authData: AuthData;
|
||||
secretKeyCiphertext: string;
|
||||
@@ -17,17 +16,20 @@ export interface CreateSecretParams {
|
||||
secretCommentCiphertext?: string;
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
secretPath: string;
|
||||
}
|
||||
|
||||
export interface GetSecretsParams {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
authData: AuthData;
|
||||
}
|
||||
|
||||
export interface GetSecretParams {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
type?: "shared" | "personal";
|
||||
authData: AuthData;
|
||||
@@ -42,7 +44,7 @@ export interface UpdateSecretParams {
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
folderId?: string;
|
||||
secretPath: string;
|
||||
}
|
||||
|
||||
export interface DeleteSecretParams {
|
||||
@@ -51,4 +53,5 @@ export interface DeleteSecretParams {
|
||||
environment: string;
|
||||
type: "shared" | "personal";
|
||||
authData: AuthData;
|
||||
secretPath: string;
|
||||
}
|
||||
|
||||
@@ -1,79 +1,88 @@
|
||||
import { Schema, model, Types, Document } from 'mongoose';
|
||||
import { Schema, model, Types, Document } from "mongoose";
|
||||
|
||||
export interface IServiceTokenData extends Document {
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
user: Types.ObjectId;
|
||||
serviceAccount: Types.ObjectId;
|
||||
lastUsed: Date;
|
||||
expiresAt: Date;
|
||||
secretHash: string;
|
||||
encryptedKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
permissions: string[];
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
user: Types.ObjectId;
|
||||
serviceAccount: Types.ObjectId;
|
||||
lastUsed: Date;
|
||||
expiresAt: Date;
|
||||
secretHash: string;
|
||||
encryptedKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
secretPath: string;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Workspace',
|
||||
required: true
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
serviceAccount: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'ServiceAccount'
|
||||
},
|
||||
lastUsed: {
|
||||
type: Date
|
||||
},
|
||||
expiresAt: {
|
||||
type: Date
|
||||
},
|
||||
secretHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
select: false
|
||||
},
|
||||
encryptedKey: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
iv: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
permissions: {
|
||||
type: [String],
|
||||
enum: ['read', 'write'],
|
||||
default: ['read']
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true,
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
},
|
||||
serviceAccount: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "ServiceAccount",
|
||||
},
|
||||
lastUsed: {
|
||||
type: Date,
|
||||
},
|
||||
expiresAt: {
|
||||
type: Date,
|
||||
},
|
||||
secretHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
select: false,
|
||||
},
|
||||
encryptedKey: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
iv: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
permissions: {
|
||||
type: [String],
|
||||
enum: ["read", "write"],
|
||||
default: ["read"],
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
default: "/",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
const ServiceTokenData = model<IServiceTokenData>(
|
||||
"ServiceTokenData",
|
||||
serviceTokenDataSchema
|
||||
);
|
||||
|
||||
const ServiceTokenData = model<IServiceTokenData>('ServiceTokenData', serviceTokenDataSchema);
|
||||
|
||||
export default ServiceTokenData;
|
||||
|
||||
@@ -1,72 +1,79 @@
|
||||
import express from 'express';
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
requireServiceTokenDataAuth,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
import { param, body } from 'express-validator';
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
requireServiceTokenDataAuth,
|
||||
validateRequest,
|
||||
} from "../../middleware";
|
||||
import { param, body } from "express-validator";
|
||||
import {
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN
|
||||
} from '../../variables';
|
||||
import { serviceTokenDataController } from '../../controllers/v2';
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
} from "../../variables";
|
||||
import { serviceTokenDataController } from "../../controllers/v2";
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN]
|
||||
}),
|
||||
serviceTokenDataController.getServiceTokenData
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN],
|
||||
}),
|
||||
serviceTokenDataController.getServiceTokenData
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body',
|
||||
locationEnvironment: 'body',
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||
}),
|
||||
body('name').exists().isString().trim(),
|
||||
body('workspaceId').exists().isString().trim(),
|
||||
body('environment').exists().isString().trim(),
|
||||
body('encryptedKey').exists().isString().trim(),
|
||||
body('iv').exists().isString().trim(),
|
||||
body('tag').exists().isString().trim(),
|
||||
body('expiresIn').exists().isNumeric(), // measured in ms
|
||||
body('permissions').isArray({ min: 1 }).custom((value: string[]) => {
|
||||
const allowedPermissions = ['read', 'write'];
|
||||
const invalidValues = value.filter((v) => !allowedPermissions.includes(v));
|
||||
if (invalidValues.length > 0) {
|
||||
throw new Error(`permissions contains invalid values: ${invalidValues.join(', ')}`);
|
||||
}
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "body",
|
||||
locationEnvironment: "body",
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
}),
|
||||
body("name").exists().isString().trim(),
|
||||
body("workspaceId").exists().isString().trim(),
|
||||
body("environment").exists().isString().trim(),
|
||||
body("encryptedKey").exists().isString().trim(),
|
||||
body("iv").exists().isString().trim(),
|
||||
body("secretPath").isString().default("/").trim(),
|
||||
body("tag").exists().isString().trim(),
|
||||
body("expiresIn").exists().isNumeric(), // measured in ms
|
||||
body("permissions")
|
||||
.isArray({ min: 1 })
|
||||
.custom((value: string[]) => {
|
||||
const allowedPermissions = ["read", "write"];
|
||||
const invalidValues = value.filter(
|
||||
(v) => !allowedPermissions.includes(v)
|
||||
);
|
||||
if (invalidValues.length > 0) {
|
||||
throw new Error(
|
||||
`permissions contains invalid values: ${invalidValues.join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
return true
|
||||
return true;
|
||||
}),
|
||||
validateRequest,
|
||||
serviceTokenDataController.createServiceTokenData
|
||||
validateRequest,
|
||||
serviceTokenDataController.createServiceTokenData
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:serviceTokenDataId',
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireServiceTokenDataAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
}),
|
||||
param('serviceTokenDataId').exists().trim(),
|
||||
validateRequest,
|
||||
serviceTokenDataController.deleteServiceTokenData
|
||||
"/:serviceTokenDataId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireServiceTokenDataAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
}),
|
||||
param("serviceTokenDataId").exists().trim(),
|
||||
validateRequest,
|
||||
serviceTokenDataController.deleteServiceTokenData
|
||||
);
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
||||
@@ -1,157 +1,162 @@
|
||||
import express from 'express';
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
import { body, param, query } from 'express-validator';
|
||||
import { secretsController } from '../../controllers/v3';
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
validateRequest,
|
||||
} from "../../middleware";
|
||||
import { body, param, query } from "express-validator";
|
||||
import { secretsController } from "../../controllers/v3";
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
SECRET_SHARED,
|
||||
SECRET_PERSONAL,
|
||||
PERMISSION_READ_SECRETS
|
||||
} from '../../variables';
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
SECRET_SHARED,
|
||||
SECRET_PERSONAL,
|
||||
PERMISSION_READ_SECRETS,
|
||||
} from "../../variables";
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
query('workspaceId').exists().isString().trim(),
|
||||
query('environment').exists().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'query',
|
||||
locationEnvironment: 'query',
|
||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.getSecrets
|
||||
"/",
|
||||
query("workspaceId").exists().isString().trim(),
|
||||
query("environment").exists().isString().trim(),
|
||||
query("secretPath").default("/").isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "query",
|
||||
locationEnvironment: "query",
|
||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.getSecrets
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:secretName',
|
||||
body('workspaceId').exists().isString().trim(),
|
||||
body('environment').exists().isString().trim(),
|
||||
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
body('secretKeyCiphertext').exists().isString().trim(),
|
||||
body('secretKeyIV').exists().isString().trim(),
|
||||
body('secretKeyTag').exists().isString().trim(),
|
||||
body('secretValueCiphertext').exists().isString().trim(),
|
||||
body('secretValueIV').exists().isString().trim(),
|
||||
body('secretValueTag').exists().isString().trim(),
|
||||
body('secretCommentCiphertext').optional().isString().trim(),
|
||||
body('secretCommentIV').optional().isString().trim(),
|
||||
body('secretCommentTag').optional().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body',
|
||||
locationEnvironment: 'body',
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.createSecret
|
||||
"/:secretName",
|
||||
body("workspaceId").exists().isString().trim(),
|
||||
body("environment").exists().isString().trim(),
|
||||
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
body("secretKeyCiphertext").exists().isString().trim(),
|
||||
body("secretKeyIV").exists().isString().trim(),
|
||||
body("secretKeyTag").exists().isString().trim(),
|
||||
body("secretValueCiphertext").exists().isString().trim(),
|
||||
body("secretValueIV").exists().isString().trim(),
|
||||
body("secretValueTag").exists().isString().trim(),
|
||||
body("secretCommentCiphertext").optional().isString().trim(),
|
||||
body("secretCommentIV").optional().isString().trim(),
|
||||
body("secretCommentTag").optional().isString().trim(),
|
||||
body("secretPath").default("/").isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "body",
|
||||
locationEnvironment: "body",
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.createSecret
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:secretName',
|
||||
param('secretName').exists().isString().trim(),
|
||||
query('workspaceId').exists().isString().trim(),
|
||||
query('environment').exists().isString().trim(),
|
||||
query('type').optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'query',
|
||||
locationEnvironment: 'query',
|
||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.getSecretByName
|
||||
"/:secretName",
|
||||
param("secretName").exists().isString().trim(),
|
||||
query("workspaceId").exists().isString().trim(),
|
||||
query("environment").exists().isString().trim(),
|
||||
query("secretPath").default("/").isString().trim(),
|
||||
query("type").optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "query",
|
||||
locationEnvironment: "query",
|
||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.getSecretByName
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:secretName',
|
||||
param('secretName').exists().isString().trim(),
|
||||
body('workspaceId').exists().isString().trim(),
|
||||
body('environment').exists().isString().trim(),
|
||||
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
body('secretValueCiphertext').exists().isString().trim(),
|
||||
body('secretValueIV').exists().isString().trim(),
|
||||
body('secretValueTag').exists().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body',
|
||||
locationEnvironment: 'body',
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.updateSecretByName
|
||||
"/:secretName",
|
||||
param("secretName").exists().isString().trim(),
|
||||
body("workspaceId").exists().isString().trim(),
|
||||
body("environment").exists().isString().trim(),
|
||||
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
body("secretValueCiphertext").exists().isString().trim(),
|
||||
body("secretValueIV").exists().isString().trim(),
|
||||
body("secretValueTag").exists().isString().trim(),
|
||||
body("secretPath").default("/").isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "body",
|
||||
locationEnvironment: "body",
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.updateSecretByName
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:secretName',
|
||||
param('secretName').exists().isString().trim(),
|
||||
body('workspaceId').exists().isString().trim(),
|
||||
body('environment').exists().isString().trim(),
|
||||
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body',
|
||||
locationEnvironment: 'body',
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.deleteSecretByName
|
||||
"/:secretName",
|
||||
param("secretName").exists().isString().trim(),
|
||||
body("workspaceId").exists().isString().trim(),
|
||||
body("environment").exists().isString().trim(),
|
||||
body("secretPath").default("/").isString().trim(),
|
||||
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "body",
|
||||
locationEnvironment: "body",
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.deleteSecretByName
|
||||
);
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
|
||||
@@ -174,6 +174,11 @@ export const searchByFolderIdWithDir = (
|
||||
// to get folder of a path given
|
||||
// Like /frontend/folder#1
|
||||
export const getFolderByPath = (folders: TFolderSchema, searchPath: string) => {
|
||||
// corner case when its just / return root
|
||||
if (searchPath === "/") {
|
||||
return folders.id === "root" ? folders : undefined;
|
||||
}
|
||||
|
||||
const path = searchPath.split("/").filter(Boolean);
|
||||
const queue = [folders];
|
||||
let segment: TFolderSchema | undefined;
|
||||
|
||||
@@ -1,171 +1,169 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { Types } from "mongoose";
|
||||
import { ISecret } from "../models";
|
||||
import {
|
||||
ISecret
|
||||
} from '../models';
|
||||
CreateSecretParams,
|
||||
GetSecretsParams,
|
||||
GetSecretParams,
|
||||
UpdateSecretParams,
|
||||
DeleteSecretParams,
|
||||
} from "../interfaces/services/SecretService";
|
||||
import {
|
||||
CreateSecretParams,
|
||||
GetSecretsParams,
|
||||
GetSecretParams,
|
||||
UpdateSecretParams,
|
||||
DeleteSecretParams
|
||||
} from '../interfaces/services/SecretService';
|
||||
import {
|
||||
createSecretBlindIndexDataHelper,
|
||||
getSecretBlindIndexSaltHelper,
|
||||
generateSecretBlindIndexWithSaltHelper,
|
||||
generateSecretBlindIndexHelper,
|
||||
createSecretHelper,
|
||||
getSecretsHelper,
|
||||
getSecretHelper,
|
||||
updateSecretHelper,
|
||||
deleteSecretHelper
|
||||
} from '../helpers/secrets';
|
||||
createSecretBlindIndexDataHelper,
|
||||
getSecretBlindIndexSaltHelper,
|
||||
generateSecretBlindIndexWithSaltHelper,
|
||||
generateSecretBlindIndexHelper,
|
||||
createSecretHelper,
|
||||
getSecretsHelper,
|
||||
getSecretHelper,
|
||||
updateSecretHelper,
|
||||
deleteSecretHelper,
|
||||
} from "../helpers/secrets";
|
||||
|
||||
class SecretService {
|
||||
/**
|
||||
* Create secret blind index data containing encrypted blind index salt
|
||||
* for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Buffer} obj.salt - 16-byte random salt
|
||||
* @param {Types.ObjectId} obj.workspaceId
|
||||
*/
|
||||
static async createSecretBlindIndexData({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await createSecretBlindIndexDataHelper({
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret blind index data containing encrypted blind index salt
|
||||
* for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Buffer} obj.salt - 16-byte random salt
|
||||
* @param {Types.ObjectId} obj.workspaceId
|
||||
*/
|
||||
static async createSecretBlindIndexData({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await createSecretBlindIndexDataHelper({
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get secret blind index salt for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
|
||||
* @returns
|
||||
*/
|
||||
static async getSecretBlindIndexSalt({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await getSecretBlindIndexSaltHelper({
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret blind index salt for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
|
||||
* @returns
|
||||
*/
|
||||
static async getSecretBlindIndexSalt({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await getSecretBlindIndexSaltHelper({
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Generate blind index for secret with name [secretName]
|
||||
* and salt [salt]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.secretName - name of secret to generate blind index for
|
||||
* @param {String} obj.salt - base64-salt
|
||||
*/
|
||||
static async generateSecretBlindIndexWithSalt({
|
||||
secretName,
|
||||
salt,
|
||||
}: {
|
||||
secretName: string;
|
||||
salt: string;
|
||||
}) {
|
||||
return await generateSecretBlindIndexWithSaltHelper({
|
||||
secretName,
|
||||
salt,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate blind index for secret with name [secretName]
|
||||
* and salt [salt]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.secretName - name of secret to generate blind index for
|
||||
* @param {String} obj.salt - base64-salt
|
||||
*/
|
||||
static async generateSecretBlindIndexWithSalt({
|
||||
secretName,
|
||||
salt
|
||||
}: {
|
||||
secretName: string;
|
||||
salt: string;
|
||||
}) {
|
||||
return await generateSecretBlindIndexWithSaltHelper({
|
||||
secretName,
|
||||
salt
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Create and return blind index for secret with
|
||||
* name [secretName] part of workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to generate blind index for
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
*/
|
||||
static async generateSecretBlindIndex({
|
||||
secretName,
|
||||
workspaceId,
|
||||
}: {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return blind index for secret with
|
||||
* name [secretName] part of workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to generate blind index for
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
*/
|
||||
static async generateSecretBlindIndex({
|
||||
secretName,
|
||||
workspaceId,
|
||||
}: {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to create
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to create secret for
|
||||
* @param {String} obj.environment - environment in workspace to create secret for
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async createSecret(createSecretParams: CreateSecretParams) {
|
||||
return await createSecretHelper(createSecretParams);
|
||||
}
|
||||
/**
|
||||
* Create secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to create
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to create secret for
|
||||
* @param {String} obj.environment - environment in workspace to create secret for
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async createSecret(createSecretParams: CreateSecretParams) {
|
||||
return await createSecretHelper(createSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secrets for workspace with id [workspaceId] and environment [environment]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.environment - environment in workspace
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async getSecrets(getSecretsParams: GetSecretsParams) {
|
||||
return await getSecretsHelper(getSecretsParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to get
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async getSecret(getSecretParams: GetSecretParams) {
|
||||
return await getSecretHelper(getSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to update
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {String} obj.secretValueCiphertext - ciphertext of secret value
|
||||
* @param {String} obj.secretValueIV - IV of secret value
|
||||
* @param {String} obj.secretValueTag - tag of secret value
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async updateSecret(updateSecretParams: UpdateSecretParams) {
|
||||
return await updateSecretHelper(updateSecretParams);
|
||||
}
|
||||
/**
|
||||
* Get secrets for workspace with id [workspaceId] and environment [environment]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.environment - environment in workspace
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async getSecrets(getSecretsParams: GetSecretsParams) {
|
||||
return await getSecretsHelper(getSecretsParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to delete
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async deleteSecret(deleteSecretParams: DeleteSecretParams) {
|
||||
return await deleteSecretHelper(deleteSecretParams);
|
||||
}
|
||||
/**
|
||||
* Get secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to get
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async getSecret(getSecretParams: GetSecretParams) {
|
||||
// TODO(akhilmhdh) The one above is diff. Change this to some other name
|
||||
return await getSecretHelper(getSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to update
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {String} obj.secretValueCiphertext - ciphertext of secret value
|
||||
* @param {String} obj.secretValueIV - IV of secret value
|
||||
* @param {String} obj.secretValueTag - tag of secret value
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async updateSecret(updateSecretParams: UpdateSecretParams) {
|
||||
return await updateSecretHelper(updateSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to delete
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async deleteSecret(deleteSecretParams: DeleteSecretParams) {
|
||||
return await deleteSecretHelper(deleteSecretParams);
|
||||
}
|
||||
}
|
||||
|
||||
export default SecretService;
|
||||
export default SecretService;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
import crypto from "crypto";
|
||||
import { Types } from "mongoose";
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
Bot,
|
||||
BackupPrivateKey,
|
||||
IntegrationAuth,
|
||||
ServiceTokenData,
|
||||
} from "../../models";
|
||||
import { generateKeyPair } from "../../utils/crypto";
|
||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
|
||||
@@ -64,7 +66,7 @@ export const backfillSecretVersions = async () => {
|
||||
),
|
||||
});
|
||||
}
|
||||
console.log("Migration: Secret version migration v1 complete")
|
||||
console.log("Migration: Secret version migration v1 complete");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -380,13 +382,15 @@ export const backfillSecretFolders = async () => {
|
||||
});
|
||||
|
||||
const newSnapshots = Object.keys(groupSnapByEnv).map((snapEnv) => {
|
||||
const secretIdsOfEnvGroup = groupSnapByEnv[snapEnv] ? groupSnapByEnv[snapEnv].map(secretVersion => secretVersion._id) : []
|
||||
const secretIdsOfEnvGroup = groupSnapByEnv[snapEnv]
|
||||
? groupSnapByEnv[snapEnv].map((secretVersion) => secretVersion._id)
|
||||
: [];
|
||||
return {
|
||||
...secSnapshot.toObject({ virtuals: false }),
|
||||
_id: new Types.ObjectId(),
|
||||
environment: snapEnv,
|
||||
secretVersions: secretIdsOfEnvGroup,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
await SecretSnapshot.insertMany(newSnapshots);
|
||||
@@ -402,5 +406,22 @@ export const backfillSecretFolders = async () => {
|
||||
.limit(50);
|
||||
}
|
||||
|
||||
console.log("Migration: Folder migration v1 complete")
|
||||
console.log("Migration: Folder migration v1 complete");
|
||||
};
|
||||
|
||||
export const backfillServiceToken = async () => {
|
||||
// Back fill because tags were missing in secret versions
|
||||
await ServiceTokenData.updateMany(
|
||||
{
|
||||
secretPath: {
|
||||
$exists: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
secretPath: "/",
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("Migration: Service token migration v1 complete");
|
||||
};
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { DatabaseService, TelemetryService } from '../../services';
|
||||
import { setTransporter } from '../../helpers/nodemailer';
|
||||
import { EELicenseService } from '../../ee/services';
|
||||
import { initSmtp } from '../../services/smtp';
|
||||
import { createTestUserForDevelopment } from '../addDevelopmentUser';
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { DatabaseService, TelemetryService } from "../../services";
|
||||
import { setTransporter } from "../../helpers/nodemailer";
|
||||
import { EELicenseService } from "../../ee/services";
|
||||
import { initSmtp } from "../../services/smtp";
|
||||
import { createTestUserForDevelopment } from "../addDevelopmentUser";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
import { validateEncryptionKeysConfig } from './validateConfig';
|
||||
import { validateEncryptionKeysConfig } from "./validateConfig";
|
||||
import {
|
||||
backfillSecretVersions,
|
||||
backfillBots,
|
||||
backfillSecretBlindIndexData,
|
||||
backfillEncryptionMetadata,
|
||||
backfillSecretFolders,
|
||||
} from './backfillData';
|
||||
backfillServiceToken,
|
||||
} from "./backfillData";
|
||||
import {
|
||||
reencryptBotPrivateKeys,
|
||||
reencryptSecretBlindIndexDataSalts,
|
||||
} from './reencryptData';
|
||||
} from "./reencryptData";
|
||||
import {
|
||||
getNodeEnv,
|
||||
getMongoURL,
|
||||
getSentryDSN,
|
||||
getClientSecretGoogle,
|
||||
getClientIdGoogle,
|
||||
} from '../../config';
|
||||
import { initializePassport } from '../auth';
|
||||
} from "../../config";
|
||||
import { initializePassport } from "../auth";
|
||||
|
||||
/**
|
||||
* Prepare Infisical upon startup. This includes tasks like:
|
||||
@@ -75,6 +76,7 @@ export const setup = async () => {
|
||||
await backfillSecretBlindIndexData();
|
||||
await backfillEncryptionMetadata();
|
||||
await backfillSecretFolders();
|
||||
await backfillServiceToken();
|
||||
|
||||
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
|
||||
// to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||
@@ -85,7 +87,7 @@ export const setup = async () => {
|
||||
Sentry.init({
|
||||
dsn: await getSentryDSN(),
|
||||
tracesSampleRate: 1.0,
|
||||
debug: (await getNodeEnv()) === 'production' ? false : true,
|
||||
debug: (await getNodeEnv()) === "production" ? false : true,
|
||||
environment: await getNodeEnv(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user