mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 16:40:30 +00:00
Merge branch 'main' into ENG-2661
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
import { Knex } from "knex";
|
||||
import { Tables } from "knex/types/tables";
|
||||
|
||||
import { TableName } from "@app/db/schemas";
|
||||
|
||||
import { DatabaseError } from "../errors";
|
||||
import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
||||
|
||||
@@ -25,28 +27,41 @@ export type TFindFilter<R extends object = object> = Partial<R> & {
|
||||
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
||||
$complex?: TKnexDynamicOperator<R>;
|
||||
};
|
||||
|
||||
export const buildFindFilter =
|
||||
<R extends object = object>({ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>) =>
|
||||
<R extends object = object>(
|
||||
{ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>,
|
||||
tableName?: TableName,
|
||||
excludeKeys?: Array<keyof R>
|
||||
) =>
|
||||
(bd: Knex.QueryBuilder<R, R>) => {
|
||||
void bd.where(filter);
|
||||
const processedFilter = tableName
|
||||
? Object.fromEntries(
|
||||
Object.entries(filter)
|
||||
.filter(([key]) => !excludeKeys || !excludeKeys.includes(key as keyof R))
|
||||
.map(([key, value]) => [`${tableName}.${key}`, value])
|
||||
)
|
||||
: filter;
|
||||
|
||||
void bd.where(processedFilter);
|
||||
if ($in) {
|
||||
Object.entries($in).forEach(([key, val]) => {
|
||||
if (val) {
|
||||
void bd.whereIn(key as never, val as never);
|
||||
void bd.whereIn([`${tableName ? `${tableName}.` : ""}${key}`] as never, val as never);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ($notNull?.length) {
|
||||
$notNull.forEach((key) => {
|
||||
void bd.whereNotNull(key as never);
|
||||
void bd.whereNotNull([`${tableName ? `${tableName}.` : ""}${key as string}`] as never);
|
||||
});
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
Object.entries($search).forEach(([key, val]) => {
|
||||
if (val) {
|
||||
void bd.whereILike(key as never, val as never);
|
||||
void bd.whereILike([`${tableName ? `${tableName}.` : ""}${key}`] as never, val as never);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ export const blockLocalAndPrivateIpAddresses = async (url: string) => {
|
||||
|
||||
const validUrl = new URL(url);
|
||||
const inputHostIps: string[] = [];
|
||||
if (isIPv4(validUrl.host)) {
|
||||
inputHostIps.push(validUrl.host);
|
||||
if (isIPv4(validUrl.hostname)) {
|
||||
inputHostIps.push(validUrl.hostname);
|
||||
} else {
|
||||
if (validUrl.host === "localhost" || validUrl.host === "host.docker.internal") {
|
||||
if (validUrl.hostname === "localhost" || validUrl.hostname === "host.docker.internal") {
|
||||
throw new BadRequestError({ message: "Local IPs not allowed as URL" });
|
||||
}
|
||||
const resolvedIps = await dns.resolve4(validUrl.host);
|
||||
const resolvedIps = await dns.resolve4(validUrl.hostname);
|
||||
inputHostIps.push(...resolvedIps);
|
||||
}
|
||||
const isInternalIp = inputHostIps.some((el) => isPrivateIp(el));
|
||||
|
||||
@@ -1541,6 +1541,7 @@ export const registerRoutes = async (
|
||||
|
||||
const secretSyncService = secretSyncServiceFactory({
|
||||
secretSyncDAL,
|
||||
secretImportDAL,
|
||||
permissionService,
|
||||
appConnectionService,
|
||||
folderDAL,
|
||||
|
||||
@@ -154,7 +154,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
secrets: z
|
||||
.object({
|
||||
secretId: z.string(),
|
||||
referencedSecretKey: z.string()
|
||||
referencedSecretKey: z.string(),
|
||||
referencedSecretEnv: z.string()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
@@ -166,6 +167,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
})
|
||||
.array()
|
||||
.optional(),
|
||||
usedBySecretSyncs: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
destination: z.string(),
|
||||
environment: z.string(),
|
||||
id: z.string(),
|
||||
path: z.string()
|
||||
})
|
||||
.array()
|
||||
.optional(),
|
||||
totalFolderCount: z.number().optional(),
|
||||
totalDynamicSecretCount: z.number().optional(),
|
||||
totalSecretCount: z.number().optional(),
|
||||
@@ -500,6 +511,24 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
}
|
||||
|
||||
const usedBySecretSyncs: { name: string; destination: string; environment: string; id: string; path: string }[] =
|
||||
[];
|
||||
for await (const environment of environments) {
|
||||
const secretSyncs = await server.services.secretSync.listSecretSyncsBySecretPath(
|
||||
{ projectId, secretPath, environment },
|
||||
req.permission
|
||||
);
|
||||
secretSyncs.forEach((sync) => {
|
||||
usedBySecretSyncs.push({
|
||||
name: sync.name,
|
||||
destination: sync.destination,
|
||||
environment,
|
||||
id: sync.id,
|
||||
path: sync.folder?.path || "/"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
folders,
|
||||
dynamicSecrets,
|
||||
@@ -512,6 +541,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
totalSecretCount,
|
||||
totalSecretRotationCount,
|
||||
importedByEnvs,
|
||||
usedBySecretSyncs,
|
||||
totalCount:
|
||||
(totalFolderCount ?? 0) +
|
||||
(totalDynamicSecretCount ?? 0) +
|
||||
@@ -611,6 +641,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
totalFolderCount: z.number().optional(),
|
||||
totalDynamicSecretCount: z.number().optional(),
|
||||
totalSecretCount: z.number().optional(),
|
||||
usedBySecretSyncs: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
destination: z.string(),
|
||||
environment: z.string(),
|
||||
id: z.string(),
|
||||
path: z.string()
|
||||
})
|
||||
.array()
|
||||
.optional(),
|
||||
importedBy: z
|
||||
.object({
|
||||
environment: z.object({
|
||||
@@ -624,7 +664,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
secrets: z
|
||||
.object({
|
||||
secretId: z.string(),
|
||||
referencedSecretKey: z.string()
|
||||
referencedSecretKey: z.string(),
|
||||
referencedSecretEnv: z.string()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
@@ -904,6 +945,18 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
secrets
|
||||
});
|
||||
|
||||
const secretSyncs = await server.services.secretSync.listSecretSyncsBySecretPath(
|
||||
{ projectId, secretPath, environment },
|
||||
req.permission
|
||||
);
|
||||
const usedBySecretSyncs = secretSyncs.map((sync) => ({
|
||||
name: sync.name,
|
||||
destination: sync.destination,
|
||||
environment: sync.environment?.name || environment,
|
||||
id: sync.id,
|
||||
path: sync.folder?.path || "/"
|
||||
}));
|
||||
|
||||
if (secrets?.length || secretRotations?.length) {
|
||||
const secretCount =
|
||||
(secrets?.length ?? 0) +
|
||||
@@ -950,6 +1003,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||
totalSecretCount,
|
||||
totalSecretRotationCount,
|
||||
importedBy,
|
||||
usedBySecretSyncs,
|
||||
totalCount:
|
||||
(totalImportCount ?? 0) +
|
||||
(totalFolderCount ?? 0) +
|
||||
|
||||
@@ -171,6 +171,19 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getFolderImports = async (secretPath: string, environmentId: string, tx?: Knex) => {
|
||||
try {
|
||||
const folderImports = await (tx || db.replicaNode())(TableName.SecretImport)
|
||||
.where({ importPath: secretPath, importEnv: environmentId })
|
||||
.join(TableName.SecretFolder, `${TableName.SecretImport}.folderId`, `${TableName.SecretFolder}.id`)
|
||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||
.select(db.ref("id").withSchema(TableName.SecretFolder).as("folderId"));
|
||||
return folderImports;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "get secret imports" });
|
||||
}
|
||||
};
|
||||
|
||||
const getFolderIsImportedBy = async (
|
||||
secretPath: string,
|
||||
environmentId: string,
|
||||
@@ -203,7 +216,8 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
db.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||
db.ref("id").withSchema(TableName.SecretFolder).as("folderId"),
|
||||
db.ref("secretKey").withSchema(TableName.SecretReferenceV2).as("referencedSecretKey")
|
||||
db.ref("secretKey").withSchema(TableName.SecretReferenceV2).as("referencedSecretKey"),
|
||||
db.ref("environment").withSchema(TableName.SecretReferenceV2).as("referencedSecretEnv")
|
||||
);
|
||||
|
||||
const folderResults = folderImports.map(({ envName, envSlug, folderName, folderId }) => ({
|
||||
@@ -214,13 +228,14 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
}));
|
||||
|
||||
const secretResults = secretReferences.map(
|
||||
({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey }) => ({
|
||||
({ envName, envSlug, secretId, folderName, folderId, referencedSecretKey, referencedSecretEnv }) => ({
|
||||
envName,
|
||||
envSlug,
|
||||
secretId,
|
||||
folderName,
|
||||
folderId,
|
||||
referencedSecretKey
|
||||
referencedSecretKey,
|
||||
referencedSecretEnv
|
||||
})
|
||||
);
|
||||
|
||||
@@ -235,6 +250,7 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
secrets: {
|
||||
secretId: string;
|
||||
referencedSecretKey: string;
|
||||
referencedSecretEnv: string;
|
||||
}[];
|
||||
folderId: string;
|
||||
folderImported: boolean;
|
||||
@@ -264,7 +280,11 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
if ("secretId" in item && item.secretId) {
|
||||
updatedAcc[env].folders[folder].secrets = [
|
||||
...updatedAcc[env].folders[folder].secrets,
|
||||
{ secretId: item.secretId, referencedSecretKey: item.referencedSecretKey }
|
||||
{
|
||||
secretId: item.secretId,
|
||||
referencedSecretKey: item.referencedSecretKey,
|
||||
referencedSecretEnv: item.referencedSecretEnv
|
||||
}
|
||||
];
|
||||
} else {
|
||||
updatedAcc[env].folders[folder].folderImported = true;
|
||||
@@ -309,6 +329,7 @@ export const secretImportDALFactory = (db: TDbClient) => {
|
||||
findLastImportPosition,
|
||||
updateAllPosition,
|
||||
getProjectImportCount,
|
||||
getFolderIsImportedBy
|
||||
getFolderIsImportedBy,
|
||||
getFolderImports
|
||||
};
|
||||
};
|
||||
|
||||
@@ -808,7 +808,7 @@ export const secretImportServiceFactory = ({
|
||||
actorOrgId,
|
||||
secrets
|
||||
}: TGetSecretImportsDTO & {
|
||||
secrets: { secretKey: string; secretValue: string }[] | undefined;
|
||||
secrets: { secretKey: string; secretValue: string; id: string }[] | undefined;
|
||||
}) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@@ -877,7 +877,8 @@ export const secretImportServiceFactory = ({
|
||||
)
|
||||
.map((otherSecret) => ({
|
||||
secretId: secret.secretKey,
|
||||
referencedSecretKey: otherSecret.secretKey
|
||||
referencedSecretKey: otherSecret.secretKey,
|
||||
referencedSecretEnv: environment
|
||||
}));
|
||||
}) || [];
|
||||
if (locallyReferenced.length > 0) {
|
||||
|
||||
@@ -56,11 +56,12 @@ export type FolderResult = {
|
||||
export type SecretResult = {
|
||||
secretId: string;
|
||||
referencedSecretKey: string;
|
||||
referencedSecretEnv: string;
|
||||
} & FolderResult;
|
||||
|
||||
export type FolderInfo = {
|
||||
folderName: string;
|
||||
secrets?: { secretId: string; referencedSecretKey: string }[];
|
||||
secrets?: { secretId: string; referencedSecretKey: string; referencedSecretEnv: string }[];
|
||||
folderId: string;
|
||||
folderImported: boolean;
|
||||
envSlug?: string;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
TDeleteSecretSyncDTO,
|
||||
TFindSecretSyncByIdDTO,
|
||||
TFindSecretSyncByNameDTO,
|
||||
TListSecretSyncsByFolderId,
|
||||
TListSecretSyncsByProjectId,
|
||||
TSecretSync,
|
||||
TTriggerSecretSyncImportSecretsByIdDTO,
|
||||
@@ -31,12 +32,14 @@ import {
|
||||
TUpdateSecretSyncDTO
|
||||
} from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||
import { TSecretSyncDALFactory } from "./secret-sync-dal";
|
||||
import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "./secret-sync-maps";
|
||||
import { TSecretSyncQueueFactory } from "./secret-sync-queue";
|
||||
|
||||
type TSecretSyncServiceFactoryDep = {
|
||||
secretSyncDAL: TSecretSyncDALFactory;
|
||||
secretImportDAL: TSecretImportDALFactory;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||
@@ -53,6 +56,7 @@ export type TSecretSyncServiceFactory = ReturnType<typeof secretSyncServiceFacto
|
||||
export const secretSyncServiceFactory = ({
|
||||
secretSyncDAL,
|
||||
folderDAL,
|
||||
secretImportDAL,
|
||||
permissionService,
|
||||
appConnectionService,
|
||||
projectBotService,
|
||||
@@ -85,6 +89,37 @@ export const secretSyncServiceFactory = ({
|
||||
return secretSyncs as TSecretSync[];
|
||||
};
|
||||
|
||||
const listSecretSyncsBySecretPath = async (
|
||||
{ projectId, secretPath, environment }: TListSecretSyncsByFolderId,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (permission.cannot(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||
if (!folder) return [];
|
||||
|
||||
const folderImports = await secretImportDAL.getFolderImports(secretPath, folder.envId);
|
||||
|
||||
const secretSyncs = await secretSyncDAL.find({
|
||||
$in: {
|
||||
folderId: folderImports.map((folderImport) => folderImport.folderId).concat(folder.id)
|
||||
}
|
||||
});
|
||||
|
||||
return secretSyncs as TSecretSync[];
|
||||
};
|
||||
|
||||
const findSecretSyncById = async ({ destination, syncId }: TFindSecretSyncByIdDTO, actor: OrgServiceActor) => {
|
||||
const secretSync = await secretSyncDAL.findById(syncId);
|
||||
|
||||
@@ -518,6 +553,7 @@ export const secretSyncServiceFactory = ({
|
||||
return {
|
||||
listSecretSyncOptions,
|
||||
listSecretSyncsByProjectId,
|
||||
listSecretSyncsBySecretPath,
|
||||
findSecretSyncById,
|
||||
findSecretSyncByName,
|
||||
createSecretSync,
|
||||
|
||||
@@ -144,6 +144,13 @@ export type TListSecretSyncsByProjectId = {
|
||||
destination?: SecretSync;
|
||||
};
|
||||
|
||||
export type TListSecretSyncsByFolderId = {
|
||||
projectId: string;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
destination?: SecretSync;
|
||||
};
|
||||
|
||||
export type TFindSecretSyncByIdDTO = {
|
||||
syncId: string;
|
||||
destination: SecretSync;
|
||||
|
||||
@@ -64,7 +64,8 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
const findOne = async (filter: Partial<TSecretsV2>, tx?: Knex) => {
|
||||
try {
|
||||
const docs = await (tx || db)(TableName.SecretV2)
|
||||
.where(filter)
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
.where(buildFindFilter(filter, TableName.SecretV2))
|
||||
.leftJoin(
|
||||
TableName.SecretV2JnTag,
|
||||
`${TableName.SecretV2}.id`,
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from "node:path";
|
||||
|
||||
import RE2 from "re2";
|
||||
|
||||
import { TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas";
|
||||
import { SecretType, TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas";
|
||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@@ -720,7 +720,7 @@ export const reshapeBridgeSecret = (
|
||||
secretReminderRecipients: secret.secretReminderRecipients || [],
|
||||
...(secretValueHidden
|
||||
? {
|
||||
secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
||||
secretValue: secret.type === SecretType.Personal ? secret.value : INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
||||
secretValueHidden: true
|
||||
}
|
||||
: {
|
||||
|
||||
Reference in New Issue
Block a user