feat(infisical-pg): fixed secret rotation and secret approval limit offset

This commit is contained in:
Akhil Mohan
2024-01-17 22:25:34 +05:30
parent 28fdf4ed4b
commit bf059e0fe5
14 changed files with 265 additions and 142 deletions

View File

@@ -103,35 +103,47 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
const findProjectRequestCount = async (projectId: string, membershipId: string, tx?: Knex) => {
try {
const doc = await (tx || db)(TableName.SecretApprovalRequest)
.join(
TableName.SecretFolder,
`${TableName.SecretApprovalRequest}.folderId`,
`${TableName.SecretFolder}.id`
const docs = await (tx || db)
.with(
"temp",
(tx || db)(TableName.SecretApprovalRequest)
.join(
TableName.SecretFolder,
`${TableName.SecretApprovalRequest}.folderId`,
`${TableName.SecretFolder}.id`
)
.join(
TableName.Environment,
`${TableName.SecretFolder}.envId`,
`${TableName.Environment}.id`
)
.join(
TableName.SapApprover,
`${TableName.SecretApprovalRequest}.policyId`,
`${TableName.SapApprover}.policyId`
)
.where({ projectId })
.andWhere((bd) =>
bd
.where(`${TableName.SapApprover}.approverId`, membershipId)
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
)
.select("status", `${TableName.SecretApprovalRequest}.id`)
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
.count("status")
)
.join(
TableName.Environment,
`${TableName.SecretFolder}.envId`,
`${TableName.Environment}.id`
)
.join(
TableName.SapApprover,
`${TableName.SecretApprovalRequest}.policyId`,
`${TableName.SapApprover}.policyId`
)
.where({ projectId })
.where(`${TableName.SapApprover}.approverId`, membershipId)
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
.select("status")
.from("temp")
.groupBy("status")
.count("status")
.select("status");
.count("status");
return {
open: parseInt(
(doc.find(({ status }) => status === RequestState.Open)?.count as string) || "0",
(docs.find(({ status }) => status === RequestState.Open)?.count as string) || "0",
10
),
closed: parseInt(
(doc.find(({ status }) => status === RequestState.Closed)?.count as string) || "0",
(docs.find(({ status }) => status === RequestState.Closed)?.count as string) || "0",
10
)
};
@@ -141,11 +153,21 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
};
const findByProjectId = async (
{ status, limit, offset, projectId, committer, environment, membershipId }: TFindQueryFilter,
{
status,
limit = 20,
offset = 0,
projectId,
committer,
environment,
membershipId
}: TFindQueryFilter,
tx?: Knex
) => {
try {
const docs = await (tx || db)(TableName.SecretApprovalRequest)
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
// this is the place u wanna look at.
const query = (tx || db)(TableName.SecretApprovalRequest)
.join(
TableName.SecretFolder,
`${TableName.SecretApprovalRequest}.folderId`,
@@ -174,7 +196,7 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
.where(
stripUndefinedInWhere({
projectId,
slug: environment,
[`${TableName.Environment}.slug` as "slug"]: environment,
[`${TableName.SecretApprovalRequest}.status`]: status,
committerId: committer
})
@@ -190,13 +212,27 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
.select(db.ref("status").withSchema(TableName.SarReviewer).as("reviewerStatus"))
.select(db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"))
.select(db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"))
.select(
db.raw(
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
)
)
.select(
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath")
)
.select(
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals")
)
.select(db.ref("approverId").withSchema(TableName.SapApprover));
.select(db.ref("approverId").withSchema(TableName.SapApprover))
.orderBy("createdAt", "desc");
const docs = await (tx || db)
.with("w", query)
.select("*")
.from<Awaited<typeof query>[number]>("w")
.where("w.rank", ">=", offset)
.andWhere("w.rank", "<", offset + limit);
const formatedDoc = sqlNestRelationships({
data: docs,
key: "id",

View File

@@ -81,6 +81,7 @@ export const secretApprovalRequestServiceFactory = ({
actorId,
projectId
);
const count = await secretApprovalRequestDal.findProjectRequestCount(projectId, membership.id);
return count;
};
@@ -91,7 +92,9 @@ export const secretApprovalRequestServiceFactory = ({
actor,
status,
environment,
committer
committer,
limit,
offset
}: TListApprovalsDTO) => {
if (actor === ActorType.SERVICE)
throw new BadRequestError({ message: "Cannot use service token" });
@@ -102,7 +105,9 @@ export const secretApprovalRequestServiceFactory = ({
committer,
environment,
status,
membershipId: membership.id
membershipId: membership.id,
limit,
offset
});
return approvals;
};
@@ -332,7 +337,11 @@ export const secretApprovalRequestServiceFactory = ({
projectId,
tx,
inputSecrets: secretUpdationCommits.map((el) => ({
...pick(el, [
filter: {
id: el.secretId,
type: SecretType.Shared
},
data: pick(el, [
"secretCommentCiphertext",
"secretCommentTag",
"secretCommentIV",
@@ -346,14 +355,8 @@ export const secretApprovalRequestServiceFactory = ({
"skipMultilineEncoding",
"secretReminderNote",
"secretReminderRepeatDays",
"version",
"algorithm",
"keyEncoding",
"secretBlindIndex"
]),
version: (el.secret?.version || 0) + 1,
id: el.secretId,
type: SecretType.Shared
])
}))
})
: [];

View File

@@ -98,7 +98,7 @@ export const secretRotationDbFn = async ({
const db = knex({
client,
connection: {
db: database,
database,
port,
host,
user: username,
@@ -142,17 +142,17 @@ export const secretRotationHttpSetFn = async (
export const getDbSetQuery = (
db: TDbProviderClients,
variable: { username: string; password: string }
variables: { username: string; password: string }
) => {
if (db === TDbProviderClients.Pg) {
return {
query: "ALTER USER :username WITH PASSWORD :password",
variable
query: `ALTER USER ?? WITH PASSWORD '${variables.password}'`,
variables: [variables.username]
};
}
// add more based on client
return {
query: "ALTER USER :username IDENTIFIED BY :password",
variable
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,
variables: [variables.username]
};
};

View File

@@ -22,6 +22,6 @@ export type TSecretRotationDbFn = {
database: string;
port: number;
query: string;
variables: Record<string, unknown>;
variables: unknown[];
ca?: string;
};

View File

@@ -1,10 +1,11 @@
import { SecretKeyEncoding } from "@app/db/schemas";
import { SecretKeyEncoding, SecretType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import {
encryptSymmetric128BitHexKeyUTF8,
infisicalSymmetricDecrypt,
infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption";
import { daysToMillisecond } from "@app/lib/dates";
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
@@ -34,7 +35,7 @@ type TSecretRotationQueueFactoryDep = {
queue: TQueueServiceFactory;
secretRotationDal: TSecretRotationDalFactory;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
secretDal: Pick<TSecretDalFactory, "bulkUpdate">;
secretDal: Pick<TSecretDalFactory, "bulkUpdate" | "find">;
secretVersionDal: Pick<TSecretVersionDalFactory, "insertMany" | "findLatestVersionMany">;
};
@@ -58,13 +59,25 @@ export const secretRotationQueueFactory = ({
secretDal,
secretVersionDal
}: TSecretRotationQueueFactoryDep) => {
const addToQueue = async (rotationId: string, interval: number) =>
const addToQueue = async (rotationId: string, interval: number) => {
const appCfg = getConfig();
queue.queue(
QueueName.SecretRotation,
QueueJobs.SecretRotation,
{ rotationId },
{ jobId: rotationId, repeat: { every: daysToMillisecond(interval), immediately: true } }
{
jobId: rotationId,
repeat: {
// on prod it this will be in days, in development this will be second
every:
appCfg.NODE_ENV === "development"
? secondsToMillis(interval)
: daysToMillisecond(interval),
immediately: true
}
}
);
};
const removeFromQueue = async (rotationId: string) =>
queue.stopRepeatableJob(QueueName.SecretRotation, rotationId);
@@ -127,7 +140,14 @@ export const secretRotationQueueFactory = ({
}
// set a random value for new password
newCredential.internal.rotated_password = alphaNumericNanoId(32);
const { username, password, host, database, port, ca } = newCredential.inputs;
const {
admin_username: username,
admin_password: password,
host,
database,
port,
ca
} = newCredential.inputs;
const dbFunctionArg = {
username,
password,
@@ -149,8 +169,10 @@ export const secretRotationQueueFactory = ({
await secretRotationDbFn({
...dbFunctionArg,
query: "SELECT NOW()",
variables: {}
variables: []
});
newCredential.outputs.db_username = newCredential.internal.username;
newCredential.outputs.db_password = newCredential.internal.rotated_password;
// clean up
if (variables.creds.length === 2) variables.creds.pop();
}
@@ -172,7 +194,6 @@ export const secretRotationQueueFactory = ({
}
}
}
variables.creds.unshift({
outputs: newCredential.outputs,
internal: newCredential.internal
@@ -205,17 +226,16 @@ export const secretRotationQueueFactory = ({
);
const updatedSecrets = await secretDal.bulkUpdate(
encryptedSecrets.map(({ secretId, value }) => ({
id: secretId,
secretValueCiphertext: value.ciphertext,
secretValueIV: value.iv,
secretValueTag: value.tag
// this secret id is validated when user is inserted
filter: { id: secretId, type: SecretType.Shared },
data: {
secretValueCiphertext: value.ciphertext,
secretValueIV: value.iv,
secretValueTag: value.tag
}
})),
tx
);
await secretDal.bulkUpdate(
updatedSecrets.map(({ id, version }) => ({ id, version: (version || 0) + 1 })),
tx
);
await secretVersionDal.insertMany(
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({
...el,
@@ -224,7 +244,9 @@ export const secretRotationQueueFactory = ({
tx
);
});
logger.info("Finished logging: rotation id: ", rotationId);
} catch (error) {
logger.error(error);
if (error instanceof DisableRotationErrors) {
if (job.id) {
queue.stopRepeatableJob(QueueName.SecretRotation, job.id);

View File

@@ -1,11 +1,12 @@
import { ForbiddenError } from "@casl/ability";
import { ForbiddenError, subject } from "@casl/ability";
import Ajv from "ajv";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types";
import { TProjectDalFactory } from "@app/services/project/project-dal";
import { TProjectEnvDalFactory } from "@app/services/project-env/project-env-dal";
import { TSecretDalFactory } from "@app/services/secret/secret-dal";
import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { TPermissionServiceFactory } from "../permission/permission-service";
@@ -25,8 +26,9 @@ import { rotationTemplates } from "./templates";
type TSecretRotationServiceFactoryDep = {
secretRotationDal: TSecretRotationDalFactory;
projectDal: Pick<TProjectDalFactory, "findById">;
folderDal: Pick<TSecretFolderDalFactory, "findBySecretPath">;
secretDal: Pick<TSecretDalFactory, "find">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
projectEnvDal: Pick<TProjectEnvDalFactory, "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
secretRotationQueue: TSecretRotationQueueFactory;
};
@@ -37,10 +39,11 @@ const ajv = new Ajv({ strict: false });
export const secretRotationServiceFactory = ({
secretRotationDal,
permissionService,
projectEnvDal,
secretRotationQueue,
licenseService,
projectDal
projectDal,
folderDal,
secretDal
}: TSecretRotationServiceFactoryDep) => {
const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
@@ -71,8 +74,20 @@ export const secretRotationServiceFactory = ({
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation
);
const env = await projectEnvDal.findOne({ slug: environment });
if (!env) throw new BadRequestError({ message: "Environment not found" });
const folder = await folderDal.findBySecretPath(projectId, environment, secretPath);
if (!folder) throw new BadRequestError({ message: "Secret path not found" });
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
const selectedSecrets = await secretDal.find({
folderId: folder.id,
$in: { id: Object.values(outputs) }
});
if (selectedSecrets.length !== Object.values(outputs).length)
throw new BadRequestError({ message: "Secrets not found" });
const project = await projectDal.findById(projectId);
const plan = await licenseService.getPlan(project.orgId);
@@ -114,7 +129,7 @@ export const secretRotationServiceFactory = ({
provider,
secretPath,
interval,
envId: env.id,
envId: folder.envId,
encryptedDataTag: encData.tag,
encryptedDataIV: encData.iv,
encryptedData: encData.ciphertext,
@@ -128,7 +143,7 @@ export const secretRotationServiceFactory = ({
Object.entries(outputs).map(([key, secretId]) => ({ key, secretId, rotationId: doc.id })),
tx
);
return { ...doc, outputs: outputSecretMapping, environment: env };
return { ...doc, outputs: outputSecretMapping, environment: folder.environment };
});
return secretRotation;
};

View File

@@ -16,7 +16,7 @@ const envSchema = z
REDIS_URL: zpStr(z.string()),
HOST: zpStr(z.string().default("localhost")),
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database conntection string")),
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
NODE_ENV: z.enum(["development", "test", "production"]).default("production"),
SALT_ROUNDS: z.coerce.number().default(10),
// TODO(akhilmhdh): will be changed to one
ENCRYPTION_KEY: zpStr(z.string().optional()),

View File

@@ -1 +1,3 @@
export const daysToMillisecond = (days: number) => days * 24 * 60 * 60 * 1000;
export const secondsToMillis = (seconds: number) => seconds * 1000;

View File

@@ -381,11 +381,12 @@ export const registerRoutes = async (
});
const secretRotationService = secretRotationServiceFactory({
permissionService,
projectEnvDal,
secretRotationDal,
secretRotationQueue,
projectDal,
licenseService
licenseService,
secretDal,
folderDal
});
const integrationService = integrationServiceFactory({

View File

@@ -1,7 +1,12 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TSecretFolders, TSecretFoldersUpdate } from "@app/db/schemas";
import {
TableName,
TProjectEnvironments,
TSecretFolders,
TSecretFoldersUpdate
} from "@app/db/schemas";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
@@ -65,19 +70,20 @@ const sqlFindMultipleFolderByEnvPathQuery = (
.select(selectAllTableCols(TableName.SecretFolder))
.where((wb) =>
formatedQuery.map(({ secretPath }) =>
wb
.orWhereRaw(
`depth = array_position(ARRAY[${secretPath
.map(() => "?")
.join(",")}]::varchar[], ${TableName.SecretFolder}.name,depth)`,
[...secretPath]
)
wb.orWhereRaw(
`depth = array_position(ARRAY[${secretPath
.map(() => "?")
.join(",")}]::varchar[], ${TableName.SecretFolder}.name,depth)`,
[...secretPath]
)
)
)
.from(TableName.SecretFolder)
.join("parent", (bd)=>
bd.on("parent.id", `${TableName.SecretFolder}.parentId`).andOn("parent.envId",`${TableName.SecretFolder}.envId`)
)
.join("parent", (bd) =>
bd
.on("parent.id", `${TableName.SecretFolder}.parentId`)
.andOn("parent.envId", `${TableName.SecretFolder}.envId`)
)
);
})
.select("*")
@@ -146,8 +152,28 @@ const sqlFindFolderByPathQuery = (
.join("parent", "parent.id", `${TableName.SecretFolder}.parentId`)
);
})
.select("*")
.from<TSecretFolders & { depth: number; path: string }>("parent");
.from<TSecretFolders & { depth: number; path: string }>("parent")
.leftJoin<TProjectEnvironments>(
TableName.Environment,
`${TableName.Environment}.id`,
"parent.envId"
)
.select<
TSecretFolders & {
depth: number;
path: string;
envId: string;
envSlug: string;
envName: string;
projectId: string;
}
>(
selectAllTableCols("parent" as TableName.SecretFolder),
db.ref("id").withSchema(TableName.Environment).as("envId"),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("name").withSchema(TableName.Environment).as("envName"),
db.ref("projectId").withSchema(TableName.Environment)
);
};
export type TSecretFolderDalFactory = ReturnType<typeof secretFolderDalFactory>;
@@ -169,7 +195,9 @@ export const secretFolderDalFactory = (db: TDbClient) => {
if (folder && folder.path !== path) {
return;
}
return folder;
if (!folder) return;
const { envId: id, envName: name, envSlug: slug, ...el } = folder;
return { ...el, envId: id, environment: { id, name, slug } };
} catch (error) {
throw new DatabaseError({ error, name: "Find by secret path" });
}

View File

@@ -1,14 +1,7 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import {
SecretsSchema,
SecretType,
TableName,
TSecrets,
TSecretsInsert,
TSecretsUpdate
} from "@app/db/schemas";
import { SecretsSchema, SecretType, TableName, TSecrets, TSecretsUpdate } from "@app/db/schemas";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
@@ -36,13 +29,22 @@ export const secretDalFactory = (db: TDbClient) => {
// the idea is to use postgres specific function
// insert with id this will cause a conflict then merge the data
const bulkUpdate = async (data: Array<TSecretsUpdate & { id: string }>, tx?: Knex) => {
const bulkUpdate = async (
data: Array<{ filter: Partial<TSecrets>; data: TSecretsUpdate }>,
tx?: Knex
) => {
try {
const secs = await (tx || db)(TableName.Secret)
.insert(data as TSecretsInsert[])
.onConflict("id")
.merge()
.returning("*");
const secs = await Promise.all(
data.map(async ({ filter, data: updateData }) => {
const [doc] = await (tx || db)(TableName.Secret)
.where(filter)
.update(updateData)
.increment("version", 1)
.returning("*");
if (!doc) throw new BadRequestError({ message: "Failed to update document" });
return doc;
})
);
return secs;
} catch (error) {
throw new DatabaseError({ error, name: "bulk update secret" });

View File

@@ -132,20 +132,25 @@ export const secretServiceFactory = ({
projectId
}: TFnSecretBulkUpdate) => {
const newSecrets = await secretDal.bulkUpdate(
inputSecrets.map(({ tags, ...el }) => ({ ...el, folderId })),
inputSecrets.map(({ filter, data: { tags, ...data } }) => ({
filter: { ...filter, folderId },
data
})),
tx
);
const secsUpdatedTag = inputSecrets.filter(({ tags }) => Boolean(tags));
const secsUpdatedTag = inputSecrets.flatMap(({ data: { tags } }, i) =>
tags?.length ? { tags, secretId: newSecrets[i].id } : []
);
if (secsUpdatedTag.length) {
await secretTagDal.deleteTagsManySecret(
projectId,
secsUpdatedTag.map(({ id }) => id),
secsUpdatedTag.flatMap(({ tags }) => tags),
tx
);
const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], id }) =>
const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], secretId }) =>
secretTags.map((tag) => ({
[`${TableName.SecretTag}Id` as const]: tag,
[`${TableName.Secret}Id` as const]: id
[`${TableName.Secret}Id` as const]: secretId
}))
);
await secretTagDal.saveTagsToSecret(newSecretTags, tx);
@@ -391,26 +396,27 @@ export const secretServiceFactory = ({
projectId,
inputSecrets: [
{
id: secrets[0].id,
version: (secrets[0].version || 0) + 1,
...pick(el, [
"type",
"secretCommentCiphertext",
"secretCommentTag",
"secretCommentIV",
"secretValueIV",
"secretValueTag",
"secretValueCiphertext",
"secretKeyCiphertext",
"secretKeyTag",
"secretKeyIV",
"metadata",
"skipMultilineEncoding",
"secretReminderNote",
"secretReminderRepeatDays",
"tags"
]),
secretBlindIndex: newSecretNameBlindIndex || keyName2BlindIndex[secretName]
filter: { id: secrets[0].id },
data: {
...pick(el, [
"type",
"secretCommentCiphertext",
"secretCommentTag",
"secretCommentIV",
"secretValueIV",
"secretValueTag",
"secretValueCiphertext",
"secretKeyCiphertext",
"secretKeyTag",
"secretKeyIV",
"metadata",
"skipMultilineEncoding",
"secretReminderNote",
"secretReminderRepeatDays",
"tags"
]),
secretBlindIndex: newSecretNameBlindIndex || keyName2BlindIndex[secretName]
}
}
],
tx
@@ -667,7 +673,7 @@ export const secretServiceFactory = ({
if (!blindIndexCfg)
throw new BadRequestError({ message: "Blind index not found", name: "Update secret" });
const { keyName2BlindIndex, secrets: secretsToBeUpdated } = await fnSecretBlindIndexCheck({
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
inputSecrets,
folderId,
isNew: false,
@@ -684,7 +690,6 @@ export const secretServiceFactory = ({
blindIndexCfg
});
const secsGroupedByBlindIndex = groupBy(secretsToBeUpdated, (el) => el.secretBlindIndex);
// get all tags
const tagIds = inputSecrets.flatMap(({ tags = [] }) => tags);
const tags = tagIds.length ? await secretTagDal.findManyTagsById(projectId, tagIds) : [];
@@ -694,13 +699,10 @@ export const secretServiceFactory = ({
folderId,
projectId,
tx,
inputSecrets: inputSecrets.map(({ secretName, newSecretName, ...el }) => {
const { version, updatedAt, ...info } =
secsGroupedByBlindIndex[keyName2BlindIndex[secretName]][0];
return {
inputSecrets: inputSecrets.map(({ secretName, newSecretName, ...el }) => ({
filter: { secretBlindIndex: keyName2BlindIndex[secretName], type: SecretType.Shared },
data: {
...el,
version: (version || 0) + 1,
...info,
folderId,
type: SecretType.Shared,
secretBlindIndex:
@@ -709,8 +711,8 @@ export const secretServiceFactory = ({
: keyName2BlindIndex[secretName],
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8
};
})
}
}))
})
);
@@ -806,7 +808,8 @@ export const secretServiceFactory = ({
includeImports
}: TGetSecretsRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found" });
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const { secrets, imports } = await getSecrets({
actorId,
@@ -842,7 +845,8 @@ export const secretServiceFactory = ({
includeImports
}: TGetASecretRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found" });
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secret = await getASecret({
actorId,
@@ -870,7 +874,8 @@ export const secretServiceFactory = ({
skipMultilineEncoding
}: TCreateSecretRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found" });
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretName, botKey);
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
@@ -914,7 +919,8 @@ export const secretServiceFactory = ({
skipMultilineEncoding
}: TUpdateSecretRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found" });
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey);
@@ -948,7 +954,8 @@ export const secretServiceFactory = ({
secretPath
}: TDeleteSecretRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
if (!botKey) throw new BadRequestError({ message: "Project bot not found" });
if (!botKey)
throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
const secret = await deleteSecret({
secretName,

View File

@@ -1,6 +1,12 @@
import { Knex } from "knex";
import { SecretType, TSecretBlindIndexes, TSecretsInsert, TSecretsUpdate } from "@app/db/schemas";
import {
SecretType,
TSecretBlindIndexes,
TSecrets,
TSecretsInsert,
TSecretsUpdate
} from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
export type TCreateSecretDTO = {
@@ -182,7 +188,7 @@ export type TFnSecretBulkInsert = {
export type TFnSecretBulkUpdate = {
folderId: string;
projectId: string;
inputSecrets: Array<TSecretsUpdate & { tags?: string[]; id: string }>;
inputSecrets: { filter: Partial<TSecrets>; data: TSecretsUpdate & { tags?: string[] } }[];
tx?: Knex;
};

View File

@@ -38,7 +38,7 @@ services:
profiles: ["test"]
image: postgres:14-alpine
ports:
- "5432:5432"
- "5430:5432"
environment:
POSTGRES_PASSWORD: infisical
POSTGRES_USER: infisical
@@ -54,6 +54,7 @@ services:
env_file:
- .env
environment:
- NODE_ENV=development
- DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable
volumes:
- ./backend-pg/src:/app/src