mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 03:26:27 +00:00
feat: added validation check for secret references made in v2 engine
This commit is contained in:
@@ -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: {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user