mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 15:41:27 +00:00
feat: secret reference graph for understanding how its pulled
This commit is contained in:
@@ -371,6 +371,13 @@ const formatMultiValueEnv = (val?: string) => {
|
||||
return `"${val.replace(/\n/g, "\\n")}"`;
|
||||
};
|
||||
|
||||
type TSecretReferenceTraceNode = {
|
||||
key: string;
|
||||
value?: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
children: TSecretReferenceTraceNode[];
|
||||
};
|
||||
type TInterpolateSecretArg = {
|
||||
projectId: string;
|
||||
decryptSecretValue: (encryptedValue?: Buffer | null) => string | undefined;
|
||||
@@ -412,14 +419,21 @@ export const expandSecretReferencesFactory = ({
|
||||
return secretCache[cacheKey][secretKey] || { value: "", tags: [] };
|
||||
};
|
||||
|
||||
const recursivelyExpandSecret = async (dto: { value?: string; secretPath: string; environment: string }) => {
|
||||
if (!dto.value) return "";
|
||||
const recursivelyExpandSecret = async (dto: {
|
||||
value?: string;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
shouldStackTrace?: boolean;
|
||||
}) => {
|
||||
const stackTrace = { ...dto, key: "root", children: [] } as TSecretReferenceTraceNode;
|
||||
|
||||
const stack = [{ ...dto, depth: 0 }];
|
||||
if (!dto.value) return { expandedValue: "", stackTrace };
|
||||
const stack = [{ ...dto, depth: 0, trace: stackTrace }];
|
||||
let expandedValue = dto.value;
|
||||
|
||||
while (stack.length) {
|
||||
const { value, secretPath, environment, depth } = stack.pop()!;
|
||||
const { value, secretPath, environment, depth, trace } = stack.pop()!;
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
if (depth > MAX_SECRET_REFERENCE_DEPTH) continue;
|
||||
const refs = value?.match(INTERPOLATION_SYNTAX_REG);
|
||||
@@ -432,6 +446,11 @@ export const expandSecretReferencesFactory = ({
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!entities.length) continue;
|
||||
|
||||
let referredSecretPath = "";
|
||||
let referredSecretKey = "";
|
||||
let referredSecretEnvironmentSlug = "";
|
||||
let referredSecretValue = "";
|
||||
|
||||
if (entities.length === 1) {
|
||||
const [secretKey] = entities;
|
||||
|
||||
@@ -444,17 +463,11 @@ export const expandSecretReferencesFactory = ({
|
||||
|
||||
const cacheKey = getCacheUniqueKey(environment, secretPath);
|
||||
secretCache[cacheKey][secretKey] = referredValue;
|
||||
if (INTERPOLATION_SYNTAX_REG.test(referredValue.value)) {
|
||||
stack.push({
|
||||
value: referredValue.value,
|
||||
secretPath,
|
||||
environment,
|
||||
depth: depth + 1
|
||||
});
|
||||
}
|
||||
if (referredValue) {
|
||||
expandedValue = expandedValue.replaceAll(interpolationSyntax, referredValue.value);
|
||||
}
|
||||
|
||||
referredSecretValue = referredValue.value;
|
||||
referredSecretKey = secretKey;
|
||||
referredSecretPath = secretPath;
|
||||
referredSecretEnvironmentSlug = environment;
|
||||
} else {
|
||||
const secretReferenceEnvironment = entities[0];
|
||||
const secretReferencePath = path.join("/", ...entities.slice(1, entities.length - 1));
|
||||
@@ -469,24 +482,42 @@ export const expandSecretReferencesFactory = ({
|
||||
|
||||
const cacheKey = getCacheUniqueKey(secretReferenceEnvironment, secretReferencePath);
|
||||
secretCache[cacheKey][secretReferenceKey] = referedValue;
|
||||
if (INTERPOLATION_SYNTAX_REG.test(referedValue.value)) {
|
||||
stack.push({
|
||||
value: referedValue.value,
|
||||
secretPath: secretReferencePath,
|
||||
environment: secretReferenceEnvironment,
|
||||
depth: depth + 1
|
||||
});
|
||||
}
|
||||
|
||||
if (referedValue) {
|
||||
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue.value);
|
||||
referredSecretValue = referedValue.value;
|
||||
referredSecretKey = secretReferenceKey;
|
||||
referredSecretPath = secretReferencePath;
|
||||
referredSecretEnvironmentSlug = secretReferenceEnvironment;
|
||||
}
|
||||
|
||||
const node = {
|
||||
value: referredSecretValue,
|
||||
secretPath: referredSecretPath,
|
||||
environment: referredSecretEnvironmentSlug,
|
||||
depth: depth + 1,
|
||||
trace
|
||||
};
|
||||
|
||||
const shouldExpandMore = INTERPOLATION_SYNTAX_REG.test(referredSecretValue);
|
||||
if (dto.shouldStackTrace) {
|
||||
const stackTraceNode = { ...node, children: [], key: referredSecretKey, trace: null };
|
||||
trace?.children.push(stackTraceNode);
|
||||
// if stack trace this would be child node
|
||||
if (shouldExpandMore) {
|
||||
stack.push({ ...node, trace: stackTraceNode });
|
||||
}
|
||||
} else if (shouldExpandMore) {
|
||||
// if no stack trace is needed we just keep going with root node
|
||||
stack.push(node);
|
||||
}
|
||||
|
||||
if (referredSecretValue) {
|
||||
expandedValue = expandedValue.replaceAll(interpolationSyntax, referredSecretValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expandedValue;
|
||||
return { expandedValue, stackTrace };
|
||||
};
|
||||
|
||||
const expandSecret = async (inputSecret: {
|
||||
@@ -500,10 +531,21 @@ export const expandSecretReferencesFactory = ({
|
||||
const shouldExpand = Boolean(inputSecret.value?.match(INTERPOLATION_SYNTAX_REG));
|
||||
if (!shouldExpand) return inputSecret.value;
|
||||
|
||||
const expandedSecretValue = await recursivelyExpandSecret(inputSecret);
|
||||
return inputSecret.skipMultilineEncoding ? formatMultiValueEnv(expandedSecretValue) : expandedSecretValue;
|
||||
const { expandedValue } = await recursivelyExpandSecret(inputSecret);
|
||||
|
||||
return inputSecret.skipMultilineEncoding ? formatMultiValueEnv(expandedValue) : expandedValue;
|
||||
};
|
||||
return expandSecret;
|
||||
|
||||
const getExpandedSecretStackTrace = async (inputSecret: {
|
||||
value?: string;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
}) => {
|
||||
const { stackTrace } = await recursivelyExpandSecret({ ...inputSecret, shouldStackTrace: true });
|
||||
return stackTrace;
|
||||
};
|
||||
|
||||
return { expandSecretReferences: expandSecret, getExpandedSecretStackTrace };
|
||||
};
|
||||
|
||||
export const reshapeBridgeSecret = (
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
TDeleteManySecretDTO,
|
||||
TDeleteSecretDTO,
|
||||
TGetASecretDTO,
|
||||
TGetSecretReferencesTreeDTO,
|
||||
TGetSecretsDTO,
|
||||
TGetSecretVersionsDTO,
|
||||
TMoveSecretsDTO,
|
||||
@@ -815,7 +816,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
})
|
||||
);
|
||||
|
||||
const expandSecretReferences = expandSecretReferencesFactory({
|
||||
const { expandSecretReferences } = expandSecretReferencesFactory({
|
||||
projectId,
|
||||
folderDAL,
|
||||
secretDAL,
|
||||
@@ -965,7 +966,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
})
|
||||
);
|
||||
|
||||
const expandSecretReferences = expandSecretReferencesFactory({
|
||||
const { expandSecretReferences } = expandSecretReferencesFactory({
|
||||
projectId,
|
||||
folderDAL,
|
||||
secretDAL,
|
||||
@@ -1032,6 +1033,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
value: secretValue,
|
||||
skipMultilineEncoding: secret.skipMultilineEncoding
|
||||
});
|
||||
|
||||
secretValue = expandedSecretValue || "";
|
||||
}
|
||||
|
||||
@@ -1928,6 +1930,77 @@ export const secretV2BridgeServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getSecretReferenceTree = async ({
|
||||
environment,
|
||||
secretPath,
|
||||
projectId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
secretName,
|
||||
actorAuthMethod
|
||||
}: TGetSecretReferencesTreeDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||
if (!folder)
|
||||
throw new NotFoundError({
|
||||
message: "Folder not found for the given environment slug & secret path",
|
||||
name: "Create secret"
|
||||
});
|
||||
const folderId = folder.id;
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
const secret = await secretDAL.findOne({
|
||||
folderId,
|
||||
key: secretName,
|
||||
type: SecretType.Shared
|
||||
});
|
||||
|
||||
const secretValue = secret.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||
: "";
|
||||
|
||||
const { getExpandedSecretStackTrace, expandSecretReferences } = expandSecretReferencesFactory({
|
||||
projectId,
|
||||
folderDAL,
|
||||
secretDAL,
|
||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||
canExpandValue: (expandEnvironment, expandSecretPath) =>
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: expandEnvironment, secretPath: expandSecretPath })
|
||||
)
|
||||
});
|
||||
|
||||
const tree = await getExpandedSecretStackTrace({
|
||||
environment,
|
||||
secretPath,
|
||||
value: secretValue
|
||||
});
|
||||
const value = await expandSecretReferences({
|
||||
environment,
|
||||
secretPath,
|
||||
value: secretValue
|
||||
});
|
||||
return { tree, value };
|
||||
};
|
||||
|
||||
return {
|
||||
createSecret,
|
||||
deleteSecret,
|
||||
@@ -1942,6 +2015,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
moveSecrets,
|
||||
getSecretsCount,
|
||||
getSecretsCountMultiEnv,
|
||||
getSecretsMultiEnv
|
||||
getSecretsMultiEnv,
|
||||
getSecretReferenceTree
|
||||
};
|
||||
};
|
||||
|
||||
@@ -278,3 +278,10 @@ export type TAttachSecretTagsDTO = {
|
||||
secretPath: string;
|
||||
type: SecretType;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TGetSecretReferencesTreeDTO = {
|
||||
projectId: string;
|
||||
secretName: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
Reference in New Issue
Block a user