mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 23:23:59 +00:00
feat(api): implemented secret caching version 2
This commit is contained in:
@@ -15,9 +15,13 @@ import {
|
||||
TFindFilter,
|
||||
TFindOpt
|
||||
} from "@app/lib/knex";
|
||||
import { BufferKeysToString, OrderByDirection } from "@app/lib/types";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||
import type { TFindSecretsByFolderIdsFilter } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||
import type {
|
||||
TFindSecretsByFolderIdsFilter,
|
||||
TGetSecretsDTO
|
||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||
import { MongoAbility } from "@casl/ability";
|
||||
|
||||
export const SecretDalCacheKeys = {
|
||||
get productKey() {
|
||||
@@ -27,34 +31,14 @@ export const SecretDalCacheKeys = {
|
||||
getSecretDalVersion: (projectId: string) => {
|
||||
return `${SecretDalCacheKeys.productKey}:${projectId}:${TableName.SecretV2}-dal-version`;
|
||||
},
|
||||
findByFolderIds: (
|
||||
getSecretsOfServiceLayer: (
|
||||
projectId: string,
|
||||
version: number,
|
||||
{ useCache, tx, ...cacheKey }: Parameters<TSecretV2BridgeDALFactory["findByFolderIds"]>[0]
|
||||
dto: TGetSecretsDTO & { permissionRules: MongoAbility["rules"] }
|
||||
) => {
|
||||
return `${SecretDalCacheKeys.productKey}:${projectId}:${
|
||||
TableName.SecretV2
|
||||
}-dal:v${version}:find-by-folder-ids:${generateCacheKeyFromData(cacheKey)}`;
|
||||
},
|
||||
findByFolderId: (
|
||||
projectId: string,
|
||||
version: number,
|
||||
{ useCache, tx, ...cacheKey }: Parameters<TSecretV2BridgeDALFactory["findByFolderId"]>[0]
|
||||
) => {
|
||||
return `${SecretDalCacheKeys.productKey}:${projectId}:${
|
||||
TableName.SecretV2
|
||||
}-dal:v${version}:find-by-folder-id:${generateCacheKeyFromData(cacheKey)}`;
|
||||
},
|
||||
find: (projectId: string, version: number, ...args: Parameters<TSecretV2BridgeDALFactory["find"]>) => {
|
||||
const [filter, opts] = args;
|
||||
delete opts?.tx;
|
||||
delete opts?.useCache;
|
||||
return `${SecretDalCacheKeys.productKey}:${projectId}:${
|
||||
TableName.SecretV2
|
||||
}-dal:v${version}:find:${generateCacheKeyFromData({
|
||||
filter,
|
||||
opts
|
||||
})}`;
|
||||
}-dal:v${version}:get-secrets-service-layer:${dto.actorId}-${generateCacheKeyFromData(dto)}`;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -64,9 +48,9 @@ interface TSecretV2DalArg {
|
||||
keyStore: TKeyStoreFactory;
|
||||
}
|
||||
|
||||
const SECRET_DAL_TTL = 5 * 60;
|
||||
const SECRET_DAL_VERSION_TTL = 15 * 60;
|
||||
const MAX_SECRET_CACHE_BYTES = 25 * 1024 * 1024;
|
||||
export const SECRET_DAL_TTL = 5 * 60;
|
||||
export const SECRET_DAL_VERSION_TTL = 15 * 60;
|
||||
export const MAX_SECRET_CACHE_BYTES = 25 * 1024 * 1024;
|
||||
export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
const secretOrm = ormify(db, TableName.SecretV2);
|
||||
|
||||
@@ -128,35 +112,9 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
}
|
||||
};
|
||||
|
||||
const find = async (
|
||||
filter: TFindFilter<TSecretsV2>,
|
||||
opts: TFindOpt<TSecretsV2> & { useCache?: { projectId: string } } = {}
|
||||
) => {
|
||||
const { offset, limit, sort, tx, useCache } = opts;
|
||||
const find = async (filter: TFindFilter<TSecretsV2>, opts: TFindOpt<TSecretsV2> = {}) => {
|
||||
const { offset, limit, sort, tx } = opts;
|
||||
try {
|
||||
let secretDalVersion = 0;
|
||||
if (useCache) {
|
||||
const cachedSecretDalVersion = await keyStore.getItem(
|
||||
SecretDalCacheKeys.getSecretDalVersion(useCache.projectId)
|
||||
);
|
||||
secretDalVersion = Number(cachedSecretDalVersion || 0);
|
||||
const cacheKey = SecretDalCacheKeys.find(useCache.projectId, secretDalVersion, filter, opts);
|
||||
const cachedSecrets = await keyStore.getItem(cacheKey);
|
||||
if (cachedSecrets) {
|
||||
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
|
||||
|
||||
const unsanitizedSecrets = JSON.parse(cachedSecrets) as BufferKeysToString<(typeof data)[number]>[];
|
||||
const sanitizedSecrets = unsanitizedSecrets.map((el) => {
|
||||
const encryptedValue = el.encryptedValue ? Buffer.from(el.encryptedValue, "base64") : null;
|
||||
const encryptedComment = el.encryptedComment ? Buffer.from(el.encryptedComment, "base64") : null;
|
||||
const createdAt = new Date(el.createdAt);
|
||||
const updatedAt = new Date(el.updatedAt);
|
||||
return { ...el, encryptedComment, encryptedValue, createdAt, updatedAt };
|
||||
});
|
||||
return sanitizedSecrets;
|
||||
}
|
||||
}
|
||||
|
||||
const query = (tx || db)(TableName.SecretV2)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter(filter))
|
||||
@@ -225,22 +183,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
]
|
||||
});
|
||||
|
||||
if (useCache) {
|
||||
const cachedSecrets = data.map((el) => {
|
||||
const encryptedValue = el.encryptedValue ? el.encryptedValue.toString("base64") : null;
|
||||
const encryptedComment = el.encryptedComment ? el.encryptedComment.toString("base64") : null;
|
||||
return { ...el, encryptedValue, encryptedComment };
|
||||
});
|
||||
const cache = JSON.stringify(cachedSecrets);
|
||||
if (Buffer.byteLength(cache, "utf8") < MAX_SECRET_CACHE_BYTES) {
|
||||
await keyStore.setItemWithExpiry(
|
||||
SecretDalCacheKeys.find(useCache.projectId, secretDalVersion, filter, opts),
|
||||
SECRET_DAL_TTL,
|
||||
cache
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: `${TableName.SecretV2}: Find` });
|
||||
@@ -345,15 +287,9 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findByFolderId = async (dto: {
|
||||
folderId: string;
|
||||
userId?: string;
|
||||
tx?: Knex;
|
||||
projectId: string;
|
||||
useCache?: boolean;
|
||||
}) => {
|
||||
const findByFolderId = async (dto: { folderId: string; userId?: string; tx?: Knex; useCache?: boolean }) => {
|
||||
try {
|
||||
const { folderId, tx, projectId } = dto;
|
||||
const { folderId, tx } = dto;
|
||||
let { userId } = dto;
|
||||
// check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo
|
||||
if (userId && !uuidValidate(userId)) {
|
||||
@@ -361,27 +297,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
userId = undefined;
|
||||
}
|
||||
|
||||
const cachedSecretDalVersion = await keyStore.getItem(SecretDalCacheKeys.getSecretDalVersion(projectId));
|
||||
const secretDalVersion = Number(cachedSecretDalVersion || 0);
|
||||
|
||||
if (dto.useCache) {
|
||||
const cacheKey = SecretDalCacheKeys.findByFolderId(projectId, secretDalVersion, dto);
|
||||
const cachedSecrets = await keyStore.getItem(cacheKey);
|
||||
if (cachedSecrets) {
|
||||
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
|
||||
|
||||
const unsanitizedSecrets = JSON.parse(cachedSecrets) as BufferKeysToString<(typeof data)[number]>[];
|
||||
const sanitizedSecrets = unsanitizedSecrets.map((el) => {
|
||||
const encryptedValue = el.encryptedValue ? Buffer.from(el.encryptedValue, "base64") : null;
|
||||
const encryptedComment = el.encryptedComment ? Buffer.from(el.encryptedComment, "base64") : null;
|
||||
const createdAt = new Date(el.createdAt);
|
||||
const updatedAt = new Date(el.updatedAt);
|
||||
return { ...el, encryptedComment, encryptedValue, createdAt, updatedAt };
|
||||
});
|
||||
return sanitizedSecrets;
|
||||
}
|
||||
}
|
||||
|
||||
const secs = await (tx || db.replicaNode())(TableName.SecretV2)
|
||||
.where({ folderId })
|
||||
.where((bd) => {
|
||||
@@ -437,22 +352,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
}
|
||||
]
|
||||
});
|
||||
if (dto.useCache) {
|
||||
const newCachedSecrets = data.map((el) => {
|
||||
const encryptedValue = el.encryptedValue ? el.encryptedValue.toString("base64") : null;
|
||||
const encryptedComment = el.encryptedComment ? el.encryptedComment.toString("base64") : null;
|
||||
return { ...el, encryptedValue, encryptedComment };
|
||||
});
|
||||
const cache = JSON.stringify(newCachedSecrets);
|
||||
|
||||
if (Buffer.byteLength(cache, "utf8") < MAX_SECRET_CACHE_BYTES) {
|
||||
await keyStore.setItemWithExpiry(
|
||||
SecretDalCacheKeys.findByFolderId(projectId, secretDalVersion, dto),
|
||||
SECRET_DAL_TTL,
|
||||
cache
|
||||
);
|
||||
}
|
||||
}
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "get all secret" });
|
||||
@@ -542,11 +441,9 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
folderIds: string[];
|
||||
userId?: string;
|
||||
tx?: Knex;
|
||||
projectId: string;
|
||||
filters?: TFindSecretsByFolderIdsFilter;
|
||||
useCache?: boolean;
|
||||
}) => {
|
||||
const { folderIds, tx, filters, useCache, projectId } = dto;
|
||||
const { folderIds, tx, filters } = dto;
|
||||
let { userId } = dto;
|
||||
try {
|
||||
// check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo)
|
||||
@@ -555,26 +452,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
userId = undefined;
|
||||
}
|
||||
|
||||
const cachedSecretDalVersion = await keyStore.getItem(SecretDalCacheKeys.getSecretDalVersion(projectId));
|
||||
const secretDalVersion = Number(cachedSecretDalVersion || 0);
|
||||
if (useCache) {
|
||||
const cacheKey = SecretDalCacheKeys.findByFolderIds(projectId, secretDalVersion, dto);
|
||||
const cachedSecrets = await keyStore.getItem(cacheKey);
|
||||
if (cachedSecrets) {
|
||||
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
|
||||
|
||||
const unsanitizedSecrets = JSON.parse(cachedSecrets) as BufferKeysToString<(typeof data)[number]>[];
|
||||
const sanitizedSecrets = unsanitizedSecrets.map((el) => {
|
||||
const encryptedValue = el.encryptedValue ? Buffer.from(el.encryptedValue, "base64") : null;
|
||||
const encryptedComment = el.encryptedComment ? Buffer.from(el.encryptedComment, "base64") : null;
|
||||
const createdAt = new Date(el.createdAt);
|
||||
const updatedAt = new Date(el.updatedAt);
|
||||
return { ...el, encryptedComment, encryptedValue, createdAt, updatedAt };
|
||||
});
|
||||
return sanitizedSecrets;
|
||||
}
|
||||
}
|
||||
|
||||
const query = (tx || db.replicaNode())(TableName.SecretV2)
|
||||
.whereIn(`${TableName.SecretV2}.folderId`, folderIds)
|
||||
.where((bd) => {
|
||||
@@ -700,22 +577,6 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
}
|
||||
]
|
||||
});
|
||||
if (useCache) {
|
||||
const cachedSecrets = data.map((el) => {
|
||||
const encryptedValue = el.encryptedValue ? el.encryptedValue.toString("base64") : null;
|
||||
const encryptedComment = el.encryptedComment ? el.encryptedComment.toString("base64") : null;
|
||||
return { ...el, encryptedValue, encryptedComment };
|
||||
});
|
||||
const cache = JSON.stringify(cachedSecrets);
|
||||
|
||||
if (Buffer.byteLength(cache, "utf8") < MAX_SECRET_CACHE_BYTES) {
|
||||
await keyStore.setItemWithExpiry(
|
||||
SecretDalCacheKeys.findByFolderIds(projectId, secretDalVersion, dto),
|
||||
SECRET_DAL_TTL,
|
||||
cache
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user