feat: added validation check for secret references made in v2 engine

This commit is contained in:
=
2024-09-23 16:29:01 +05:30
parent 4f5c49a529
commit 7f04e9e97d
3 changed files with 90 additions and 19 deletions
@@ -1,6 +1,7 @@
import path from "node:path"; import path from "node:path";
import { TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas"; import { TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas";
import { UnauthorizedError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
@@ -375,6 +376,7 @@ type TInterpolateSecretArg = {
decryptSecretValue: (encryptedValue?: Buffer | null) => string | undefined; decryptSecretValue: (encryptedValue?: Buffer | null) => string | undefined;
secretDAL: Pick<TSecretV2BridgeDALFactory, "findByFolderId">; secretDAL: Pick<TSecretV2BridgeDALFactory, "findByFolderId">;
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">; folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
canExpandValue: (environment: string, secretPath: string) => boolean;
}; };
const MAX_SECRET_REFERENCE_DEPTH = 10; const MAX_SECRET_REFERENCE_DEPTH = 10;
@@ -382,7 +384,8 @@ export const expandSecretReferencesFactory = ({
projectId, projectId,
decryptSecretValue: decryptSecret, decryptSecretValue: decryptSecret,
secretDAL, secretDAL,
folderDAL folderDAL,
canExpandValue
}: TInterpolateSecretArg) => { }: TInterpolateSecretArg) => {
const secretCache: Record<string, Record<string, string>> = {}; const secretCache: Record<string, Record<string, string>> = {};
const getCacheUniqueKey = (environment: string, secretPath: string) => `${environment}-${secretPath}`; const getCacheUniqueKey = (environment: string, secretPath: string) => `${environment}-${secretPath}`;
@@ -432,6 +435,11 @@ export const expandSecretReferencesFactory = ({
if (entities.length === 1) { if (entities.length === 1) {
const [secretKey] = entities; const [secretKey] = entities;
if (!canExpandValue(environment, secretPath))
throw new UnauthorizedError({
message: `You don't have access to secret ${secretKey} in environment ${environment} of secret path ${secretPath} `
});
// eslint-disable-next-line no-continue,no-await-in-loop // eslint-disable-next-line no-continue,no-await-in-loop
const referedValue = await fetchSecret(environment, secretPath, secretKey); const referedValue = await fetchSecret(environment, secretPath, secretKey);
const cacheKey = getCacheUniqueKey(environment, secretPath); const cacheKey = getCacheUniqueKey(environment, secretPath);
@@ -452,6 +460,11 @@ export const expandSecretReferencesFactory = ({
const secretReferencePath = path.join("/", ...entities.slice(1, entities.length - 1)); const secretReferencePath = path.join("/", ...entities.slice(1, entities.length - 1));
const secretReferenceKey = entities[entities.length - 1]; const secretReferenceKey = entities[entities.length - 1];
if (!canExpandValue(secretReferenceEnvironment, secretReferencePath))
throw new UnauthorizedError({
message: `You don't have access to secret ${secretReferenceKey} in environment ${secretReferenceEnvironment} of secret path ${secretReferencePath} `
});
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const referedValue = await fetchSecret(secretReferenceEnvironment, secretReferencePath, secretReferenceKey); const referedValue = await fetchSecret(secretReferenceEnvironment, secretReferencePath, secretReferenceKey);
const cacheKey = getCacheUniqueKey(secretReferenceEnvironment, secretReferencePath); const cacheKey = getCacheUniqueKey(secretReferenceEnvironment, secretReferencePath);
@@ -152,6 +152,15 @@ export const secretV2BridgeServiceFactory = ({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId projectId
}); });
references.forEach((referredSecret) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment: referredSecret.environment,
secretPath: referredSecret.secretPath
})
);
});
const secret = await secretDAL.transaction((tx) => const secret = await secretDAL.transaction((tx) =>
fnSecretBulkInsert({ fnSecretBulkInsert({
@@ -292,6 +301,17 @@ export const secretV2BridgeServiceFactory = ({
references: getAllNestedSecretReferences(secretValue) references: getAllNestedSecretReferences(secretValue)
} }
: {}; : {};
if (encryptedValue.references) {
encryptedValue.references.forEach((referredSecret) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment: referredSecret.environment,
secretPath: referredSecret.secretPath
})
);
});
}
const updatedSecret = await secretDAL.transaction(async (tx) => const updatedSecret = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({ fnSecretBulkUpdate({
@@ -675,7 +695,12 @@ export const secretV2BridgeServiceFactory = ({
projectId, projectId,
folderDAL, folderDAL,
secretDAL, secretDAL,
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined) decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
canExpandValue: (expandEnvironment, expandSecretPath) =>
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment: expandEnvironment, secretPath: expandSecretPath })
)
}); });
if (shouldExpandSecretReferences) { if (shouldExpandSecretReferences) {
@@ -799,7 +824,12 @@ export const secretV2BridgeServiceFactory = ({
projectId, projectId,
folderDAL, folderDAL,
secretDAL, secretDAL,
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined) decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
canExpandValue: (expandEnvironment, expandSecretPath) =>
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment: expandEnvironment, secretPath: expandSecretPath })
)
}); });
// now if secret is not found // now if secret is not found
@@ -916,21 +946,34 @@ export const secretV2BridgeServiceFactory = ({
const newSecrets = await secretDAL.transaction(async (tx) => const newSecrets = await secretDAL.transaction(async (tx) =>
fnSecretBulkInsert({ fnSecretBulkInsert({
inputSecrets: inputSecrets.map((el) => ({ inputSecrets: inputSecrets.map((el) => {
version: 1, const references = getAllNestedSecretReferences(el.secretValue);
encryptedComment: setKnexStringValue( references.forEach((referredSecret) => {
el.secretComment, ForbiddenError.from(permission).throwUnlessCan(
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob ProjectPermissionActions.Read,
), subject(ProjectPermissionSub.Secrets, {
encryptedValue: el.secretValue environment: referredSecret.environment,
? secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob secretPath: referredSecret.secretPath
: undefined, })
skipMultilineEncoding: el.skipMultilineEncoding, );
key: el.secretKey, });
tagIds: el.tagIds,
references: getAllNestedSecretReferences(el.secretValue), return {
type: SecretType.Shared version: 1,
})), encryptedComment: setKnexStringValue(
el.secretComment,
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
),
encryptedValue: el.secretValue
? secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob
: undefined,
skipMultilineEncoding: el.skipMultilineEncoding,
key: el.secretKey,
tagIds: el.tagIds,
references,
type: SecretType.Shared
};
}),
folderId, folderId,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
@@ -1037,6 +1080,19 @@ export const secretV2BridgeServiceFactory = ({
references: getAllNestedSecretReferences(el.secretValue) references: getAllNestedSecretReferences(el.secretValue)
} }
: {}; : {};
if (encryptedValue.references) {
encryptedValue.references.forEach((referredSecret) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment: referredSecret.environment,
secretPath: referredSecret.secretPath
})
);
});
}
return { return {
filter: { id: originalSecret.id, type: SecretType.Shared }, filter: { id: originalSecret.id, type: SecretType.Shared },
data: { data: {
+3 -1
View File
@@ -290,7 +290,9 @@ export const secretQueueFactory = ({
decryptSecretValue: dto.decryptor, decryptSecretValue: dto.decryptor,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
folderDAL, folderDAL,
projectId: dto.projectId projectId: dto.projectId,
// on integration expand all secrets
canExpandValue: () => true
}); });
// process secrets in current folder // process secrets in current folder
const secrets = await secretV2BridgeDAL.findByFolderId(dto.folderId); const secrets = await secretV2BridgeDAL.findByFolderId(dto.folderId);