mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge remote-tracking branch 'origin' into certificate-alerting
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
@@ -61,6 +62,10 @@ export const auditLogServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createAuditLog = async (data: TCreateAuditLogDTO) => {
|
const createAuditLog = async (data: TCreateAuditLogDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (appCfg.DISABLE_AUDIT_LOG_GENERATION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// add all cases in which project id or org id cannot be added
|
// add all cases in which project id or org id cannot be added
|
||||||
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
||||||
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" });
|
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" });
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { removeTrailingSlash } from "@app/lib/fn";
|
|||||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +29,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
||||||
@@ -36,7 +38,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
projectEnvDAL
|
projectEnvDAL,
|
||||||
|
licenseService
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const createSecretApprovalPolicy = async ({
|
const createSecretApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
@@ -65,6 +68,15 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to create secret approval policy due to plan restriction. Upgrade plan to create secret approval policy."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||||
|
|
||||||
@@ -115,6 +127,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update secret approval policy due to plan restriction. Upgrade plan to update secret approval policy."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.updateById(
|
const doc = await secretApprovalPolicyDAL.updateById(
|
||||||
secretApprovalPolicy.id,
|
secretApprovalPolicy.id,
|
||||||
@@ -167,6 +187,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update secret approval policy due to plan restriction. Upgrade plan to update secret approval policy."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await secretApprovalPolicyDAL.deleteById(secretPolicyId);
|
await secretApprovalPolicyDAL.deleteById(secretPolicyId);
|
||||||
return sapPolicy;
|
return sapPolicy;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/se
|
|||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
@@ -97,6 +98,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
||||||
@@ -122,7 +124,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
kmsService,
|
kmsService,
|
||||||
secretV2BridgeDAL,
|
secretV2BridgeDAL,
|
||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
secretVersionTagV2BridgeDAL
|
secretVersionTagV2BridgeDAL,
|
||||||
|
licenseService
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
@@ -295,6 +298,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to review secret approval request due to plan restriction. Upgrade plan to review secret approval request."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
@@ -345,6 +356,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update secret approval request due to plan restriction. Upgrade plan to update secret approval request."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
@@ -386,6 +405,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.secretApproval) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to merge secret approval request due to plan restriction. Upgrade plan to merge secret approval request."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { policy, folderId, projectId } = secretApprovalRequest;
|
const { policy, folderId, projectId } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
|
|||||||
@@ -5,17 +5,26 @@ import { Redlock, Settings } from "@app/lib/red-lock";
|
|||||||
export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
|
export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
|
||||||
|
|
||||||
// all the key prefixes used must be set here to avoid conflict
|
// all the key prefixes used must be set here to avoid conflict
|
||||||
export enum KeyStorePrefixes {
|
export const KeyStorePrefixes = {
|
||||||
SecretReplication = "secret-replication-import-lock",
|
SecretReplication: "secret-replication-import-lock",
|
||||||
KmsProjectDataKeyCreation = "kms-project-data-key-creation-lock",
|
KmsProjectDataKeyCreation: "kms-project-data-key-creation-lock",
|
||||||
KmsProjectKeyCreation = "kms-project-key-creation-lock",
|
KmsProjectKeyCreation: "kms-project-key-creation-lock",
|
||||||
WaitUntilReadyKmsProjectDataKeyCreation = "wait-until-ready-kms-project-data-key-creation-",
|
WaitUntilReadyKmsProjectDataKeyCreation: "wait-until-ready-kms-project-data-key-creation-",
|
||||||
WaitUntilReadyKmsProjectKeyCreation = "wait-until-ready-kms-project-key-creation-",
|
WaitUntilReadyKmsProjectKeyCreation: "wait-until-ready-kms-project-key-creation-",
|
||||||
KmsOrgKeyCreation = "kms-org-key-creation-lock",
|
KmsOrgKeyCreation: "kms-org-key-creation-lock",
|
||||||
KmsOrgDataKeyCreation = "kms-org-data-key-creation-lock",
|
KmsOrgDataKeyCreation: "kms-org-data-key-creation-lock",
|
||||||
WaitUntilReadyKmsOrgKeyCreation = "wait-until-ready-kms-org-key-creation-",
|
WaitUntilReadyKmsOrgKeyCreation: "wait-until-ready-kms-org-key-creation-",
|
||||||
WaitUntilReadyKmsOrgDataKeyCreation = "wait-until-ready-kms-org-data-key-creation-"
|
WaitUntilReadyKmsOrgDataKeyCreation: "wait-until-ready-kms-org-data-key-creation-",
|
||||||
}
|
|
||||||
|
SyncSecretIntegrationLock: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||||
|
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||||
|
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||||
|
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KeyStoreTtls = {
|
||||||
|
SetSyncSecretIntegrationLastRunTimestampInSeconds: 10
|
||||||
|
};
|
||||||
|
|
||||||
type TWaitTillReady = {
|
type TWaitTillReady = {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -37,10 +46,10 @@ export const keyStoreFactory = (redisUrl: string) => {
|
|||||||
|
|
||||||
const setItemWithExpiry = async (
|
const setItemWithExpiry = async (
|
||||||
key: string,
|
key: string,
|
||||||
exp: number | string,
|
expiryInSeconds: number | string,
|
||||||
value: string | number | Buffer,
|
value: string | number | Buffer,
|
||||||
prefix?: string
|
prefix?: string
|
||||||
) => redis.set(prefix ? `${prefix}:${key}` : key, value, "EX", exp);
|
) => redis.set(prefix ? `${prefix}:${key}` : key, value, "EX", expiryInSeconds);
|
||||||
|
|
||||||
const deleteItem = async (key: string) => redis.del(key);
|
const deleteItem = async (key: string) => redis.del(key);
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ const envSchema = z
|
|||||||
MAINTENANCE_MODE: zodStrBool.default("false"),
|
MAINTENANCE_MODE: zodStrBool.default("false"),
|
||||||
CAPTCHA_SECRET: zpStr(z.string().optional()),
|
CAPTCHA_SECRET: zpStr(z.string().optional()),
|
||||||
PLAIN_API_KEY: zpStr(z.string().optional()),
|
PLAIN_API_KEY: zpStr(z.string().optional()),
|
||||||
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional())
|
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional()),
|
||||||
|
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false")
|
||||||
})
|
})
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
|||||||
@@ -1,2 +1,8 @@
|
|||||||
export const getLastMidnightDateISO = (last = 1) =>
|
export const getLastMidnightDateISO = (last = 1) =>
|
||||||
`${new Date(new Date().setDate(new Date().getDate() - last)).toISOString().slice(0, 10)}T00:00:00Z`;
|
`${new Date(new Date().setDate(new Date().getDate() - last)).toISOString().slice(0, 10)}T00:00:00Z`;
|
||||||
|
|
||||||
|
export const getTimeDifferenceInSeconds = (lhsTimestamp: string, rhsTimestamp: string) => {
|
||||||
|
const lhs = new Date(lhsTimestamp);
|
||||||
|
const rhs = new Date(rhsTimestamp);
|
||||||
|
return Math.floor((Number(lhs) - Number(rhs)) / 1000);
|
||||||
|
};
|
||||||
|
|||||||
@@ -361,7 +361,8 @@ export const registerRoutes = async (
|
|||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyDAL
|
secretApprovalPolicyDAL,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
||||||
|
|
||||||
@@ -739,6 +740,7 @@ export const registerRoutes = async (
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
const secretQueueService = secretQueueFactory({
|
const secretQueueService = secretQueueFactory({
|
||||||
|
keyStore,
|
||||||
queueService,
|
queueService,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
@@ -824,7 +826,8 @@ export const registerRoutes = async (
|
|||||||
secretVersionTagV2BridgeDAL,
|
secretVersionTagV2BridgeDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
userDAL
|
userDAL,
|
||||||
|
licenseService
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretService = secretServiceFactory({
|
const secretService = secretServiceFactory({
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approv
|
|||||||
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
||||||
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
||||||
import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal";
|
import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal";
|
||||||
|
import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { groupBy, isSamePath, unique } from "@app/lib/fn";
|
import { getTimeDifferenceInSeconds, groupBy, isSamePath, unique } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
@@ -79,6 +80,7 @@ type TSecretQueueFactoryDep = {
|
|||||||
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "deleteByProjectId">;
|
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "deleteByProjectId">;
|
||||||
snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId" | "deleteSnapshotsAboveLimit">;
|
snapshotDAL: Pick<TSnapshotDALFactory, "findNSecretV1SnapshotByFolderId" | "deleteSnapshotsAboveLimit">;
|
||||||
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany" | "batchInsert">;
|
snapshotSecretV2BridgeDAL: Pick<TSnapshotSecretV2DALFactory, "insertMany" | "batchInsert">;
|
||||||
|
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetSecrets = {
|
export type TGetSecrets = {
|
||||||
@@ -122,7 +124,8 @@ export const secretQueueFactory = ({
|
|||||||
secretRotationDAL,
|
secretRotationDAL,
|
||||||
snapshotDAL,
|
snapshotDAL,
|
||||||
snapshotSecretV2BridgeDAL,
|
snapshotSecretV2BridgeDAL,
|
||||||
secretApprovalRequestDAL
|
secretApprovalRequestDAL,
|
||||||
|
keyStore
|
||||||
}: TSecretQueueFactoryDep) => {
|
}: TSecretQueueFactoryDep) => {
|
||||||
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -576,7 +579,6 @@ export const secretQueueFactory = ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
|
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
@@ -641,111 +643,157 @@ export const secretQueueFactory = ({
|
|||||||
`getIntegrationSecrets: secret integration sync started [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}] [depth=${job.data.depth}]`
|
`getIntegrationSecrets: secret integration sync started [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}] [depth=${job.data.depth}]`
|
||||||
);
|
);
|
||||||
|
|
||||||
const secrets = shouldUseSecretV2Bridge
|
const lock = await keyStore.acquireLock(
|
||||||
? await getIntegrationSecretsV2({
|
[KeyStorePrefixes.SyncSecretIntegrationLock(projectId, environment, secretPath)],
|
||||||
environment,
|
10000,
|
||||||
projectId,
|
{
|
||||||
folderId: folder.id,
|
retryCount: 3,
|
||||||
depth: 1,
|
retryDelay: 2000
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
|
||||||
})
|
|
||||||
: await getIntegrationSecrets({
|
|
||||||
environment,
|
|
||||||
projectId,
|
|
||||||
folderId: folder.id,
|
|
||||||
key: botKey as string,
|
|
||||||
depth: 1
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const integration of toBeSyncedIntegrations) {
|
|
||||||
const integrationAuth = {
|
|
||||||
...integration.integrationAuth,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
projectId: integration.projectId
|
|
||||||
};
|
|
||||||
|
|
||||||
const { accessToken, accessId } = await integrationAuthService.getIntegrationAccessToken(
|
|
||||||
integrationAuth,
|
|
||||||
shouldUseSecretV2Bridge,
|
|
||||||
botKey
|
|
||||||
);
|
|
||||||
let awsAssumeRoleArn = null;
|
|
||||||
if (shouldUseSecretV2Bridge) {
|
|
||||||
if (integrationAuth.encryptedAwsAssumeIamRoleArn) {
|
|
||||||
awsAssumeRoleArn = secretManagerDecryptor({
|
|
||||||
cipherTextBlob: Buffer.from(integrationAuth.encryptedAwsAssumeIamRoleArn)
|
|
||||||
}).toString();
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
integrationAuth.awsAssumeIamRoleArnTag &&
|
|
||||||
integrationAuth.awsAssumeIamRoleArnIV &&
|
|
||||||
integrationAuth.awsAssumeIamRoleArnCipherText
|
|
||||||
) {
|
|
||||||
awsAssumeRoleArn = decryptSymmetric128BitHexKeyUTF8({
|
|
||||||
ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText,
|
|
||||||
iv: integrationAuth.awsAssumeIamRoleArnIV,
|
|
||||||
tag: integrationAuth.awsAssumeIamRoleArnTag,
|
|
||||||
key: botKey as string
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
const lockAcquiredTime = new Date();
|
||||||
|
|
||||||
const suffixedSecrets: typeof secrets = {};
|
const lastRunSyncIntegrationTimestamp = await keyStore.getItem(
|
||||||
const metadata = integration.metadata as Record<string, string>;
|
KeyStorePrefixes.SyncSecretIntegrationLastRunTimestamp(projectId, environment, secretPath)
|
||||||
if (metadata) {
|
);
|
||||||
Object.keys(secrets).forEach((key) => {
|
|
||||||
const prefix = metadata?.secretPrefix || "";
|
|
||||||
const suffix = metadata?.secretSuffix || "";
|
|
||||||
const newKey = prefix + key + suffix;
|
|
||||||
suffixedSecrets[newKey] = secrets[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// check whether the integration should wait or not
|
||||||
// akhilmhdh: this needs to changed later to be more easier to use
|
if (lastRunSyncIntegrationTimestamp) {
|
||||||
// at present this is not at all extendable like to add a new parameter for just one integration need to modify multiple places
|
const INTEGRATION_INTERVAL = 2000;
|
||||||
const response = await syncIntegrationSecrets({
|
const isStaleSyncIntegration = new Date(job.timestamp) < new Date(lastRunSyncIntegrationTimestamp);
|
||||||
createManySecretsRawFn,
|
if (isStaleSyncIntegration) {
|
||||||
updateManySecretsRawFn,
|
logger.info(
|
||||||
integrationDAL,
|
`getIntegrationSecrets: secret integration sync stale [jobId=${job.id}] [jobId=${job.id}] [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}] [depth=${job.data.depth}]`
|
||||||
integration,
|
|
||||||
integrationAuth,
|
|
||||||
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
|
||||||
accessId: accessId as string,
|
|
||||||
awsAssumeRoleArn,
|
|
||||||
accessToken,
|
|
||||||
projectId,
|
|
||||||
appendices: {
|
|
||||||
prefix: metadata?.secretPrefix || "",
|
|
||||||
suffix: metadata?.secretSuffix || ""
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await integrationDAL.updateById(integration.id, {
|
|
||||||
lastSyncJobId: job.id,
|
|
||||||
lastUsed: new Date(),
|
|
||||||
syncMessage: response?.syncMessage ?? "",
|
|
||||||
isSynced: response?.isSynced ?? true
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(
|
|
||||||
err,
|
|
||||||
`Secret integration sync error [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}]`
|
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
const message =
|
|
||||||
(err instanceof AxiosError ? JSON.stringify(err?.response?.data) : (err as Error)?.message) ||
|
|
||||||
"Unknown error occurred.";
|
|
||||||
|
|
||||||
await integrationDAL.updateById(integration.id, {
|
|
||||||
lastSyncJobId: job.id,
|
|
||||||
lastUsed: new Date(),
|
|
||||||
syncMessage: message,
|
|
||||||
isSynced: false
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const timeDifferenceWithLastIntegration = getTimeDifferenceInSeconds(
|
||||||
|
lockAcquiredTime.toISOString(),
|
||||||
|
lastRunSyncIntegrationTimestamp
|
||||||
|
);
|
||||||
|
if (timeDifferenceWithLastIntegration < INTEGRATION_INTERVAL && timeDifferenceWithLastIntegration > 0)
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 2000 - timeDifferenceWithLastIntegration * 1000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// akhilmhdh: this try catch is for lock release
|
||||||
|
try {
|
||||||
|
const secrets = shouldUseSecretV2Bridge
|
||||||
|
? await getIntegrationSecretsV2({
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
folderId: folder.id,
|
||||||
|
depth: 1,
|
||||||
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
||||||
|
})
|
||||||
|
: await getIntegrationSecrets({
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
folderId: folder.id,
|
||||||
|
key: botKey as string,
|
||||||
|
depth: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const integration of toBeSyncedIntegrations) {
|
||||||
|
const integrationAuth = {
|
||||||
|
...integration.integrationAuth,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
projectId: integration.projectId
|
||||||
|
};
|
||||||
|
|
||||||
|
const { accessToken, accessId } = await integrationAuthService.getIntegrationAccessToken(
|
||||||
|
integrationAuth,
|
||||||
|
shouldUseSecretV2Bridge,
|
||||||
|
botKey
|
||||||
|
);
|
||||||
|
let awsAssumeRoleArn = null;
|
||||||
|
if (shouldUseSecretV2Bridge) {
|
||||||
|
if (integrationAuth.encryptedAwsAssumeIamRoleArn) {
|
||||||
|
awsAssumeRoleArn = secretManagerDecryptor({
|
||||||
|
cipherTextBlob: Buffer.from(integrationAuth.encryptedAwsAssumeIamRoleArn)
|
||||||
|
}).toString();
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
integrationAuth.awsAssumeIamRoleArnTag &&
|
||||||
|
integrationAuth.awsAssumeIamRoleArnIV &&
|
||||||
|
integrationAuth.awsAssumeIamRoleArnCipherText
|
||||||
|
) {
|
||||||
|
awsAssumeRoleArn = decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText,
|
||||||
|
iv: integrationAuth.awsAssumeIamRoleArnIV,
|
||||||
|
tag: integrationAuth.awsAssumeIamRoleArnTag,
|
||||||
|
key: botKey as string
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffixedSecrets: typeof secrets = {};
|
||||||
|
const metadata = integration.metadata as Record<string, string>;
|
||||||
|
if (metadata) {
|
||||||
|
Object.keys(secrets).forEach((key) => {
|
||||||
|
const prefix = metadata?.secretPrefix || "";
|
||||||
|
const suffix = metadata?.secretSuffix || "";
|
||||||
|
const newKey = prefix + key + suffix;
|
||||||
|
suffixedSecrets[newKey] = secrets[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// akhilmhdh: this try catch is for catching integration error and saving it in db
|
||||||
|
try {
|
||||||
|
// akhilmhdh: this needs to changed later to be more easier to use
|
||||||
|
// at present this is not at all extendable like to add a new parameter for just one integration need to modify multiple places
|
||||||
|
const response = await syncIntegrationSecrets({
|
||||||
|
createManySecretsRawFn,
|
||||||
|
updateManySecretsRawFn,
|
||||||
|
integrationDAL,
|
||||||
|
integration,
|
||||||
|
integrationAuth,
|
||||||
|
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
||||||
|
accessId: accessId as string,
|
||||||
|
awsAssumeRoleArn,
|
||||||
|
accessToken,
|
||||||
|
projectId,
|
||||||
|
appendices: {
|
||||||
|
prefix: metadata?.secretPrefix || "",
|
||||||
|
suffix: metadata?.secretSuffix || ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await integrationDAL.updateById(integration.id, {
|
||||||
|
lastSyncJobId: job.id,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
syncMessage: response?.syncMessage ?? "",
|
||||||
|
isSynced: response?.isSynced ?? true
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
err,
|
||||||
|
`Secret integration sync error [projectId=${job.data.projectId}] [environment=${job.data.environment}] [secretPath=${job.data.secretPath}]`
|
||||||
|
);
|
||||||
|
|
||||||
|
const message =
|
||||||
|
(err instanceof AxiosError ? JSON.stringify(err?.response?.data) : (err as Error)?.message) ||
|
||||||
|
"Unknown error occurred.";
|
||||||
|
|
||||||
|
await integrationDAL.updateById(integration.id, {
|
||||||
|
lastSyncJobId: job.id,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
syncMessage: message,
|
||||||
|
isSynced: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await lock.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
await keyStore.setItemWithExpiry(
|
||||||
|
KeyStorePrefixes.SyncSecretIntegrationLastRunTimestamp(projectId, environment, secretPath),
|
||||||
|
KeyStoreTtls.SetSyncSecretIntegrationLastRunTimestampInSeconds,
|
||||||
|
lockAcquiredTime.toISOString()
|
||||||
|
);
|
||||||
logger.info("Secret integration sync ended: %s", job.id);
|
logger.info("Secret integration sync ended: %s", job.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ description: "Learn how to manage secrets in local development environments."
|
|||||||
There is a number of issues that arise with secret management in local development environment:
|
There is a number of issues that arise with secret management in local development environment:
|
||||||
1. **Getting secrets onto local machines**. When new developers join or a new project is created, the process of getting the development set of secrets onto local machines is often unclear. As a result, developers end up spending a lot of time onboarding and risk potentially following insecure practices when sharing secrets from one developer to another.
|
1. **Getting secrets onto local machines**. When new developers join or a new project is created, the process of getting the development set of secrets onto local machines is often unclear. As a result, developers end up spending a lot of time onboarding and risk potentially following insecure practices when sharing secrets from one developer to another.
|
||||||
2. **Syncing secrets with teammates**. One of the problems with .env files is that they become unsynced when one of the developers updates a secret or configuration. Even if the rest of the team is notified, developers don't make all the right changes immediately, and later on end up spending a lot of time debugging an issue due to missing environment variables. This leads to a lot of inefficiencies and lost time.
|
2. **Syncing secrets with teammates**. One of the problems with .env files is that they become unsynced when one of the developers updates a secret or configuration. Even if the rest of the team is notified, developers don't make all the right changes immediately, and later on end up spending a lot of time debugging an issue due to missing environment variables. This leads to a lot of inefficiencies and lost time.
|
||||||
3. **Accidentally leaking secrets**. When developing locally, it's common for developers to accidentally leak a hardcoded as part of a commit. As soon as the secret is part of the git history, it becomes hard to get it removed and create a security vulnerability.
|
3. **Accidentally leaking secrets**. When developing locally, it's common for developers to accidentally leak a hardcoded secret as part of a commit. As soon as the secret is part of the git history, it becomes hard to get it removed and create a security vulnerability.
|
||||||
|
|
||||||
## Solution
|
## Solution
|
||||||
|
|
||||||
@@ -31,4 +31,4 @@ By default, all the secrets in the Infisical environments are shared among proje
|
|||||||
|
|
||||||
### Secret Scanning
|
### Secret Scanning
|
||||||
|
|
||||||
In addition, Infisical also provides a set of tools to automatically prevent secret leaks to git history. This functionality can be set up on the level of [Infisical CLI using pre-commit hooks](/cli/scanning-overview#automatically-scan-changes-before-you-commit) or through a direct integration with platforms like GitHub.
|
In addition, Infisical also provides a set of tools to automatically prevent secret leaks to git history. This functionality can be set up on the level of [Infisical CLI using pre-commit hooks](/cli/scanning-overview#automatically-scan-changes-before-you-commit) or through a direct integration with platforms like GitHub.
|
||||||
|
|||||||
@@ -273,8 +273,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
const orgUsers = await fetchOrgUsers(currentOrg.id);
|
const orgUsers = await fetchOrgUsers(currentOrg.id);
|
||||||
await addUsersToProject.mutateAsync({
|
await addUsersToProject.mutateAsync({
|
||||||
usernames: orgUsers
|
usernames: orgUsers
|
||||||
.map((member) => member.user.username)
|
.filter(
|
||||||
.filter((username) => username !== user.username),
|
(member) => member.user.username !== user.username && member.status === "accepted"
|
||||||
|
)
|
||||||
|
.map((member) => member.user.username),
|
||||||
projectId: newProjectId,
|
projectId: newProjectId,
|
||||||
orgId: currentOrg.id
|
orgId: currentOrg.id
|
||||||
});
|
});
|
||||||
@@ -482,7 +484,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
)}
|
)}
|
||||||
<Link href={`/org/${currentOrg?.id}/admin`} legacyBehavior>
|
<Link href={`/org/${currentOrg?.id}/admin`} legacyBehavior>
|
||||||
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
<DropdownMenuItem className="mt-1 border-t border-mineshaft-600">
|
||||||
Organization Admin Console
|
Organization Admin Console
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
|
|||||||
@@ -552,8 +552,10 @@ const OrganizationPage = () => {
|
|||||||
|
|
||||||
await addUsersToProject.mutateAsync({
|
await addUsersToProject.mutateAsync({
|
||||||
usernames: orgUsers
|
usernames: orgUsers
|
||||||
.map((member) => member.user.username)
|
.filter(
|
||||||
.filter((username) => username !== user.username),
|
(member) => member.user.username !== user.username && member.status === "accepted"
|
||||||
|
)
|
||||||
|
.map((member) => member.user.username),
|
||||||
projectId: newProjectId,
|
projectId: newProjectId,
|
||||||
orgId: currentOrg.id
|
orgId: currentOrg.id
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user