feat: secret v2 architecture for secret rotation

This commit is contained in:
=
2024-07-30 23:19:06 +05:30
parent 4a06e3e712
commit acf9a488ac
14 changed files with 206 additions and 182 deletions
+8
View File
@@ -239,6 +239,9 @@ import {
TSecretRotationOutputs, TSecretRotationOutputs,
TSecretRotationOutputsInsert, TSecretRotationOutputsInsert,
TSecretRotationOutputsUpdate, TSecretRotationOutputsUpdate,
TSecretRotationOutputV2,
TSecretRotationOutputV2Insert,
TSecretRotationOutputV2Update,
TSecretRotations, TSecretRotations,
TSecretRotationsInsert, TSecretRotationsInsert,
TSecretRotationsUpdate, TSecretRotationsUpdate,
@@ -718,6 +721,11 @@ declare module "knex/types/tables" {
TSecretApprovalRequestSecretTagsV2Insert, TSecretApprovalRequestSecretTagsV2Insert,
TSecretApprovalRequestSecretTagsV2Update TSecretApprovalRequestSecretTagsV2Update
>; >;
[TableName.SecretRotationOutputV2]: KnexOriginal.CompositeTableType<
TSecretRotationOutputV2,
TSecretRotationOutputV2Insert,
TSecretRotationOutputV2Update
>;
// KMS service // KMS service
[TableName.KmsServerRootConfig]: KnexOriginal.CompositeTableType< [TableName.KmsServerRootConfig]: KnexOriginal.CompositeTableType<
TKmsRootConfig, TKmsRootConfig,
@@ -128,7 +128,18 @@ export async function up(knex: Knex): Promise<void> {
if (!hasEncryptedAccess) t.binary("encryptedAccess"); if (!hasEncryptedAccess) t.binary("encryptedAccess");
if (!hasEncryptedAccessId) t.binary("encryptedAccessId"); if (!hasEncryptedAccessId) t.binary("encryptedAccessId");
if (!hasEncryptedRefresh) t.binary("encryptedRefresh"); if (!hasEncryptedRefresh) t.binary("encryptedRefresh");
if (!hasEncryptedAwsIamAssumRole) t.binary("encryptedAwsIamAssumRole"); if (!hasEncryptedAwsIamAssumRole) t.binary("encryptedAwsAssumeIamRoleArn");
});
}
if (!(await knex.schema.hasTable(TableName.SecretRotationOutputV2))) {
await knex.schema.createTable(TableName.SecretRotationOutputV2, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("key").notNullable();
t.uuid("secretId").notNullable();
t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
t.uuid("rotationId").notNullable();
t.foreign("rotationId").references("id").inTable(TableName.SecretRotation).onDelete("CASCADE");
}); });
} }
} }
@@ -141,13 +152,15 @@ export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretV2JnTag); await knex.schema.dropTableIfExists(TableName.SecretV2JnTag);
await knex.schema.dropTableIfExists(TableName.SecretReferenceV2); await knex.schema.dropTableIfExists(TableName.SecretReferenceV2);
await dropOnUpdateTrigger(knex, TableName.SecretV2); await knex.schema.dropTableIfExists(TableName.SecretRotationOutputV2);
await knex.schema.dropTableIfExists(TableName.SecretV2);
await dropOnUpdateTrigger(knex, TableName.SecretVersionV2); await dropOnUpdateTrigger(knex, TableName.SecretVersionV2);
await knex.schema.dropTableIfExists(TableName.SecretVersionV2Tag); await knex.schema.dropTableIfExists(TableName.SecretVersionV2Tag);
await knex.schema.dropTableIfExists(TableName.SecretVersionV2); await knex.schema.dropTableIfExists(TableName.SecretVersionV2);
await dropOnUpdateTrigger(knex, TableName.SecretV2);
await knex.schema.dropTableIfExists(TableName.SecretV2);
if (await knex.schema.hasTable(TableName.IntegrationAuth)) { if (await knex.schema.hasTable(TableName.IntegrationAuth)) {
const hasEncryptedAccess = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccess"); const hasEncryptedAccess = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccess");
const hasEncryptedAccessId = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccessId"); const hasEncryptedAccessId = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccessId");
@@ -160,7 +173,7 @@ export async function down(knex: Knex): Promise<void> {
if (hasEncryptedAccess) t.dropColumn("encryptedAccess"); if (hasEncryptedAccess) t.dropColumn("encryptedAccess");
if (hasEncryptedAccessId) t.dropColumn("encryptedAccessId"); if (hasEncryptedAccessId) t.dropColumn("encryptedAccessId");
if (hasEncryptedRefresh) t.dropColumn("encryptedRefresh"); if (hasEncryptedRefresh) t.dropColumn("encryptedRefresh");
if (hasEncryptedAwsIamAssumRole) t.dropColumn("encryptedAwsIamAssumRole"); if (hasEncryptedAwsIamAssumRole) t.dropColumn("encryptedAwsAssumeIamRoleArn");
}); });
} }
} }
+1
View File
@@ -103,3 +103,4 @@ export * from "./user-encryption-keys";
export * from "./user-group-membership"; export * from "./user-group-membership";
export * from "./users"; export * from "./users";
export * from "./webhooks"; export * from "./webhooks";
export * from "./secret-rotation-output-v2";
+2 -1
View File
@@ -35,10 +35,11 @@ export const IntegrationAuthsSchema = z.object({
awsAssumeIamRoleArnCipherText: z.string().nullable().optional(), awsAssumeIamRoleArnCipherText: z.string().nullable().optional(),
awsAssumeIamRoleArnIV: z.string().nullable().optional(), awsAssumeIamRoleArnIV: z.string().nullable().optional(),
awsAssumeIamRoleArnTag: z.string().nullable().optional(), awsAssumeIamRoleArnTag: z.string().nullable().optional(),
encryptedAwsIamAssumRole: zodBuffer.nullable().optional(),
encryptedAccess: zodBuffer.nullable().optional(), encryptedAccess: zodBuffer.nullable().optional(),
encryptedAccessId: zodBuffer.nullable().optional(), encryptedAccessId: zodBuffer.nullable().optional(),
encryptedRefresh: zodBuffer.nullable().optional(), encryptedRefresh: zodBuffer.nullable().optional(),
encryptedAwsIamAssumRole: zodBuffer.nullable().optional() encryptedAwsAssumeIamRoleArn: zodBuffer.nullable().optional()
}); });
export type TIntegrationAuths = z.infer<typeof IntegrationAuthsSchema>; export type TIntegrationAuths = z.infer<typeof IntegrationAuthsSchema>;
+1
View File
@@ -101,6 +101,7 @@ export enum TableName {
JnSecretTag = "secret_tag_junction", JnSecretTag = "secret_tag_junction",
SecretVersionTag = "secret_version_tag_junction", SecretVersionTag = "secret_version_tag_junction",
SecretVersionV2Tag = "secret_version_v2_tag_junction", SecretVersionV2Tag = "secret_version_v2_tag_junction",
SecretRotationOutputV2 = "secret_rotation_output_v2",
// KMS Service // KMS Service
KmsServerRootConfig = "kms_root_config", KmsServerRootConfig = "kms_root_config",
KmsKey = "kms_keys", KmsKey = "kms_keys",
@@ -0,0 +1,21 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const SecretRotationOutputV2Schema = z.object({
id: z.string().uuid(),
key: z.string(),
secretId: z.string().uuid(),
rotationId: z.string().uuid()
});
export type TSecretRotationOutputV2 = z.infer<typeof SecretRotationOutputV2Schema>;
export type TSecretRotationOutputV2Insert = Omit<z.input<typeof SecretRotationOutputV2Schema>, TImmutableDBKeys>;
export type TSecretRotationOutputV2Update = Partial<
Omit<z.input<typeof SecretRotationOutputV2Schema>, TImmutableDBKeys>
>;
@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas"; import { SecretRotationOutputsSchema, SecretRotationsSchema } from "@app/db/schemas";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -112,18 +112,10 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
outputs: z outputs: z
.object({ .object({
key: z.string(), key: z.string(),
secret: SecretsSchema.pick({ secret: z.object({
id: true, secretKey: z.string(),
version: true, id: z.string(),
secretKeyIV: true, version: z.number()
secretKeyTag: true,
secretKeyCiphertext: true,
secretValueIV: true,
secretValueTag: true,
secretValueCiphertext: true,
secretCommentIV: true,
secretCommentTag: true,
secretCommentCiphertext: true
}) })
}) })
.array() .array()
@@ -10,6 +10,7 @@ export type TSecretRotationDALFactory = ReturnType<typeof secretRotationDALFacto
export const secretRotationDALFactory = (db: TDbClient) => { export const secretRotationDALFactory = (db: TDbClient) => {
const secretRotationOrm = ormify(db, TableName.SecretRotation); const secretRotationOrm = ormify(db, TableName.SecretRotation);
const secretRotationOutputOrm = ormify(db, TableName.SecretRotationOutput); const secretRotationOutputOrm = ormify(db, TableName.SecretRotationOutput);
const secretRotationOutputV2Orm = ormify(db, TableName.SecretRotationOutputV2);
const findQuery = (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx: Knex) => const findQuery = (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx: Knex) =>
tx(TableName.SecretRotation) tx(TableName.SecretRotation)
@@ -31,13 +32,7 @@ export const secretRotationDALFactory = (db: TDbClient) => {
.select(tx.ref("version").withSchema(TableName.Secret).as("secVersion")) .select(tx.ref("version").withSchema(TableName.Secret).as("secVersion"))
.select(tx.ref("secretKeyIV").withSchema(TableName.Secret)) .select(tx.ref("secretKeyIV").withSchema(TableName.Secret))
.select(tx.ref("secretKeyTag").withSchema(TableName.Secret)) .select(tx.ref("secretKeyTag").withSchema(TableName.Secret))
.select(tx.ref("secretKeyCiphertext").withSchema(TableName.Secret)) .select(tx.ref("secretKeyCiphertext").withSchema(TableName.Secret));
.select(tx.ref("secretValueIV").withSchema(TableName.Secret))
.select(tx.ref("secretValueTag").withSchema(TableName.Secret))
.select(tx.ref("secretValueCiphertext").withSchema(TableName.Secret))
.select(tx.ref("secretCommentIV").withSchema(TableName.Secret))
.select(tx.ref("secretCommentTag").withSchema(TableName.Secret))
.select(tx.ref("secretCommentCiphertext").withSchema(TableName.Secret));
const find = async (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx?: Knex) => { const find = async (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx?: Knex) => {
try { try {
@@ -54,33 +49,65 @@ export const secretRotationDALFactory = (db: TDbClient) => {
{ {
key: "secId", key: "secId",
label: "outputs" as const, label: "outputs" as const,
mapper: ({ mapper: ({ secId, outputKey, secVersion, secretKeyIV, secretKeyTag, secretKeyCiphertext }) => ({
secId,
outputKey,
secVersion,
secretKeyIV,
secretKeyTag,
secretKeyCiphertext,
secretValueTag,
secretValueIV,
secretValueCiphertext,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext
}) => ({
key: outputKey, key: outputKey,
secret: { secret: {
id: secId, id: secId,
version: secVersion, version: secVersion,
secretKeyIV, secretKeyIV,
secretKeyTag, secretKeyTag,
secretKeyCiphertext, secretKeyCiphertext
secretValueTag, }
secretValueIV, })
secretValueCiphertext, }
secretCommentIV, ]
secretCommentTag, });
secretCommentCiphertext } catch (error) {
throw new DatabaseError({ error, name: "SecretRotationFind" });
}
};
const findQuerySecretV2 = (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx: Knex) =>
tx(TableName.SecretRotation)
.where(filter)
.join(TableName.Environment, `${TableName.SecretRotation}.envId`, `${TableName.Environment}.id`)
.leftJoin(
TableName.SecretRotationOutputV2,
`${TableName.SecretRotation}.id`,
`${TableName.SecretRotationOutputV2}.rotationId`
)
.join(TableName.SecretV2, `${TableName.SecretRotationOutputV2}.secretId`, `${TableName.SecretV2}.id`)
.select(selectAllTableCols(TableName.SecretRotation))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
.select(tx.ref("projectId").withSchema(TableName.Environment))
.select(tx.ref("key").withSchema(TableName.SecretRotationOutputV2).as("outputKey"))
.select(tx.ref("id").withSchema(TableName.SecretV2).as("secId"))
.select(tx.ref("version").withSchema(TableName.SecretV2).as("secVersion"))
.select(tx.ref("key").withSchema(TableName.SecretV2).as("secretKey"));
const findSecretV2 = async (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx?: Knex) => {
try {
const data = await findQuerySecretV2(filter, tx || db.replicaNode());
return sqlNestRelationships({
data,
key: "id",
parentMapper: (el) => ({
...SecretRotationsSchema.parse(el),
projectId: el.projectId,
environment: { id: el.envId, name: el.envName, slug: el.envSlug }
}),
childrenMapper: [
{
key: "secId",
label: "outputs" as const,
mapper: ({ secId, outputKey, secVersion, secretKey }) => ({
key: outputKey,
secret: {
id: secId,
version: secVersion,
secretKey
} }
}) })
} }
@@ -114,12 +141,17 @@ export const secretRotationDALFactory = (db: TDbClient) => {
}; };
const findRotationOutputsByRotationId = async (rotationId: string) => secretRotationOutputOrm.find({ rotationId }); const findRotationOutputsByRotationId = async (rotationId: string) => secretRotationOutputOrm.find({ rotationId });
const findRotationOutputsV2ByRotationId = async (rotationId: string) =>
secretRotationOutputV2Orm.find({ rotationId });
return { return {
...secretRotationOrm, ...secretRotationOrm,
find, find,
findSecretV2,
findById, findById,
secretOutputInsertMany: secretRotationOutputOrm.insertMany, secretOutputInsertMany: secretRotationOutputOrm.insertMany,
findRotationOutputsByRotationId secretOutputV2InsertMany: secretRotationOutputV2Orm.insertMany,
findRotationOutputsByRotationId,
findRotationOutputsV2ByRotationId
}; };
}; };
@@ -121,7 +121,13 @@ export const secretRotationQueueFactory = ({
try { try {
if (!rotationProvider || !secretRotation) throw new DisableRotationErrors({ message: "Provider not found" }); if (!rotationProvider || !secretRotation) throw new DisableRotationErrors({ message: "Provider not found" });
const rotationOutputs = await secretRotationDAL.findRotationOutputsByRotationId(rotationId); const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(secretRotation.projectId);
let rotationOutputs;
if (shouldUseSecretV2Bridge) {
rotationOutputs = await secretRotationDAL.findRotationOutputsV2ByRotationId(rotationId);
} else {
rotationOutputs = await secretRotationDAL.findRotationOutputsByRotationId(rotationId);
}
if (!rotationOutputs.length) throw new DisableRotationErrors({ message: "Secrets not found" }); if (!rotationOutputs.length) throw new DisableRotationErrors({ message: "Secrets not found" });
// deep copy // deep copy
@@ -277,7 +283,6 @@ export const secretRotationQueueFactory = ({
internal: newCredential.internal internal: newCredential.internal
}); });
const encVarData = infisicalSymmetricEncypt(JSON.stringify(variables)); const encVarData = infisicalSymmetricEncypt(JSON.stringify(variables));
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(secretRotation.projectId);
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager, type: KmsDataKey.SecretManager,
projectId: secretRotation.projectId projectId: secretRotation.projectId
@@ -1,12 +1,15 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import Ajv from "ajv"; import Ajv from "ajv";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { ProjectVersion } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TSecretDALFactory } from "@app/services/secret/secret-dal"; import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
@@ -22,9 +25,11 @@ type TSecretRotationServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findById">; projectDAL: Pick<TProjectDALFactory, "findById">;
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">; folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
secretDAL: Pick<TSecretDALFactory, "find">; secretDAL: Pick<TSecretDALFactory, "find">;
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "find">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
secretRotationQueue: TSecretRotationQueueFactory; secretRotationQueue: TSecretRotationQueueFactory;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
}; };
export type TSecretRotationServiceFactory = ReturnType<typeof secretRotationServiceFactory>; export type TSecretRotationServiceFactory = ReturnType<typeof secretRotationServiceFactory>;
@@ -37,7 +42,9 @@ export const secretRotationServiceFactory = ({
licenseService, licenseService,
projectDAL, projectDAL,
folderDAL, folderDAL,
secretDAL secretDAL,
projectBotService,
secretV2BridgeDAL
}: TSecretRotationServiceFactoryDep) => { }: TSecretRotationServiceFactoryDep) => {
const getProviderTemplates = async ({ const getProviderTemplates = async ({
actor, actor,
@@ -92,15 +99,25 @@ export const secretRotationServiceFactory = ({
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath }) 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 project = await projectDAL.findById(projectId);
const shouldUseBridge = project.version === ProjectVersion.V3;
if (shouldUseBridge) {
const selectedSecrets = await secretV2BridgeDAL.find({
folderId: folder.id,
$in: { id: Object.values(outputs) }
});
if (selectedSecrets.length !== Object.values(outputs).length)
throw new BadRequestError({ message: "Secrets not found" });
} else {
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 plan = await licenseService.getPlan(project.orgId); const plan = await licenseService.getPlan(project.orgId);
if (!plan.secretRotation) if (!plan.secretRotation)
throw new BadRequestError({ throw new BadRequestError({
@@ -148,10 +165,18 @@ export const secretRotationServiceFactory = ({
}, },
tx tx
); );
const outputSecretMapping = await secretRotationDAL.secretOutputInsertMany( let outputSecretMapping;
Object.entries(outputs).map(([key, secretId]) => ({ key, secretId, rotationId: doc.id })), if (shouldUseBridge) {
tx outputSecretMapping = await secretRotationDAL.secretOutputV2InsertMany(
); Object.entries(outputs).map(([key, secretId]) => ({ key, secretId, rotationId: doc.id })),
tx
);
} else {
outputSecretMapping = await secretRotationDAL.secretOutputInsertMany(
Object.entries(outputs).map(([key, secretId]) => ({ key, secretId, rotationId: doc.id })),
tx
);
}
return { ...doc, outputs: outputSecretMapping, environment: folder.environment }; return { ...doc, outputs: outputSecretMapping, environment: folder.environment };
}); });
await secretRotationQueue.addToQueue(secretRotation.id, secretRotation.interval); await secretRotationQueue.addToQueue(secretRotation.id, secretRotation.interval);
@@ -167,8 +192,30 @@ export const secretRotationServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
const doc = await secretRotationDAL.find({ projectId }); const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
return doc; if (shouldUseSecretV2Bridge) {
const docs = await secretRotationDAL.findSecretV2({ projectId });
return docs;
}
if (!botKey) throw new BadRequestError({ message: "bot not found" });
const docs = await secretRotationDAL.find({ projectId });
return docs.map((el) => ({
...el,
outputs: el.outputs.map((output) => ({
...output,
secret: {
id: output.secret.id,
version: output.secret.version,
secretKey: decryptSymmetric128BitHexKeyUTF8({
ciphertext: output.secret.secretKeyCiphertext,
iv: output.secret.secretKeyIV,
tag: output.secret.secretKeyTag,
key: botKey
})
}
}))
}));
}; };
const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => { const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => {
+3 -1
View File
@@ -869,7 +869,9 @@ export const registerRoutes = async (
projectDAL, projectDAL,
licenseService, licenseService,
secretDAL, secretDAL,
folderDAL folderDAL,
projectBotService,
secretV2BridgeDAL
}); });
const integrationService = integrationServiceFactory({ const integrationService = integrationServiceFactory({
@@ -1,14 +1,9 @@
import { useCallback } from "react";
import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import {
decryptAssymmetric,
decryptSymmetric
} from "@app/components/utilities/cryptography/crypto";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { import {
TGetSecretRotationList, TGetSecretRotationListDTO,
TGetSecretRotationProviders, TGetSecretRotationProviders,
TSecretRotation, TSecretRotation,
TSecretRotationProviderList TSecretRotationProviderList
@@ -19,7 +14,7 @@ export const secretRotationKeys = {
{ workspaceId }, { workspaceId },
"secret-rotation-providers" "secret-rotation-providers"
], ],
list: ({ workspaceId }: Omit<TGetSecretRotationList, "decryptFileKey">) => list: ({ workspaceId }: Omit<TGetSecretRotationListDTO, "decryptFileKey">) =>
[{ workspaceId }, "secret-rotations"] as const [{ workspaceId }, "secret-rotations"] as const
}; };
@@ -53,7 +48,7 @@ export const useGetSecretRotationProviders = ({
const fetchSecretRotations = async ({ const fetchSecretRotations = async ({
workspaceId workspaceId
}: Omit<TGetSecretRotationList, "decryptFileKey">) => { }: Omit<TGetSecretRotationListDTO, "decryptFileKey">) => {
const { data } = await apiRequest.get<{ secretRotations: TSecretRotation[] }>( const { data } = await apiRequest.get<{ secretRotations: TSecretRotation[] }>(
"/api/v1/secret-rotations", "/api/v1/secret-rotations",
{ params: { workspaceId } } { params: { workspaceId } }
@@ -63,14 +58,13 @@ const fetchSecretRotations = async ({
export const useGetSecretRotations = ({ export const useGetSecretRotations = ({
workspaceId, workspaceId,
decryptFileKey,
options = {} options = {}
}: TGetSecretRotationList & { }: TGetSecretRotationListDTO & {
options?: Omit< options?: Omit<
UseQueryOptions< UseQueryOptions<
TSecretRotation[], TSecretRotation[],
unknown, unknown,
TSecretRotation<{ key: string }>[], TSecretRotation[],
ReturnType<typeof secretRotationKeys.list> ReturnType<typeof secretRotationKeys.list>
>, >,
"queryKey" | "queryFn" "queryKey" | "queryFn"
@@ -80,31 +74,5 @@ export const useGetSecretRotations = ({
...options, ...options,
queryKey: secretRotationKeys.list({ workspaceId }), queryKey: secretRotationKeys.list({ workspaceId }),
enabled: Boolean(workspaceId) && (options?.enabled ?? true), enabled: Boolean(workspaceId) && (options?.enabled ?? true),
queryFn: async () => fetchSecretRotations({ workspaceId }), queryFn: async () => fetchSecretRotations({ workspaceId })
select: useCallback(
(data: TSecretRotation[]) => {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
const decryptKey = decryptAssymmetric({
ciphertext: decryptFileKey.encryptedKey,
nonce: decryptFileKey.nonce,
publicKey: decryptFileKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
return data.map((el) => ({
...el,
outputs: el.outputs.map(({ key, secret }) => ({
key,
secret: {
key: decryptSymmetric({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key: decryptKey
})
}
}))
}));
},
[decryptFileKey]
)
}); });
@@ -1,5 +1,3 @@
import { UserWsKeyPair } from "../keys/types";
import { EncryptedSecret } from "../secrets/types";
import { WorkspaceEnv } from "../workspace/types"; import { WorkspaceEnv } from "../workspace/types";
export enum TProviderFunctionTypes { export enum TProviderFunctionTypes {
@@ -74,7 +72,7 @@ export type TDbProviderTemplate = {
outputs: Record<string, unknown>; outputs: Record<string, unknown>;
}; };
export type TSecretRotation<T extends unknown = EncryptedSecret> = { export type TSecretRotation = {
id: string; id: string;
interval: number; interval: number;
provider: string; provider: string;
@@ -85,7 +83,11 @@ export type TSecretRotation<T extends unknown = EncryptedSecret> = {
secretPath: string; secretPath: string;
outputs: Array<{ outputs: Array<{
key: string; key: string;
secret: T; secret: {
version: number;
id: string;
secretKey: string;
};
}>; }>;
status?: "success" | "failed"; status?: "success" | "failed";
lastRotatedAt?: string; lastRotatedAt?: string;
@@ -103,9 +105,8 @@ export type TGetSecretRotationProviders = {
workspaceId: string; workspaceId: string;
}; };
export type TGetSecretRotationList = { export type TGetSecretRotationListDTO = {
workspaceId: string; workspaceId: string;
decryptFileKey: UserWsKeyPair;
}; };
export type TCreateSecretRotationDTO = { export type TCreateSecretRotationDTO = {
@@ -1,4 +1,3 @@
import { useTranslation } from "react-i18next";
import Link from "next/link"; import Link from "next/link";
import { import {
faArrowsSpin, faArrowsSpin,
@@ -16,12 +15,9 @@ import { formatDistance } from "date-fns";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
Button,
DeleteActionModal, DeleteActionModal,
EmptyState, EmptyState,
IconButton, IconButton,
Modal,
ModalContent,
Skeleton, Skeleton,
Spinner, Spinner,
Table, Table,
@@ -48,22 +44,17 @@ import {
useDeleteSecretRotation, useDeleteSecretRotation,
useGetSecretRotationProviders, useGetSecretRotationProviders,
useGetSecretRotations, useGetSecretRotations,
useGetUserWsKey, useRestartSecretRotation
useGetWorkspaceBot,
useRestartSecretRotation,
useUpdateBotActiveStatus
} from "@app/hooks/api"; } from "@app/hooks/api";
import { TSecretRotationProviderTemplate } from "@app/hooks/api/types"; import { TSecretRotationProviderTemplate } from "@app/hooks/api/types";
import { CreateRotationForm } from "./components/CreateRotationForm"; import { CreateRotationForm } from "./components/CreateRotationForm";
import { generateBotKey } from "./SecretRotationPage.utils";
export const SecretRotationPage = withProjectPermission( export const SecretRotationPage = withProjectPermission(
() => { () => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { t } = useTranslation();
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
"createRotation", "createRotation",
"activeBot", "activeBot",
@@ -77,13 +68,10 @@ export const SecretRotationPage = withProjectPermission(
); );
const { subscription } = useSubscription(); const { subscription } = useSubscription();
const { data: userWsKey } = useGetUserWsKey(workspaceId);
const { data: secretRotationProviders, isLoading: isRotationProviderLoading } = const { data: secretRotationProviders, isLoading: isRotationProviderLoading } =
useGetSecretRotationProviders({ workspaceId }); useGetSecretRotationProviders({ workspaceId });
const { data: secretRotations, isLoading: isRotationLoading } = useGetSecretRotations({ const { data: secretRotations, isLoading: isRotationLoading } = useGetSecretRotations({
workspaceId, workspaceId
decryptFileKey: userWsKey!
}); });
const { const {
@@ -97,11 +85,6 @@ export const SecretRotationPage = withProjectPermission(
isLoading: isRestartingRotation isLoading: isRestartingRotation
} = useRestartSecretRotation(); } = useRestartSecretRotation();
const { data: bot } = useGetWorkspaceBot(workspaceId);
const { mutateAsync: updateBotActiveStatus } = useUpdateBotActiveStatus();
const isBotActive = Boolean(bot?.isActive);
const handleDeleteRotation = async () => { const handleDeleteRotation = async () => {
const { id } = popUp.deleteRotation.data as { id: string }; const { id } = popUp.deleteRotation.data as { id: string };
try { try {
@@ -142,29 +125,6 @@ export const SecretRotationPage = withProjectPermission(
} }
}; };
const handleUserAcceptBotCondition = async () => {
const provider = popUp.activeBot?.data as TSecretRotationProviderTemplate;
try {
if (bot?.id) {
const botKey = generateBotKey(bot.publicKey, userWsKey!);
await updateBotActiveStatus({
isActive: true,
botId: bot.id,
workspaceId,
botKey
});
}
handlePopUpOpen("createRotation", provider);
handlePopUpClose("activeBot");
} catch (error) {
console.log(error);
createNotification({
type: "error",
text: "Failed to create bot"
});
}
};
const handleCreateRotation = async (provider: TSecretRotationProviderTemplate) => { const handleCreateRotation = async (provider: TSecretRotationProviderTemplate) => {
if (subscription && !subscription?.secretRotation) { if (subscription && !subscription?.secretRotation) {
handlePopUpOpen("upgradePlan"); handlePopUpOpen("upgradePlan");
@@ -174,11 +134,7 @@ export const SecretRotationPage = withProjectPermission(
createNotification({ type: "error", text: "Access permission denied!!" }); createNotification({ type: "error", text: "Access permission denied!!" });
return; return;
} }
if (isBotActive) { handlePopUpOpen("createRotation", provider);
handlePopUpOpen("createRotation", provider);
} else {
handlePopUpOpen("activeBot", provider);
}
}; };
return ( return (
@@ -391,30 +347,6 @@ export const SecretRotationPage = withProjectPermission(
onToggle={(isOpen) => handlePopUpToggle("createRotation", isOpen)} onToggle={(isOpen) => handlePopUpToggle("createRotation", isOpen)}
provider={(popUp.createRotation.data as TSecretRotationProviderTemplate) || {}} provider={(popUp.createRotation.data as TSecretRotationProviderTemplate) || {}}
/> />
<Modal
isOpen={popUp.activeBot?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("activeBot", isOpen)}
>
<ModalContent
title={t("integrations.grant-access-to-secrets") as string}
footerContent={
<div className="flex items-center space-x-2">
<Button onClick={() => handleUserAcceptBotCondition()}>
{t("integrations.grant-access-button") as string}
</Button>
<Button
onClick={() => handlePopUpClose("activeBot")}
variant="outline_bg"
colorSchema="secondary"
>
Cancel
</Button>
</div>
}
>
{t("integrations.why-infisical-needs-access")}
</ModalContent>
</Modal>
<DeleteActionModal <DeleteActionModal
isOpen={popUp.deleteRotation.isOpen} isOpen={popUp.deleteRotation.isOpen}
title="Are you sure want to delete this rotation?" title="Are you sure want to delete this rotation?"