Merge pull request #2897 from Infisical/feat/resource-metadata

feat: resource metadata
This commit is contained in:
Maidul Islam
2025-01-08 14:47:01 -05:00
committed by GitHub
46 changed files with 864 additions and 137 deletions
+8
View File
@@ -218,6 +218,9 @@ import {
TRateLimit, TRateLimit,
TRateLimitInsert, TRateLimitInsert,
TRateLimitUpdate, TRateLimitUpdate,
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate,
TSamlConfigs, TSamlConfigs,
TSamlConfigsInsert, TSamlConfigsInsert,
TSamlConfigsUpdate, TSamlConfigsUpdate,
@@ -887,6 +890,11 @@ declare module "knex/types/tables" {
TProjectSplitBackfillIdsInsert, TProjectSplitBackfillIdsInsert,
TProjectSplitBackfillIdsUpdate TProjectSplitBackfillIdsUpdate
>; >;
[TableName.ResourceMetadata]: KnexOriginal.CompositeTableType<
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate
>;
[TableName.AppConnection]: KnexOriginal.CompositeTableType< [TableName.AppConnection]: KnexOriginal.CompositeTableType<
TAppConnections, TAppConnections,
TAppConnectionsInsert, TAppConnectionsInsert,
@@ -0,0 +1,40 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ResourceMetadata))) {
await knex.schema.createTable(TableName.ResourceMetadata, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.string("key").notNullable();
tb.string("value", 1020).notNullable();
tb.uuid("orgId").notNullable();
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
tb.uuid("userId");
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
tb.uuid("identityId");
tb.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
tb.uuid("secretId");
tb.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
tb.timestamps(true, true, true);
});
}
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
if (!hasSecretMetadataField) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.jsonb("secretMetadata");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ResourceMetadata);
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
if (hasSecretMetadataField) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.dropColumn("secretMetadata");
});
}
}
+1
View File
@@ -71,6 +71,7 @@ export * from "./project-user-additional-privilege";
export * from "./project-user-membership-roles"; export * from "./project-user-membership-roles";
export * from "./projects"; export * from "./projects";
export * from "./rate-limit"; export * from "./rate-limit";
export * from "./resource-metadata";
export * from "./saml-configs"; export * from "./saml-configs";
export * from "./scim-tokens"; export * from "./scim-tokens";
export * from "./secret-approval-policies"; export * from "./secret-approval-policies";
+1
View File
@@ -80,6 +80,7 @@ export enum TableName {
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege", IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
// used by both identity and users // used by both identity and users
IdentityMetadata = "identity_metadata", IdentityMetadata = "identity_metadata",
ResourceMetadata = "resource_metadata",
ScimToken = "scim_tokens", ScimToken = "scim_tokens",
AccessApprovalPolicy = "access_approval_policies", AccessApprovalPolicy = "access_approval_policies",
AccessApprovalPolicyApprover = "access_approval_policies_approvers", AccessApprovalPolicyApprover = "access_approval_policies_approvers",
@@ -0,0 +1,24 @@
// 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 ResourceMetadataSchema = z.object({
id: z.string().uuid(),
key: z.string(),
value: z.string(),
orgId: z.string().uuid(),
userId: z.string().uuid().nullable().optional(),
identityId: z.string().uuid().nullable().optional(),
secretId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TResourceMetadata = z.infer<typeof ResourceMetadataSchema>;
export type TResourceMetadataInsert = Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>;
export type TResourceMetadataUpdate = Partial<Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>>;
@@ -24,7 +24,8 @@ export const SecretApprovalRequestsSecretsV2Schema = z.object({
requestId: z.string().uuid(), requestId: z.string().uuid(),
op: z.string(), op: z.string(),
secretId: z.string().uuid().nullable().optional(), secretId: z.string().uuid().nullable().optional(),
secretVersion: z.string().uuid().nullable().optional() secretVersion: z.string().uuid().nullable().optional(),
secretMetadata: z.unknown().nullable().optional()
}); });
export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>; export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>;
@@ -12,6 +12,7 @@ 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";
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas"; import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge( const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge(
UsersSchema.pick({ UsersSchema.pick({
@@ -274,6 +275,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
.extend({ .extend({
op: z.string(), op: z.string(),
tags: tagSchema, tags: tagSchema,
secretMetadata: ResourceMetadataSchema.nullish(),
secret: z secret: z
.object({ .object({
id: z.string(), id: z.string(),
@@ -291,7 +293,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
secretKey: z.string(), secretKey: z.string(),
secretValue: z.string().optional(), secretValue: z.string().optional(),
secretComment: z.string().optional(), secretComment: z.string().optional(),
tags: tagSchema tags: tagSchema,
secretMetadata: ResourceMetadataSchema.nullish()
}) })
.optional() .optional()
}) })
@@ -256,6 +256,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`, `${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
db.ref("id").withSchema("secVerTag") db.ref("id").withSchema("secVerTag")
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2)) .select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2))
.select({ .select({
secVerTagId: "secVerTag.id", secVerTagId: "secVerTag.id",
@@ -279,6 +280,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"), db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"), db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment") db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment")
)
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
); );
const formatedDoc = sqlNestRelationships({ const formatedDoc = sqlNestRelationships({
data: doc, data: doc,
@@ -338,9 +344,19 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
}) })
} }
] ]
},
{
key: "metadataId",
label: "oldSecretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({ return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({
...el, ...el,
secret: secret?.[0], secret: secret?.[0],
@@ -22,6 +22,8 @@ import { KmsDataKey } from "@app/services/kms/kms-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 { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "@app/services/secret/secret-dal"; import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { import {
decryptSecretWithBot, decryptSecretWithBot,
@@ -91,6 +93,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">; secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">; snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">; secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">; secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
smtpService: Pick<TSmtpService, "sendMail">; smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">; userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">;
@@ -138,7 +141,8 @@ export const secretApprovalRequestServiceFactory = ({
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
licenseService, licenseService,
projectSlackConfigDAL projectSlackConfigDAL,
resourceMetadataDAL
}: 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" });
@@ -241,6 +245,7 @@ export const secretApprovalRequestServiceFactory = ({
secretKey: el.key, secretKey: el.key,
id: el.id, id: el.id,
version: el.version, version: el.version,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "", secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
secretComment: el.encryptedComment secretComment: el.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
@@ -269,7 +274,8 @@ export const secretApprovalRequestServiceFactory = ({
secretComment: el.secretVersion.encryptedComment secretComment: el.secretVersion.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
: "", : "",
tags: el.secretVersion.tags tags: el.secretVersion.tags,
secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO
} }
: undefined : undefined
})); }));
@@ -543,6 +549,7 @@ export const secretApprovalRequestServiceFactory = ({
? await fnSecretV2BridgeBulkInsert({ ? await fnSecretV2BridgeBulkInsert({
tx, tx,
folderId, folderId,
orgId: actorOrgId,
inputSecrets: secretCreationCommits.map((el) => ({ inputSecrets: secretCreationCommits.map((el) => ({
tagIds: el?.tags.map(({ id }) => id), tagIds: el?.tags.map(({ id }) => id),
version: 1, version: 1,
@@ -550,6 +557,7 @@ export const secretApprovalRequestServiceFactory = ({
encryptedValue: el.encryptedValue, encryptedValue: el.encryptedValue,
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key, key: el.key,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
references: el.encryptedValue references: el.encryptedValue
? getAllSecretReferencesV2Bridge( ? getAllSecretReferencesV2Bridge(
secretManagerDecryptor({ secretManagerDecryptor({
@@ -559,6 +567,7 @@ export const secretApprovalRequestServiceFactory = ({
: [], : [],
type: SecretType.Shared type: SecretType.Shared
})), })),
resourceMetadataDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL, secretTagDAL,
@@ -568,6 +577,7 @@ export const secretApprovalRequestServiceFactory = ({
const updatedSecrets = secretUpdationCommits.length const updatedSecrets = secretUpdationCommits.length
? await fnSecretV2BridgeBulkUpdate({ ? await fnSecretV2BridgeBulkUpdate({
folderId, folderId,
orgId: actorOrgId,
tx, tx,
inputSecrets: secretUpdationCommits.map((el) => { inputSecrets: secretUpdationCommits.map((el) => {
const encryptedValue = const encryptedValue =
@@ -592,6 +602,7 @@ export const secretApprovalRequestServiceFactory = ({
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key, key: el.key,
tags: el?.tags.map(({ id }) => id), tags: el?.tags.map(({ id }) => id),
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
...encryptedValue ...encryptedValue
} }
}; };
@@ -599,7 +610,8 @@ export const secretApprovalRequestServiceFactory = ({
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL: secretVersionTagV2BridgeDAL secretVersionTagDAL: secretVersionTagV2BridgeDAL,
resourceMetadataDAL
}) })
: []; : [];
const deletedSecret = secretDeletionCommits.length const deletedSecret = secretDeletionCommits.length
@@ -824,6 +836,7 @@ export const secretApprovalRequestServiceFactory = ({
} }
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId: actorOrgId,
secretPath: folder.path, secretPath: folder.path,
environmentSlug: folder.environmentSlug, environmentSlug: folder.environmentSlug,
actorId, actorId,
@@ -1208,6 +1221,7 @@ export const secretApprovalRequestServiceFactory = ({
), ),
skipMultilineEncoding: createdSecret.skipMultilineEncoding, skipMultilineEncoding: createdSecret.skipMultilineEncoding,
key: createdSecret.secretKey, key: createdSecret.secretKey,
secretMetadata: createdSecret.secretMetadata,
type: SecretType.Shared type: SecretType.Shared
})) }))
); );
@@ -1263,12 +1277,14 @@ export const secretApprovalRequestServiceFactory = ({
reminderNote, reminderNote,
secretComment, secretComment,
metadata, metadata,
skipMultilineEncoding skipMultilineEncoding,
secretMetadata
}) => { }) => {
const secretId = updatingSecretsGroupByKey[secretKey][0].id; const secretId = updatingSecretsGroupByKey[secretKey][0].id;
if (tagIds?.length) commitTagIds[secretKey] = tagIds; if (tagIds?.length) commitTagIds[secretKey] = tagIds;
return { return {
...latestSecretVersions[secretId], ...latestSecretVersions[secretId],
secretMetadata,
key: newSecretName || secretKey, key: newSecretName || secretKey,
encryptedComment: setKnexStringValue( encryptedComment: setKnexStringValue(
secretComment, secretComment,
@@ -1370,7 +1386,8 @@ export const secretApprovalRequestServiceFactory = ({
reminderRepeatDays, reminderRepeatDays,
encryptedValue, encryptedValue,
secretId, secretId,
secretVersion secretVersion,
secretMetadata
}) => ({ }) => ({
version, version,
requestId: doc.id, requestId: doc.id,
@@ -1383,7 +1400,8 @@ export const secretApprovalRequestServiceFactory = ({
reminderRepeatDays, reminderRepeatDays,
reminderNote, reminderNote,
encryptedComment, encryptedComment,
key key,
secretMetadata: JSON.stringify(secretMetadata)
}) })
), ),
tx tx
@@ -1,5 +1,6 @@
import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas"; import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretOperations } from "@app/services/secret/secret-types"; import { SecretOperations } from "@app/services/secret/secret-types";
export enum RequestState { export enum RequestState {
@@ -34,6 +35,7 @@ export type TApprovalCreateSecretV2Bridge = {
reminderRepeatDays?: number | null; reminderRepeatDays?: number | null;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
metadata?: Record<string, string>; metadata?: Record<string, string>;
secretMetadata?: ResourceMetadataDTO;
tagIds?: string[]; tagIds?: string[];
}; };
@@ -13,6 +13,8 @@ import { ActorType } from "@app/services/auth/auth-type";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "@app/services/secret/secret-dal"; import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns"; import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue"; import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
@@ -56,6 +58,7 @@ type TSecretReplicationServiceFactoryDep = {
>; >;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">; secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
secretVersionV2TagBridgeDAL: Pick<TSecretVersionV2TagDALFactory, "find" | "insertMany">; secretVersionV2TagBridgeDAL: Pick<TSecretVersionV2TagDALFactory, "find" | "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "replicateSecrets">; secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "replicateSecrets">;
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">; queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">;
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">; secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
@@ -121,7 +124,8 @@ export const secretReplicationServiceFactory = ({
secretVersionV2TagBridgeDAL, secretVersionV2TagBridgeDAL,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
kmsService kmsService,
resourceMetadataDAL
}: TSecretReplicationServiceFactoryDep) => { }: TSecretReplicationServiceFactoryDep) => {
const $getReplicatedSecrets = ( const $getReplicatedSecrets = (
botKey: string, botKey: string,
@@ -151,8 +155,10 @@ export const secretReplicationServiceFactory = ({
}; };
const $getReplicatedSecretsV2 = ( const $getReplicatedSecretsV2 = (
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[], localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[],
importedSecrets: { secrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[] }[] importedSecrets: {
secrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[];
}[]
) => { ) => {
const deDupe = new Set<string>(); const deDupe = new Set<string>();
const secrets = [...localSecrets]; const secrets = [...localSecrets];
@@ -178,6 +184,7 @@ export const secretReplicationServiceFactory = ({
secretPath, secretPath,
environmentSlug, environmentSlug,
projectId, projectId,
orgId,
actorId, actorId,
actor, actor,
pickOnlyImportIds, pickOnlyImportIds,
@@ -222,6 +229,7 @@ export const secretReplicationServiceFactory = ({
.map(({ folderId }) => .map(({ folderId }) =>
secretQueueService.replicateSecrets({ secretQueueService.replicateSecrets({
projectId, projectId,
orgId,
secretPath: foldersGroupedById[folderId][0]?.path as string, secretPath: foldersGroupedById[folderId][0]?.path as string,
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string, environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
actorId, actorId,
@@ -267,6 +275,7 @@ export const secretReplicationServiceFactory = ({
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
: undefined : undefined
})); }));
const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets); const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets);
const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key); const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key);
@@ -333,13 +342,29 @@ export const secretReplicationServiceFactory = ({
.map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create .map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create
const locallyUpdatedSecrets = sourceSecrets const locallyUpdatedSecrets = sourceSecrets
.filter( .filter(({ key, secretKey, secretValue, secretMetadata }) => {
({ key, secretKey, secretValue }) => const sourceSecretMetadataJson = JSON.stringify(
(secretMetadata ?? []).map((entry) => ({
key: entry.key,
value: entry.value
}))
);
const destinationSecretMetadataJson = JSON.stringify(
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretMetadata ?? []).map((entry) => ({
key: entry.key,
value: entry.value
}))
);
return (
destinationLocalSecretsGroupedByKey[key]?.[0] && destinationLocalSecretsGroupedByKey[key]?.[0] &&
// if key or value changed // if key or value changed
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretKey !== secretKey || (destinationLocalSecretsGroupedByKey[key]?.[0]?.secretKey !== secretKey ||
destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue) destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue ||
) sourceSecretMetadataJson !== destinationSecretMetadataJson)
);
})
.map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create .map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create
const locallyDeletedSecrets = destinationLocalSecrets const locallyDeletedSecrets = destinationLocalSecrets
@@ -387,6 +412,7 @@ export const secretReplicationServiceFactory = ({
op: operation, op: operation,
requestId: approvalRequestDoc.id, requestId: approvalRequestDoc.id,
metadata: doc.metadata, metadata: doc.metadata,
secretMetadata: JSON.stringify(doc.secretMetadata),
key: doc.key, key: doc.key,
encryptedValue: doc.encryptedValue, encryptedValue: doc.encryptedValue,
encryptedComment: doc.encryptedComment, encryptedComment: doc.encryptedComment,
@@ -406,10 +432,12 @@ export const secretReplicationServiceFactory = ({
if (locallyCreatedSecrets.length) { if (locallyCreatedSecrets.length) {
await fnSecretV2BridgeBulkInsert({ await fnSecretV2BridgeBulkInsert({
folderId: destinationReplicationFolderId, folderId: destinationReplicationFolderId,
orgId,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
tx, tx,
secretTagDAL, secretTagDAL,
resourceMetadataDAL,
secretVersionTagDAL: secretVersionV2TagBridgeDAL, secretVersionTagDAL: secretVersionV2TagBridgeDAL,
inputSecrets: locallyCreatedSecrets.map((doc) => { inputSecrets: locallyCreatedSecrets.map((doc) => {
return { return {
@@ -419,6 +447,7 @@ export const secretReplicationServiceFactory = ({
encryptedValue: doc.encryptedValue, encryptedValue: doc.encryptedValue,
encryptedComment: doc.encryptedComment, encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding, skipMultilineEncoding: doc.skipMultilineEncoding,
secretMetadata: doc.secretMetadata,
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : [] references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
}; };
}) })
@@ -426,10 +455,12 @@ export const secretReplicationServiceFactory = ({
} }
if (locallyUpdatedSecrets.length) { if (locallyUpdatedSecrets.length) {
await fnSecretV2BridgeBulkUpdate({ await fnSecretV2BridgeBulkUpdate({
orgId,
folderId: destinationReplicationFolderId, folderId: destinationReplicationFolderId,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
tx, tx,
resourceMetadataDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL: secretVersionV2TagBridgeDAL, secretVersionTagDAL: secretVersionV2TagBridgeDAL,
inputSecrets: locallyUpdatedSecrets.map((doc) => { inputSecrets: locallyUpdatedSecrets.map((doc) => {
@@ -445,6 +476,7 @@ export const secretReplicationServiceFactory = ({
encryptedValue: doc.encryptedValue as Buffer, encryptedValue: doc.encryptedValue as Buffer,
encryptedComment: doc.encryptedComment, encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding, skipMultilineEncoding: doc.skipMultilineEncoding,
secretMetadata: doc.secretMetadata,
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : [] references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
} }
}; };
@@ -466,6 +498,7 @@ export const secretReplicationServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId,
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environmentSlug, environmentSlug: destinationFolder.environmentSlug,
actorId, actorId,
@@ -751,6 +784,7 @@ export const secretReplicationServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId,
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environmentSlug, environmentSlug: destinationFolder.environmentSlug,
actorId, actorId,
+2 -1
View File
@@ -1150,7 +1150,8 @@ export const INTEGRATION = {
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.", shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.", shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
shouldEnableDelete: "The flag to enable deletion of secrets.", shouldEnableDelete: "The flag to enable deletion of secrets.",
octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy." octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy.",
metadataSyncMode: "The mode for syncing metadata to external system"
} }
}, },
UPDATE: { UPDATE: {
+13 -5
View File
@@ -181,6 +181,7 @@ import { projectUserMembershipRoleDALFactory } from "@app/services/project-membe
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue"; import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service"; import { secretServiceFactory } from "@app/services/secret/secret-service";
@@ -374,6 +375,7 @@ export const registerRoutes = async (
const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db); const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db);
const projectTemplateDAL = projectTemplateDALFactory(db); const projectTemplateDAL = projectTemplateDALFactory(db);
const resourceMetadataDAL = resourceMetadataDALFactory(db);
const permissionService = permissionServiceFactory({ const permissionService = permissionServiceFactory({
permissionDAL, permissionDAL,
@@ -854,7 +856,8 @@ export const registerRoutes = async (
secretApprovalRequestDAL, secretApprovalRequestDAL,
projectKeyDAL, projectKeyDAL,
projectUserMembershipRoleDAL, projectUserMembershipRoleDAL,
orgService orgService,
resourceMetadataDAL
}); });
const projectService = projectServiceFactory({ const projectService = projectServiceFactory({
@@ -980,7 +983,8 @@ export const registerRoutes = async (
secretApprovalPolicyService, secretApprovalPolicyService,
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
kmsService, kmsService,
snapshotService snapshotService,
resourceMetadataDAL
}); });
const secretApprovalRequestService = secretApprovalRequestServiceFactory({ const secretApprovalRequestService = secretApprovalRequestServiceFactory({
@@ -1007,7 +1011,8 @@ export const registerRoutes = async (
projectEnvDAL, projectEnvDAL,
userDAL, userDAL,
licenseService, licenseService,
projectSlackConfigDAL projectSlackConfigDAL,
resourceMetadataDAL
}); });
const secretService = secretServiceFactory({ const secretService = secretServiceFactory({
@@ -1086,8 +1091,10 @@ export const registerRoutes = async (
kmsService, kmsService,
secretV2BridgeDAL, secretV2BridgeDAL,
secretVersionV2TagBridgeDAL: secretVersionTagV2BridgeDAL, secretVersionV2TagBridgeDAL: secretVersionTagV2BridgeDAL,
secretVersionV2BridgeDAL secretVersionV2BridgeDAL,
resourceMetadataDAL
}); });
const secretRotationQueue = secretRotationQueueFactory({ const secretRotationQueue = secretRotationQueueFactory({
telemetryService, telemetryService,
secretRotationDAL, secretRotationDAL,
@@ -1339,7 +1346,8 @@ export const registerRoutes = async (
folderDAL, folderDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
queueService, queueService,
secretV2BridgeService secretV2BridgeService,
resourceMetadataDAL
}); });
const migrationService = externalMigrationServiceFactory({ const migrationService = externalMigrationServiceFactory({
@@ -17,6 +17,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedDynamicSecretSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas"; import { SanitizedDynamicSecretSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretsOrderBy } from "@app/services/secret/secret-types"; import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@@ -116,6 +117,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
@@ -408,6 +410,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
@@ -693,6 +696,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
@@ -864,6 +868,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
+18 -3
View File
@@ -18,6 +18,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type"; import { ActorType, AuthMode } from "@app/services/auth/auth-type";
import { ProjectFilterType } from "@app/services/project/project-types"; import { ProjectFilterType } from "@app/services/project/project-types";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretOperations, SecretProtectionType } from "@app/services/secret/secret-types"; import { SecretOperations, SecretProtectionType } from "@app/services/secret/secret-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@@ -205,6 +206,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
@@ -220,7 +222,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretPath: z.string(), secretPath: z.string(),
environment: z.string(), environment: z.string(),
folderId: z.string().optional(), folderId: z.string().optional(),
secrets: secretRawSchema.omit({ createdAt: true, updatedAt: true }).array() secrets: secretRawSchema
.omit({ createdAt: true, updatedAt: true })
.extend({
secretMetadata: ResourceMetadataSchema.optional()
})
.array()
}) })
.array() .array()
.optional() .optional()
@@ -348,7 +355,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}) })
.extend({ name: z.string() }) .extend({ name: z.string() })
.array() .array()
.optional() .optional(),
secretMetadata: ResourceMetadataSchema.optional()
}) })
}) })
} }
@@ -450,6 +458,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.CREATE.secretValue), .describe(RAW_SECRETS.CREATE.secretValue),
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment), secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds), tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding), skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type), type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type),
@@ -484,6 +493,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretValue: req.body.secretValue, secretValue: req.body.secretValue,
skipMultilineEncoding: req.body.skipMultilineEncoding, skipMultilineEncoding: req.body.skipMultilineEncoding,
secretComment: req.body.secretComment, secretComment: req.body.secretComment,
secretMetadata: req.body.secretMetadata,
tagIds: req.body.tagIds, tagIds: req.body.tagIds,
secretReminderNote: req.body.secretReminderNote, secretReminderNote: req.body.secretReminderNote,
secretReminderRepeatDays: req.body.secretReminderRepeatDays secretReminderRepeatDays: req.body.secretReminderRepeatDays
@@ -558,6 +568,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type), type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type),
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds), tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
metadata: z.record(z.string()).optional(), metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote), secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
secretReminderRepeatDays: z secretReminderRepeatDays: z
.number() .number()
@@ -595,8 +606,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretReminderNote: req.body.secretReminderNote, secretReminderNote: req.body.secretReminderNote,
metadata: req.body.metadata, metadata: req.body.metadata,
newSecretName: req.body.newSecretName, newSecretName: req.body.newSecretName,
secretComment: req.body.secretComment secretComment: req.body.secretComment,
secretMetadata: req.body.secretMetadata
}); });
if (secretOperation.type === SecretProtectionType.Approval) { if (secretOperation.type === SecretProtectionType.Approval) {
return { approval: secretOperation.approval }; return { approval: secretOperation.approval };
} }
@@ -1850,6 +1863,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment), secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding), skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
metadata: z.record(z.string()).optional(), metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds) tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds)
}) })
.array() .array()
@@ -1952,6 +1966,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName), newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName),
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds), tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote), secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
secretMetadata: ResourceMetadataSchema.optional(),
secretReminderRepeatDays: z secretReminderRepeatDays: z
.number() .number()
.optional() .optional()
@@ -16,6 +16,7 @@ import { TProjectDALFactory } from "../project/project-dal";
import { TProjectServiceFactory } from "../project/project-service"; import { TProjectServiceFactory } from "../project/project-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service"; import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@@ -35,6 +36,8 @@ export type TImportDataIntoInfisicalDTO = {
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">; secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">; secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findById">; folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findById">;
projectService: Pick<TProjectServiceFactory, "createProject">; projectService: Pick<TProjectServiceFactory, "createProject">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">; projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
@@ -503,6 +506,7 @@ export const importDataIntoInfisicalFn = async ({
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
folderDAL, folderDAL,
resourceMetadataDAL,
input: { data, actor, actorId, actorOrgId, actorAuthMethod } input: { data, actor, actorId, actorOrgId, actorAuthMethod }
}: TImportDataIntoInfisicalDTO) => { }: TImportDataIntoInfisicalDTO) => {
// Import data to infisical // Import data to infisical
@@ -762,6 +766,8 @@ export const importDataIntoInfisicalFn = async ({
}; };
}), }),
folderId: selectedFolder.id, folderId: selectedFolder.id,
orgId: actorOrgId,
resourceMetadataDAL,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
@@ -8,6 +8,7 @@ import { TProjectDALFactory } from "../project/project-dal";
import { TProjectServiceFactory } from "../project/project-service"; import { TProjectServiceFactory } from "../project/project-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service"; import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@@ -35,6 +36,8 @@ export type TExternalMigrationQueueFactoryDep = {
projectService: Pick<TProjectServiceFactory, "createProject">; projectService: Pick<TProjectServiceFactory, "createProject">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">; projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">; secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>; export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>;
@@ -52,7 +55,8 @@ export const externalMigrationQueueFactory = ({
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
folderDAL folderDAL,
resourceMetadataDAL
}: TExternalMigrationQueueFactoryDep) => { }: TExternalMigrationQueueFactoryDep) => {
const startImport = async (dto: { const startImport = async (dto: {
actorEmail: string; actorEmail: string;
@@ -109,7 +113,8 @@ export const externalMigrationQueueFactory = ({
kmsService, kmsService,
projectService, projectService,
projectEnvService, projectEnvService,
secretV2BridgeService secretV2BridgeService,
resourceMetadataDAL
}); });
if (projectsNotImported.length) { if (projectsNotImported.length) {
@@ -427,3 +427,8 @@ export const getIntegrationOptions = async () => {
return INTEGRATION_OPTIONS; return INTEGRATION_OPTIONS;
}; };
export enum IntegrationMetadataSyncMode {
CUSTOM = "custom",
SECRET_METADATA = "secret-metadata"
}
@@ -38,6 +38,7 @@ import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/
import { TIntegrationDALFactory } from "../integration/integration-dal"; import { TIntegrationDALFactory } from "../integration/integration-dal";
import { IntegrationMetadataSchema } from "../integration/integration-schema"; import { IntegrationMetadataSchema } from "../integration/integration-schema";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { IntegrationAuthMetadataSchema } from "./integration-auth-schema"; import { IntegrationAuthMetadataSchema } from "./integration-auth-schema";
import { import {
CircleCiScope, CircleCiScope,
@@ -48,6 +49,7 @@ import {
import { import {
IntegrationInitialSyncBehavior, IntegrationInitialSyncBehavior,
IntegrationMappingBehavior, IntegrationMappingBehavior,
IntegrationMetadataSyncMode,
Integrations, Integrations,
IntegrationUrls IntegrationUrls
} from "./integration-list"; } from "./integration-list";
@@ -1082,14 +1084,14 @@ const syncSecretsAWSSecretManager = async ({
projectId projectId
}: { }: {
integration: TIntegrations; integration: TIntegrations;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string; secretMetadata?: ResourceMetadataDTO }>;
accessId: string | null; accessId: string | null;
accessToken: string; accessToken: string;
awsAssumeRoleArn: string | null; awsAssumeRoleArn: string | null;
projectId?: string; projectId?: string;
}) => { }) => {
const appCfg = getConfig(); const appCfg = getConfig();
const metadata = z.record(z.any()).parse(integration.metadata || {}); const metadata = IntegrationMetadataSchema.parse(integration.metadata || {});
if (!accessId && !awsAssumeRoleArn) { if (!accessId && !awsAssumeRoleArn) {
throw new Error("AWS access ID/AWS Assume Role is required"); throw new Error("AWS access ID/AWS Assume Role is required");
@@ -1137,8 +1139,25 @@ const syncSecretsAWSSecretManager = async ({
const processAwsSecret = async ( const processAwsSecret = async (
secretId: string, secretId: string,
secretValue: Record<string, string | null | undefined> | string secretValue: Record<string, string | null | undefined> | string,
secretMetadata?: ResourceMetadataDTO
) => { ) => {
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
const shouldTag =
(secretAWSTag && secretAWSTag.length) ||
(metadata.metadataSyncMode === IntegrationMetadataSyncMode.SECRET_METADATA &&
metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE);
const tagArray =
(metadata.metadataSyncMode === IntegrationMetadataSyncMode.SECRET_METADATA ? secretMetadata : secretAWSTag) ?? [];
const integrationTagObj = tagArray.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
try { try {
const awsSecretManagerSecret = await secretsManager.send( const awsSecretManagerSecret = await secretsManager.send(
new GetSecretValueCommand({ new GetSecretValueCommand({
@@ -1174,9 +1193,7 @@ const syncSecretsAWSSecretManager = async ({
} }
} }
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined; if (shouldTag) {
if (secretAWSTag && secretAWSTag.length) {
const describedSecret = await secretsManager.send( const describedSecret = await secretsManager.send(
// requires secretsmanager:DescribeSecret policy // requires secretsmanager:DescribeSecret policy
new DescribeSecretCommand({ new DescribeSecretCommand({
@@ -1186,14 +1203,6 @@ const syncSecretsAWSSecretManager = async ({
if (!describedSecret.Tags) return; if (!describedSecret.Tags) return;
const integrationTagObj = secretAWSTag.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
const awsTagObj = (describedSecret.Tags || []).reduce( const awsTagObj = (describedSecret.Tags || []).reduce(
(acc, item) => { (acc, item) => {
if (item.Key && item.Value) { if (item.Key && item.Value) {
@@ -1225,7 +1234,7 @@ const syncSecretsAWSSecretManager = async ({
} }
}); });
secretAWSTag?.forEach((tag) => { tagArray.forEach((tag) => {
if (!(tag.key in awsTagObj)) { if (!(tag.key in awsTagObj)) {
// create tag in AWS secret manager // create tag in AWS secret manager
tagsToUpdate.push({ tagsToUpdate.push({
@@ -1262,8 +1271,8 @@ const syncSecretsAWSSecretManager = async ({
Name: secretId, Name: secretId,
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue), SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue),
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }), ...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
Tags: metadata.secretAWSTag Tags: shouldTag
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ ? tagArray.map((tag: { key: string; value: string }) => ({
Key: tag.key, Key: tag.key,
Value: tag.value Value: tag.value
})) }))
@@ -1280,7 +1289,7 @@ const syncSecretsAWSSecretManager = async ({
if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) { if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) {
for await (const [key, value] of Object.entries(secrets)) { for await (const [key, value] of Object.entries(secrets)) {
await processAwsSecret(key, value.value); await processAwsSecret(key, value.value, value.secretMetadata);
} }
} else { } else {
await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets)); await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets));
@@ -4440,7 +4449,7 @@ export const syncIntegrationSecrets = async ({
secretPath: string; secretPath: string;
}; };
integrationAuth: TIntegrationAuths; integrationAuth: TIntegrationAuths;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string; secretMetadata?: ResourceMetadataDTO }>;
accessId: string | null; accessId: string | null;
awsAssumeRoleArn: string | null; awsAssumeRoleArn: string | null;
accessToken: string; accessToken: string;
@@ -2,7 +2,7 @@ import { z } from "zod";
import { INTEGRATION } from "@app/lib/api-docs"; import { INTEGRATION } from "@app/lib/api-docs";
import { IntegrationMappingBehavior } from "../integration-auth/integration-list"; import { IntegrationMappingBehavior, IntegrationMetadataSyncMode } from "../integration-auth/integration-list";
export const IntegrationMetadataSchema = z.object({ export const IntegrationMetadataSchema = z.object({
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir), initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
@@ -50,6 +50,11 @@ export const IntegrationMetadataSchema = z.object({
shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets), shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets),
shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets), shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets),
metadataSyncMode: z
.nativeEnum(IntegrationMetadataSyncMode)
.optional()
.describe(INTEGRATION.CREATE.metadata.metadataSyncMode),
octopusDeployScopeValues: z octopusDeployScopeValues: z
.object({ .object({
// in Octopus Deploy Scope Value Format // in Octopus Deploy Scope Value Format
@@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TResourceMetadataDALFactory = ReturnType<typeof resourceMetadataDALFactory>;
export const resourceMetadataDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.ResourceMetadata);
return orm;
};
@@ -0,0 +1,10 @@
import z from "zod";
export const ResourceMetadataSchema = z
.object({
key: z.string().trim().min(1),
value: z.string().trim().default("")
})
.array();
export type ResourceMetadataDTO = z.infer<typeof ResourceMetadataSchema>;
@@ -1,6 +1,7 @@
import { SecretType, TSecretImports, TSecrets, TSecretsV2 } from "@app/db/schemas"; import { SecretType, TSecretImports, TSecrets, TSecretsV2 } from "@app/db/schemas";
import { groupBy, unique } from "@app/lib/fn"; import { groupBy, unique } from "@app/lib/fn";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "../secret/secret-dal"; import { TSecretDALFactory } from "../secret/secret-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@@ -39,6 +40,7 @@ type TSecretImportSecretsV2 = {
// But for somereason ts consider ? and undefined explicit as different just ts things // But for somereason ts consider ? and undefined explicit as different just ts things
secretValue: string; secretValue: string;
secretComment: string; secretComment: string;
secretMetadata?: ResourceMetadataDTO;
})[]; })[];
}; };
@@ -160,6 +160,7 @@ export const secretImportServiceFactory = ({
if (secImport.isReplication && sourceFolder) { if (secImport.isReplication && sourceFolder) {
await secretQueueService.replicateSecrets({ await secretQueueService.replicateSecrets({
secretPath: secImport.importPath, secretPath: secImport.importPath,
orgId: actorOrgId,
projectId, projectId,
environmentSlug: importEnv.slug, environmentSlug: importEnv.slug,
pickOnlyImportIds: [secImport.id], pickOnlyImportIds: [secImport.id],
@@ -169,6 +170,7 @@ export const secretImportServiceFactory = ({
} else { } else {
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
orgId: actorOrgId,
projectId, projectId,
environmentSlug: environment, environmentSlug: environment,
actorId, actorId,
@@ -340,6 +342,7 @@ export const secretImportServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
orgId: actorOrgId,
projectId, projectId,
environmentSlug: environment, environmentSlug: environment,
actor, actor,
@@ -415,6 +418,7 @@ export const secretImportServiceFactory = ({
if (membership && sourceFolder) { if (membership && sourceFolder) {
await secretQueueService.replicateSecrets({ await secretQueueService.replicateSecrets({
orgId: actorOrgId,
secretPath: secretImportDoc.importPath, secretPath: secretImportDoc.importPath,
projectId, projectId,
environmentSlug: secretImportDoc.importEnv.slug, environmentSlug: secretImportDoc.importEnv.slug,
@@ -78,6 +78,12 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.select(selectAllTableCols(TableName.SecretV2)) .select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
@@ -103,6 +109,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug, slug,
name: slug name: slug
}) })
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
@@ -221,7 +236,9 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
const secs = await (tx || db.replicaNode())(TableName.SecretV2) const secs = await (tx || db.replicaNode())(TableName.SecretV2)
.where({ folderId }) .where({ folderId })
.where((bd) => { .where((bd) => {
void bd.whereNull("userId").orWhere({ userId: userId || null }); void bd
.whereNull(`${TableName.SecretV2}.userId`)
.orWhere({ [`${TableName.SecretV2}.userId` as "userId"]: userId || null });
}) })
.leftJoin( .leftJoin(
TableName.SecretV2JnTag, TableName.SecretV2JnTag,
@@ -233,10 +250,16 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretV2)) .select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.orderBy("id", "asc"); .orderBy("id", "asc");
const data = sqlNestRelationships({ const data = sqlNestRelationships({
@@ -253,6 +276,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug, slug,
name: slug name: slug
}) })
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
@@ -367,7 +399,9 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
} }
}) })
.where((bd) => { .where((bd) => {
void bd.whereNull(`${TableName.SecretV2}.userId`).orWhere({ userId: userId || null }); void bd
.whereNull(`${TableName.SecretV2}.userId`)
.orWhere({ [`${TableName.SecretV2}.userId` as "userId"]: userId || null });
}) })
.leftJoin( .leftJoin(
TableName.SecretV2JnTag, TableName.SecretV2JnTag,
@@ -379,13 +413,23 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select( .select(
selectAllTableCols(TableName.SecretV2), selectAllTableCols(TableName.SecretV2),
db.raw(`DENSE_RANK() OVER (ORDER BY "key" ${filters?.orderDirection ?? OrderByDirection.ASC}) as rank`) db.raw(
`DENSE_RANK() OVER (ORDER BY "${TableName.SecretV2}".key ${
filters?.orderDirection ?? OrderByDirection.ASC
}) as rank`
)
) )
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.where((bd) => { .where((bd) => {
const slugs = filters?.tagSlugs?.filter(Boolean); const slugs = filters?.tagSlugs?.filter(Boolean);
if (slugs && slugs.length > 0) { if (slugs && slugs.length > 0) {
@@ -425,6 +469,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug, slug,
name: slug name: slug
}) })
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
@@ -545,10 +598,17 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretV2)) .select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")); .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
);
const docs = sqlNestRelationships({ const docs = sqlNestRelationships({
data: rawDocs, data: rawDocs,
key: "id", key: "id",
@@ -563,6 +623,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug, slug,
name: slug name: slug
}) })
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
@@ -6,6 +6,7 @@ import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types"; import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
@@ -54,9 +55,11 @@ export const getAllSecretReferences = (maybeSecretReference: string) => {
export const fnSecretBulkInsert = async ({ export const fnSecretBulkInsert = async ({
// TODO: Pick types here // TODO: Pick types here
folderId, folderId,
orgId,
inputSecrets, inputSecrets,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
resourceMetadataDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
tx tx
@@ -91,6 +94,7 @@ export const fnSecretBulkInsert = async ({
sanitizedInputSecrets.map((el) => ({ ...el, folderId })), sanitizedInputSecrets.map((el) => ({ ...el, folderId })),
tx tx
); );
const newSecretGroupedByKeyName = groupBy(newSecrets, (item) => item.key); const newSecretGroupedByKeyName = groupBy(newSecrets, (item) => item.key);
const newSecretTags = inputSecrets.flatMap(({ tagIds: secretTags = [], key }) => const newSecretTags = inputSecrets.flatMap(({ tagIds: secretTags = [], key }) =>
secretTags.map((tag) => ({ secretTags.map((tag) => ({
@@ -106,6 +110,7 @@ export const fnSecretBulkInsert = async ({
})), })),
tx tx
); );
await secretDAL.upsertSecretReferences( await secretDAL.upsertSecretReferences(
inputSecrets.map(({ references = [], key }) => ({ inputSecrets.map(({ references = [], key }) => ({
secretId: newSecretGroupedByKeyName[key][0].id, secretId: newSecretGroupedByKeyName[key][0].id,
@@ -113,6 +118,22 @@ export const fnSecretBulkInsert = async ({
})), })),
tx tx
); );
await resourceMetadataDAL.insertMany(
inputSecrets.flatMap(({ key: secretKey, secretMetadata }) => {
if (secretMetadata) {
return secretMetadata.map(({ key, value }) => ({
key,
value,
secretId: newSecretGroupedByKeyName[secretKey][0].id,
orgId
}));
}
return [];
}),
tx
);
if (newSecretTags.length) { if (newSecretTags.length) {
const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx); const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx);
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId); const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
@@ -120,6 +141,7 @@ export const fnSecretBulkInsert = async ({
[`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id, [`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id,
[`${TableName.SecretTag}Id` as const]: secret_tagsId [`${TableName.SecretTag}Id` as const]: secret_tagsId
})); }));
await secretVersionTagDAL.insertMany(newSecretVersionTags, tx); await secretVersionTagDAL.insertMany(newSecretVersionTags, tx);
} }
@@ -130,10 +152,12 @@ export const fnSecretBulkUpdate = async ({
tx, tx,
inputSecrets, inputSecrets,
folderId, folderId,
orgId,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL secretVersionTagDAL,
resourceMetadataDAL
}: TFnSecretBulkUpdate) => { }: TFnSecretBulkUpdate) => {
const sanitizedInputSecrets = inputSecrets.map( const sanitizedInputSecrets = inputSecrets.map(
({ ({
@@ -231,6 +255,34 @@ export const fnSecretBulkUpdate = async ({
} }
} }
const inputSecretIdsWithMetadata = inputSecrets
.filter((sec) => Boolean(sec.data.secretMetadata))
.map((sec) => sec.filter.id);
await resourceMetadataDAL.delete(
{
$in: {
secretId: inputSecretIdsWithMetadata
}
},
tx
);
await resourceMetadataDAL.insertMany(
inputSecrets.flatMap(({ filter: { id }, data: { secretMetadata } }) => {
if (secretMetadata) {
return secretMetadata.map(({ key, value }) => ({
key,
value,
secretId: id,
orgId
}));
}
return [];
}),
tx
);
return newSecrets.map((secret) => ({ ...secret, _id: secret.id })); return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
}; };
@@ -570,6 +622,7 @@ export const reshapeBridgeSecret = (
color?: string | null; color?: string | null;
name: string; name: string;
}[]; }[];
secretMetadata?: ResourceMetadataDTO;
} }
) => ({ ) => ({
secretKey: secret.key, secretKey: secret.key,
@@ -588,6 +641,7 @@ export const reshapeBridgeSecret = (
secretReminderRepeatDays: secret.reminderRepeatDays, secretReminderRepeatDays: secret.reminderRepeatDays,
secretReminderNote: secret.reminderNote, secretReminderNote: secret.reminderNote,
metadata: secret.metadata, metadata: secret.metadata,
secretMetadata: secret.secretMetadata,
createdAt: secret.createdAt, createdAt: secret.createdAt,
updatedAt: secret.updatedAt updatedAt: secret.updatedAt
}); });
@@ -18,6 +18,7 @@ import { ActorType } from "../auth/auth-type";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types"; import { KmsDataKey } from "../kms/kms-types";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretQueueFactory } from "../secret/secret-queue"; import { TSecretQueueFactory } from "../secret/secret-queue";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
@@ -74,6 +75,7 @@ type TSecretV2BridgeServiceFactoryDep = {
"insertV2Bridge" | "insertApprovalSecretV2Tags" "insertV2Bridge" | "insertApprovalSecretV2Tags"
>; >;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">; snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TSecretV2BridgeServiceFactory = ReturnType<typeof secretV2BridgeServiceFactory>; export type TSecretV2BridgeServiceFactory = ReturnType<typeof secretV2BridgeServiceFactory>;
@@ -95,7 +97,8 @@ export const secretV2BridgeServiceFactory = ({
secretApprovalPolicyService, secretApprovalPolicyService,
secretApprovalRequestDAL, secretApprovalRequestDAL,
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
kmsService kmsService,
resourceMetadataDAL
}: TSecretV2BridgeServiceFactoryDep) => { }: TSecretV2BridgeServiceFactoryDep) => {
const $validateSecretReferences = async ( const $validateSecretReferences = async (
projectId: string, projectId: string,
@@ -141,7 +144,7 @@ export const secretV2BridgeServiceFactory = ({
}, },
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
} }
] ]
@@ -186,6 +189,7 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod, actorAuthMethod,
projectId, projectId,
secretPath, secretPath,
secretMetadata,
...inputSecret ...inputSecret
}: TCreateSecretDTO) => { }: TCreateSecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission( const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
@@ -255,6 +259,7 @@ export const secretV2BridgeServiceFactory = ({
const secret = await secretDAL.transaction((tx) => const secret = await secretDAL.transaction((tx) =>
fnSecretBulkInsert({ fnSecretBulkInsert({
folderId, folderId,
orgId: actorOrgId,
inputSecrets: [ inputSecrets: [
{ {
version: 1, version: 1,
@@ -272,9 +277,11 @@ export const secretV2BridgeServiceFactory = ({
key: secretName, key: secretName,
userId: inputSecret.type === SecretType.Personal ? actorId : null, userId: inputSecret.type === SecretType.Personal ? actorId : null,
tagIds: inputSecret.tagIds, tagIds: inputSecret.tagIds,
references: nestedReferences references: nestedReferences,
secretMetadata
} }
], ],
resourceMetadataDAL,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
@@ -287,6 +294,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(folderId); await snapshotService.performSnapshot(folderId);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
orgId: actorOrgId,
actorId, actorId,
actor, actor,
projectId, projectId,
@@ -309,6 +317,7 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod, actorAuthMethod,
projectId, projectId,
secretPath, secretPath,
secretMetadata,
...inputSecret ...inputSecret
}: TUpdateSecretDTO) => { }: TUpdateSecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission( const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
@@ -435,6 +444,8 @@ export const secretV2BridgeServiceFactory = ({
const updatedSecret = await secretDAL.transaction(async (tx) => const updatedSecret = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({ fnSecretBulkUpdate({
folderId, folderId,
orgId: actorOrgId,
resourceMetadataDAL,
inputSecrets: [ inputSecrets: [
{ {
filter: { id: secretId }, filter: { id: secretId },
@@ -448,6 +459,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: inputSecret.skipMultilineEncoding, skipMultilineEncoding: inputSecret.skipMultilineEncoding,
key: inputSecret.newSecretName || secretName, key: inputSecret.newSecretName || secretName,
tags: inputSecret.tagIds, tags: inputSecret.tagIds,
secretMetadata,
...encryptedValue ...encryptedValue
} }
} }
@@ -475,6 +487,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
} }
@@ -562,6 +575,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
} }
@@ -961,8 +975,8 @@ export const secretV2BridgeServiceFactory = ({
? secretDAL.findOneWithTags({ ? secretDAL.findOneWithTags({
folderId, folderId,
type: secretType, type: secretType,
key: secretName, [`${TableName.SecretV2}.key` as "key"]: secretName,
userId: secretType === SecretType.Personal ? actorId : null [`${TableName.SecretV2}.userId` as "userId"]: secretType === SecretType.Personal ? actorId : null
}) })
: secretVersionDAL : secretVersionDAL
.findOne({ .findOne({
@@ -1113,7 +1127,7 @@ export const secretV2BridgeServiceFactory = ({
value: [ value: [
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
}, },
{ {
@@ -1185,11 +1199,14 @@ export const secretV2BridgeServiceFactory = ({
key: el.secretKey, key: el.secretKey,
tagIds: el.tagIds, tagIds: el.tagIds,
references, references,
secretMetadata: el.secretMetadata,
type: SecretType.Shared type: SecretType.Shared
}; };
}), }),
folderId, folderId,
orgId: actorOrgId,
secretDAL, secretDAL,
resourceMetadataDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
@@ -1203,6 +1220,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
secretPath, secretPath,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1254,7 +1272,7 @@ export const secretV2BridgeServiceFactory = ({
value: [ value: [
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
}, },
{ {
@@ -1319,7 +1337,7 @@ export const secretV2BridgeServiceFactory = ({
value: [ value: [
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
}, },
{ {
@@ -1371,6 +1389,7 @@ export const secretV2BridgeServiceFactory = ({
const secrets = await secretDAL.transaction(async (tx) => const secrets = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({ fnSecretBulkUpdate({
folderId, folderId,
orgId: actorOrgId,
tx, tx,
inputSecrets: inputSecrets.map((el) => { inputSecrets: inputSecrets.map((el) => {
const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0]; const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0];
@@ -1394,6 +1413,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
key: el.newSecretName || el.secretKey, key: el.newSecretName || el.secretKey,
tags: el.tagIds, tags: el.tagIds,
secretMetadata: el.secretMetadata,
...encryptedValue ...encryptedValue
} }
}; };
@@ -1401,7 +1421,8 @@ export const secretV2BridgeServiceFactory = ({
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL secretVersionTagDAL,
resourceMetadataDAL
}) })
); );
await snapshotService.performSnapshot(folderId); await snapshotService.performSnapshot(folderId);
@@ -1410,6 +1431,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
secretPath, secretPath,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1461,7 +1483,7 @@ export const secretV2BridgeServiceFactory = ({
value: [ value: [
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
}, },
{ {
@@ -1512,6 +1534,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
secretPath, secretPath,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1815,10 +1838,12 @@ export const secretV2BridgeServiceFactory = ({
if (locallyCreatedSecrets.length) { if (locallyCreatedSecrets.length) {
await fnSecretBulkInsert({ await fnSecretBulkInsert({
folderId: destinationFolder.id, folderId: destinationFolder.id,
orgId: actorOrgId,
secretVersionDAL, secretVersionDAL,
secretDAL, secretDAL,
tx, tx,
secretTagDAL, secretTagDAL,
resourceMetadataDAL,
secretVersionTagDAL, secretVersionTagDAL,
inputSecrets: locallyCreatedSecrets.map((doc) => { inputSecrets: locallyCreatedSecrets.map((doc) => {
return { return {
@@ -1830,6 +1855,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: doc.skipMultilineEncoding, skipMultilineEncoding: doc.skipMultilineEncoding,
reminderNote: doc.reminderNote, reminderNote: doc.reminderNote,
reminderRepeatDays: doc.reminderRepeatDays, reminderRepeatDays: doc.reminderRepeatDays,
secretMetadata: doc.secretMetadata,
references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : [] references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : []
}; };
}) })
@@ -1838,6 +1864,8 @@ export const secretV2BridgeServiceFactory = ({
if (locallyUpdatedSecrets.length) { if (locallyUpdatedSecrets.length) {
await fnSecretBulkUpdate({ await fnSecretBulkUpdate({
folderId: destinationFolder.id, folderId: destinationFolder.id,
orgId: actorOrgId,
resourceMetadataDAL,
secretVersionDAL, secretVersionDAL,
secretDAL, secretDAL,
tx, tx,
@@ -1855,6 +1883,7 @@ export const secretV2BridgeServiceFactory = ({
encryptedComment: doc.encryptedComment, encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding, skipMultilineEncoding: doc.skipMultilineEncoding,
reminderNote: doc.reminderNote, reminderNote: doc.reminderNote,
secretMetadata: doc.secretMetadata,
reminderRepeatDays: doc.reminderRepeatDays, reminderRepeatDays: doc.reminderRepeatDays,
...(doc.encryptedValue ...(doc.encryptedValue
? { ? {
@@ -1938,6 +1967,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(destinationFolder.id); await snapshotService.performSnapshot(destinationFolder.id);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId: actorOrgId,
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environment.slug, environmentSlug: destinationFolder.environment.slug,
actorId, actorId,
@@ -1949,6 +1979,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(sourceFolder.id); await snapshotService.performSnapshot(sourceFolder.id);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId: actorOrgId,
secretPath: sourceFolder.path, secretPath: sourceFolder.path,
environmentSlug: sourceFolder.environment.slug, environmentSlug: sourceFolder.environment.slug,
actorId, actorId,
@@ -7,6 +7,8 @@ import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
import { TSecretVersionV2DALFactory } from "./secret-version-dal"; import { TSecretVersionV2DALFactory } from "./secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "./secret-version-tag-dal"; import { TSecretVersionV2TagDALFactory } from "./secret-version-tag-dal";
@@ -58,6 +60,7 @@ export type TCreateSecretDTO = TProjectPermission & {
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
secretMetadata?: ResourceMetadataDTO;
}; };
export type TUpdateSecretDTO = TProjectPermission & { export type TUpdateSecretDTO = TProjectPermission & {
@@ -75,6 +78,7 @@ export type TUpdateSecretDTO = TProjectPermission & {
metadata?: { metadata?: {
source?: string; source?: string;
}; };
secretMetadata?: ResourceMetadataDTO;
}; };
export type TDeleteSecretDTO = TProjectPermission & { export type TDeleteSecretDTO = TProjectPermission & {
@@ -94,6 +98,7 @@ export type TCreateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
secretComment?: string; secretComment?: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
tagIds?: string[]; tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
metadata?: { metadata?: {
source?: string; source?: string;
}; };
@@ -113,6 +118,7 @@ export type TUpdateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
tagIds?: string[]; tagIds?: string[];
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
secretMetadata?: ResourceMetadataDTO;
}[]; }[];
}; };
@@ -136,8 +142,16 @@ export type TSecretReference = { environment: string; secretPath: string; secret
export type TFnSecretBulkInsert = { export type TFnSecretBulkInsert = {
folderId: string; folderId: string;
orgId: string;
tx?: Knex; tx?: Knex;
inputSecrets: Array<Omit<TSecretsV2Insert, "folderId"> & { tagIds?: string[]; references: TSecretReference[] }>; inputSecrets: Array<
Omit<TSecretsV2Insert, "folderId"> & {
tagIds?: string[];
references: TSecretReference[];
secretMetadata?: ResourceMetadataDTO;
}
>;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">; secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">;
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">; secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2">; secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2">;
@@ -156,10 +170,12 @@ type TRequireReferenceIfValue =
export type TFnSecretBulkUpdate = { export type TFnSecretBulkUpdate = {
folderId: string; folderId: string;
orgId: string;
inputSecrets: { inputSecrets: {
filter: Partial<TSecretsV2>; filter: Partial<TSecretsV2>;
data: TRequireReferenceIfValue & { tags?: string[] }; data: TRequireReferenceIfValue & { tags?: string[]; secretMetadata?: ResourceMetadataDTO };
}[]; }[];
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">; secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">;
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">; secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2">; secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2">;
+9 -2
View File
@@ -749,7 +749,8 @@ export const createManySecretsRawFnFactory = ({
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
kmsService kmsService,
resourceMetadataDAL
}: TCreateManySecretsRawFnFactory) => { }: TCreateManySecretsRawFnFactory) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL); const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
const createManySecretsRawFn = async ({ const createManySecretsRawFn = async ({
@@ -760,7 +761,7 @@ export const createManySecretsRawFnFactory = ({
userId userId
}: TCreateManySecretsRawFn) => { }: TCreateManySecretsRawFn) => {
const { botKey, shouldUseSecretV2Bridge } = await getBotKeyFn(projectId); const { botKey, shouldUseSecretV2Bridge } = await getBotKeyFn(projectId);
const project = await projectDAL.findById(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) if (!folder)
throw new NotFoundError({ throw new NotFoundError({
@@ -814,7 +815,9 @@ export const createManySecretsRawFnFactory = ({
tagIds: el.tags tagIds: el.tags
})), })),
folderId, folderId,
orgId: project.orgId,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
resourceMetadataDAL,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL: secretVersionTagV2BridgeDAL, secretVersionTagDAL: secretVersionTagV2BridgeDAL,
@@ -909,6 +912,7 @@ export const updateManySecretsRawFnFactory = ({
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
resourceMetadataDAL,
kmsService kmsService
}: TUpdateManySecretsRawFnFactory) => { }: TUpdateManySecretsRawFnFactory) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL); const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
@@ -920,6 +924,7 @@ export const updateManySecretsRawFnFactory = ({
userId userId
}: TUpdateManySecretsRawFn): Promise<Array<{ id: string }>> => { }: TUpdateManySecretsRawFn): Promise<Array<{ id: string }>> => {
const { botKey, shouldUseSecretV2Bridge } = await getBotKeyFn(projectId); const { botKey, shouldUseSecretV2Bridge } = await getBotKeyFn(projectId);
const project = await projectDAL.findById(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) if (!folder)
@@ -988,11 +993,13 @@ export const updateManySecretsRawFnFactory = ({
const updatedSecrets = await secretDAL.transaction(async (tx) => const updatedSecrets = await secretDAL.transaction(async (tx) =>
fnSecretV2BridgeBulkUpdate({ fnSecretV2BridgeBulkUpdate({
folderId, folderId,
orgId: project.orgId,
tx, tx,
inputSecrets: inputSecrets.map((el) => ({ inputSecrets: inputSecrets.map((el) => ({
filter: { id: secretsToUpdateInDBGroupedByKey[el.key][0].id, type: SecretType.Shared }, filter: { id: secretsToUpdateInDBGroupedByKey[el.key][0].id, type: SecretType.Shared },
data: el data: el
})), })),
resourceMetadataDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL, secretTagDAL,
+23 -5
View File
@@ -47,6 +47,8 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectKeyDALFactory } from "../project-key/project-key-dal"; import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal"; import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns"; import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
@@ -104,6 +106,7 @@ type TSecretQueueFactoryDep = {
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">; auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
orgService: Pick<TOrgServiceFactory, "addGhostUser">; orgService: Pick<TOrgServiceFactory, "addGhostUser">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">; projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TGetSecrets = { export type TGetSecrets = {
@@ -120,7 +123,12 @@ export const uniqueSecretQueueKey = (environment: string, secretPath: string) =>
type TIntegrationSecret = Record< type TIntegrationSecret = Record<
string, string,
{ value: string; comment?: string; skipMultilineEncoding?: boolean | null | undefined } {
value: string;
comment?: string;
skipMultilineEncoding?: boolean | null | undefined;
secretMetadata?: ResourceMetadataDTO;
}
>; >;
// TODO(akhilmhdh): split this into multiple queue // TODO(akhilmhdh): split this into multiple queue
@@ -157,7 +165,8 @@ export const secretQueueFactory = ({
auditLogService, auditLogService,
orgService, orgService,
projectUserMembershipRoleDAL, projectUserMembershipRoleDAL,
projectKeyDAL projectKeyDAL,
resourceMetadataDAL
}: TSecretQueueFactoryDep) => { }: TSecretQueueFactoryDep) => {
const integrationMeter = opentelemetry.metrics.getMeter("Integrations"); const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", { const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
@@ -306,7 +315,8 @@ export const secretQueueFactory = ({
kmsService, kmsService,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
secretVersionTagV2BridgeDAL secretVersionTagV2BridgeDAL,
resourceMetadataDAL
}); });
const updateManySecretsRawFn = updateManySecretsRawFnFactory({ const updateManySecretsRawFn = updateManySecretsRawFnFactory({
@@ -321,7 +331,8 @@ export const secretQueueFactory = ({
kmsService, kmsService,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
secretVersionTagV2BridgeDAL secretVersionTagV2BridgeDAL,
resourceMetadataDAL
}); });
/** /**
@@ -372,6 +383,7 @@ export const secretQueueFactory = ({
} }
content[secretKey].skipMultilineEncoding = Boolean(secret.skipMultilineEncoding); content[secretKey].skipMultilineEncoding = Boolean(secret.skipMultilineEncoding);
content[secretKey].secretMetadata = secret.secretMetadata;
}) })
); );
@@ -397,7 +409,8 @@ export const secretQueueFactory = ({
content[importedSecret.key] = { content[importedSecret.key] = {
skipMultilineEncoding: importedSecret.skipMultilineEncoding, skipMultilineEncoding: importedSecret.skipMultilineEncoding,
comment: importedSecret.secretComment, comment: importedSecret.secretComment,
value: importedSecret.secretValue || "" value: importedSecret.secretValue || "",
secretMetadata: importedSecret.secretMetadata
}; };
} }
} }
@@ -597,6 +610,7 @@ export const secretQueueFactory = ({
_depth: depth, _depth: depth,
secretPath, secretPath,
projectId, projectId,
orgId,
environmentSlug: environment, environmentSlug: environment,
excludeReplication, excludeReplication,
actorId, actorId,
@@ -625,6 +639,7 @@ export const secretQueueFactory = ({
_deDupeReplicationQueue: deDupeReplicationQueue, _deDupeReplicationQueue: deDupeReplicationQueue,
_depth: depth, _depth: depth,
projectId, projectId,
orgId,
secretPath, secretPath,
actorId, actorId,
actor, actor,
@@ -681,6 +696,7 @@ export const secretQueueFactory = ({
if (!folder) { if (!folder) {
throw new Error("Secret path not found"); throw new Error("Secret path not found");
} }
const project = await projectDAL.findById(projectId);
// find all imports made with the given environment and secret path // find all imports made with the given environment and secret path
const linkSourceDto = { const linkSourceDto = {
@@ -715,6 +731,7 @@ export const secretQueueFactory = ({
.map(({ folderId }) => .map(({ folderId }) =>
syncSecrets({ syncSecrets({
projectId, projectId,
orgId: project.orgId,
secretPath: foldersGroupedById[folderId][0]?.path as string, secretPath: foldersGroupedById[folderId][0]?.path as string,
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string, environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
_deDupeQueue: deDupeQueue, _deDupeQueue: deDupeQueue,
@@ -767,6 +784,7 @@ export const secretQueueFactory = ({
.map((folderId) => .map((folderId) =>
syncSecrets({ syncSecrets({
projectId, projectId,
orgId: project.orgId,
secretPath: referencedFoldersGroupedById[folderId][0]?.path as string, secretPath: referencedFoldersGroupedById[folderId][0]?.path as string,
environmentSlug: referencedFoldersGroupedById[folderId][0]?.environmentSlug as string, environmentSlug: referencedFoldersGroupedById[folderId][0]?.environmentSlug as string,
_deDupeQueue: deDupeQueue, _deDupeQueue: deDupeQueue,
+26 -8
View File
@@ -288,6 +288,7 @@ export const secretServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
} }
@@ -429,6 +430,7 @@ export const secretServiceFactory = ({
await snapshotService.performSnapshot(folderId); await snapshotService.performSnapshot(folderId);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath: path, secretPath: path,
orgId: actorOrgId,
actorId, actorId,
actor, actor,
projectId, projectId,
@@ -526,6 +528,7 @@ export const secretServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
} }
@@ -820,6 +823,7 @@ export const secretServiceFactory = ({
actorId, actorId,
secretPath: path, secretPath: path,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -928,6 +932,7 @@ export const secretServiceFactory = ({
actorId, actorId,
secretPath: path, secretPath: path,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1014,6 +1019,7 @@ export const secretServiceFactory = ({
actorId, actorId,
secretPath: path, secretPath: path,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1385,7 +1391,8 @@ export const secretServiceFactory = ({
skipMultilineEncoding, skipMultilineEncoding,
tagIds, tagIds,
secretReminderNote, secretReminderNote,
secretReminderRepeatDays secretReminderRepeatDays,
secretMetadata
}: TCreateSecretRawDTO) => { }: TCreateSecretRawDTO) => {
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
const policy = const policy =
@@ -1412,7 +1419,8 @@ export const secretServiceFactory = ({
secretValue, secretValue,
tagIds, tagIds,
reminderNote: secretReminderNote, reminderNote: secretReminderNote,
reminderRepeatDays: secretReminderRepeatDays reminderRepeatDays: secretReminderRepeatDays,
secretMetadata
} }
] ]
} }
@@ -1435,7 +1443,8 @@ export const secretServiceFactory = ({
tagIds, tagIds,
secretReminderNote, secretReminderNote,
skipMultilineEncoding, skipMultilineEncoding,
secretReminderRepeatDays secretReminderRepeatDays,
secretMetadata
}); });
return { secret, type: SecretProtectionType.Direct as const }; return { secret, type: SecretProtectionType.Direct as const };
} }
@@ -1525,7 +1534,8 @@ export const secretServiceFactory = ({
secretReminderRepeatDays, secretReminderRepeatDays,
metadata, metadata,
secretComment, secretComment,
newSecretName newSecretName,
secretMetadata
}: TUpdateSecretRawDTO) => { }: TUpdateSecretRawDTO) => {
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
const policy = const policy =
@@ -1553,7 +1563,8 @@ export const secretServiceFactory = ({
secretValue, secretValue,
tagIds, tagIds,
reminderNote: secretReminderNote, reminderNote: secretReminderNote,
reminderRepeatDays: secretReminderRepeatDays reminderRepeatDays: secretReminderRepeatDays,
secretMetadata
} }
] ]
} }
@@ -1577,7 +1588,8 @@ export const secretServiceFactory = ({
secretName, secretName,
newSecretName, newSecretName,
metadata, metadata,
secretValue secretValue,
secretMetadata
}); });
return { type: SecretProtectionType.Direct as const, secret }; return { type: SecretProtectionType.Direct as const, secret };
} }
@@ -1793,7 +1805,8 @@ export const secretServiceFactory = ({
secretComment: el.secretComment, secretComment: el.secretComment,
metadata: el.metadata, metadata: el.metadata,
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
secretKey: el.secretKey secretKey: el.secretKey,
secretMetadata: el.secretMetadata
})) }))
} }
}); });
@@ -1919,7 +1932,8 @@ export const secretServiceFactory = ({
secretValue: el.secretValue, secretValue: el.secretValue,
secretComment: el.secretComment, secretComment: el.secretComment,
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
secretKey: el.secretKey secretKey: el.secretKey,
secretMetadata: el.secretMetadata
})) }))
} }
}); });
@@ -2262,6 +2276,7 @@ export const secretServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
projectId: project.id, projectId: project.id,
orgId: project.orgId,
environmentSlug: environment, environmentSlug: environment,
excludeReplication: true excludeReplication: true
}); });
@@ -2370,6 +2385,7 @@ export const secretServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
projectId: project.id, projectId: project.id,
orgId: project.orgId,
environmentSlug: environment, environmentSlug: environment,
excludeReplication: true excludeReplication: true
}); });
@@ -2828,6 +2844,7 @@ export const secretServiceFactory = ({
await snapshotService.performSnapshot(destinationFolder.id); await snapshotService.performSnapshot(destinationFolder.id);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId: project.id, projectId: project.id,
orgId: project.orgId,
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environment.slug, environmentSlug: destinationFolder.environment.slug,
actorId, actorId,
@@ -2839,6 +2856,7 @@ export const secretServiceFactory = ({
await snapshotService.performSnapshot(sourceFolder.id); await snapshotService.performSnapshot(sourceFolder.id);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId: project.id, projectId: project.id,
orgId: project.orgId,
secretPath: sourceFolder.path, secretPath: sourceFolder.path,
environmentSlug: sourceFolder.environment.slug, environmentSlug: sourceFolder.environment.slug,
actorId, actorId,
+16 -1
View File
@@ -14,6 +14,8 @@ import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { ActorType } from "../auth/auth-type"; import { ActorType } from "../auth/auth-type";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
@@ -211,6 +213,7 @@ export type TCreateSecretRawDTO = TProjectPermission & {
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
secretMetadata?: ResourceMetadataDTO;
}; };
export type TUpdateSecretRawDTO = TProjectPermission & { export type TUpdateSecretRawDTO = TProjectPermission & {
@@ -228,6 +231,7 @@ export type TUpdateSecretRawDTO = TProjectPermission & {
metadata?: { metadata?: {
source?: string; source?: string;
}; };
secretMetadata?: ResourceMetadataDTO;
}; };
export type TDeleteSecretRawDTO = TProjectPermission & { export type TDeleteSecretRawDTO = TProjectPermission & {
@@ -248,6 +252,7 @@ export type TCreateManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
secretComment?: string; secretComment?: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
tagIds?: string[]; tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
metadata?: { metadata?: {
source?: string; source?: string;
}; };
@@ -266,6 +271,7 @@ export type TUpdateManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
secretComment?: string; secretComment?: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
tagIds?: string[]; tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
}[]; }[];
@@ -293,7 +299,13 @@ export type TSecretReference = { environment: string; secretPath: string };
export type TFnSecretBulkInsert = { export type TFnSecretBulkInsert = {
folderId: string; folderId: string;
tx?: Knex; tx?: Knex;
inputSecrets: Array<Omit<TSecretsInsert, "folderId"> & { tags?: string[]; references?: TSecretReference[] }>; inputSecrets: Array<
Omit<TSecretsInsert, "folderId"> & {
tags?: string[];
references?: TSecretReference[];
secretMetadata?: ResourceMetadataDTO;
}
>;
secretDAL: Pick<TSecretDALFactory, "insertMany" | "upsertSecretReferences">; secretDAL: Pick<TSecretDALFactory, "insertMany" | "upsertSecretReferences">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "insertMany">; secretVersionDAL: Pick<TSecretVersionDALFactory, "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecret">; secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecret">;
@@ -389,6 +401,7 @@ export type TCreateManySecretsRawFnFactory = {
>; >;
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">; secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">; secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
}; };
export type TCreateManySecretsRawFn = { export type TCreateManySecretsRawFn = {
@@ -425,6 +438,7 @@ export type TUpdateManySecretsRawFnFactory = {
>; >;
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">; secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">; secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TUpdateManySecretsRawFn = { export type TUpdateManySecretsRawFn = {
@@ -460,6 +474,7 @@ export type TSyncSecretsDTO<T extends boolean = false> = {
_depth?: number; _depth?: number;
secretPath: string; secretPath: string;
projectId: string; projectId: string;
orgId: string;
environmentSlug: string; environmentSlug: string;
// cases for just doing sync integration and webhook // cases for just doing sync integration and webhook
excludeReplication?: T; excludeReplication?: T;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 580 KiB

+18 -6
View File
@@ -17,6 +17,7 @@ Prerequisites:
If your instance is deployed on AWS, the aws-sdk will automatically retrieve the credentials. Ensure that you assign the provided permission policy to your deployed instance, such as ECS or EC2. If your instance is deployed on AWS, the aws-sdk will automatically retrieve the credentials. Ensure that you assign the provided permission policy to your deployed instance, such as ECS or EC2.
The following steps are for instances not deployed on AWS The following steps are for instances not deployed on AWS
<Steps> <Steps>
<Step title="Create an IAM User"> <Step title="Create an IAM User">
Navigate to [Create IAM User](https://console.aws.amazon.com/iamv2/home#/users/create) in your AWS Console. Navigate to [Create IAM User](https://console.aws.amazon.com/iamv2/home#/users/create) in your AWS Console.
@@ -40,9 +41,10 @@ The following steps are for instances not deployed on AWS
<Step title="Obtain the IAM User Credentials"> <Step title="Obtain the IAM User Credentials">
Obtain the AWS access key ID and secret access key for your IAM User by navigating to IAM > Users > [Your User] > Security credentials > Access keys. Obtain the AWS access key ID and secret access key for your IAM User by navigating to IAM > Users > [Your User] > Security credentials > Access keys.
![Access Key Step 1](../../images/integrations/aws/integrations-aws-access-key-1.png) ![Access Key Step 1](../../images/integrations/aws/integrations-aws-access-key-1.png)
![Access Key Step 2](../../images/integrations/aws/integrations-aws-access-key-2.png) ![Access Key Step 2](../../images/integrations/aws/integrations-aws-access-key-2.png)
![Access Key Step 3](../../images/integrations/aws/integrations-aws-access-key-3.png) ![Access Key Step 3](../../images/integrations/aws/integrations-aws-access-key-3.png)
</Step> </Step>
<Step title="Set Up Integration Keys"> <Step title="Set Up Integration Keys">
1. Set the access key as **CLIENT_ID_AWS_INTEGRATION**. 1. Set the access key as **CLIENT_ID_AWS_INTEGRATION**.
@@ -59,6 +61,7 @@ The following steps are for instances not deployed on AWS
2. Select **AWS Account** as the **Trusted Entity Type**. 2. Select **AWS Account** as the **Trusted Entity Type**.
3. Choose **Another AWS Account** and enter **381492033652** (Infisical AWS Account ID). This restricts the role to be assumed only by Infisical. If self-hosting, provide your AWS account number instead. 3. Choose **Another AWS Account** and enter **381492033652** (Infisical AWS Account ID). This restricts the role to be assumed only by Infisical. If self-hosting, provide your AWS account number instead.
4. Optionally, enable **Require external ID** and enter your **project ID** to further enhance security. 4. Optionally, enable **Require external ID** and enter your **project ID** to further enhance security.
</Step> </Step>
<Step title="Add Required Permissions for the IAM Role"> <Step title="Add Required Permissions for the IAM Role">
@@ -89,11 +92,13 @@ The following steps are for instances not deployed on AWS
] ]
} }
``` ```
</Step> </Step>
<Step title="Copy the AWS IAM Role ARN"> <Step title="Copy the AWS IAM Role ARN">
![Copy IAM Role ARN](../../images/integrations/aws/integration-aws-iam-assume-arn.png) ![Copy IAM Role
</Step> ARN](../../images/integrations/aws/integration-aws-iam-assume-arn.png)
</Step>
<Step title="Authorize Infisical for AWS Secrets Manager"> <Step title="Authorize Infisical for AWS Secrets Manager">
1. Navigate to your project's integrations tab in Infisical. 1. Navigate to your project's integrations tab in Infisical.
@@ -104,6 +109,7 @@ The following steps are for instances not deployed on AWS
![Select Assume Role](../../images/integrations/aws/integration-aws-iam-assume-select.png) ![Select Assume Role](../../images/integrations/aws/integration-aws-iam-assume-select.png)
4. Provide the **AWS IAM Role ARN** obtained from the previous step. 4. Provide the **AWS IAM Role ARN** obtained from the previous step.
</Step> <Step title="Start integration"> </Step> <Step title="Start integration">
Select how you want to integration to work by specifying a number of parameters: Select how you want to integration to work by specifying a number of parameters:
@@ -127,6 +133,12 @@ The following steps are for instances not deployed on AWS
Optionally, you can add tags or specify the encryption key of all the secrets created via this integration: Optionally, you can add tags or specify the encryption key of all the secrets created via this integration:
<ParamField path="Tag Sync Mode" type="string" optional>
The sync mode for AWS tags. The supported options are `Secret Metadata` and `Custom`. If `Secret Metadata` is selected,
the metadata of the Infisical secrets are used as tags in AWS. If custom is selected, then the key/value of the **Secret Tag** field is used. `Secret Metadata` mode
is only supported for one-to-one integrations.
</ParamField>
<ParamField path="Secret Tag" type="string" optional> <ParamField path="Secret Tag" type="string" optional>
The Key/Value of a tag that will be added to secrets in AWS. Please note that it is possible to add multiple tags via API. The Key/Value of a tag that will be added to secrets in AWS. Please note that it is possible to add multiple tags via API.
</ParamField> </ParamField>
@@ -4,7 +4,12 @@ import { createNotification } from "@app/components/notifications";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { workspaceKeys } from "../workspace"; import { workspaceKeys } from "../workspace";
import { TCloudIntegration, TIntegrationWithEnv, TOctopusDeployScopeValues } from "./types"; import {
IntegrationMetadataSyncMode,
TCloudIntegration,
TIntegrationWithEnv,
TOctopusDeployScopeValues
} from "./types";
export const integrationQueryKeys = { export const integrationQueryKeys = {
getIntegrations: () => ["integrations"] as const, getIntegrations: () => ["integrations"] as const,
@@ -88,6 +93,7 @@ export const useCreateIntegration = () => {
shouldProtectSecrets?: boolean; shouldProtectSecrets?: boolean;
shouldEnableDelete?: boolean; shouldEnableDelete?: boolean;
octopusDeployScopeValues?: TOctopusDeployScopeValues; octopusDeployScopeValues?: TOctopusDeployScopeValues;
metadataSyncMode?: IntegrationMetadataSyncMode;
}; };
}) => { }) => {
const { const {
@@ -62,6 +62,7 @@ export type TIntegration = {
octopusDeployScopeValues?: TOctopusDeployScopeValues; octopusDeployScopeValues?: TOctopusDeployScopeValues;
awsIamRole?: string; awsIamRole?: string;
region?: string; region?: string;
metadataSyncMode?: IntegrationMetadataSyncMode;
}; };
}; };
@@ -92,3 +93,8 @@ export enum IntegrationMappingBehavior {
ONE_TO_ONE = "one-to-one", ONE_TO_ONE = "one-to-one",
MANY_TO_ONE = "many-to-one" MANY_TO_ONE = "many-to-one"
} }
export enum IntegrationMetadataSyncMode {
CUSTOM = "custom",
SECRET_METADATA = "secret-metadata"
}
@@ -82,6 +82,7 @@ export type TSecretApprovalRequest = {
conflicts: Array<{ secretId: string; op: CommitType.UPDATE }>; conflicts: Array<{ secretId: string; op: CommitType.UPDATE }>;
commits: ({ commits: ({
// if there is no secret means it was creation // if there is no secret means it was creation
secretMetadata?: { key: string; value: string }[];
secret?: { version: number }; secret?: { version: number };
secretVersion: SecretV3Raw; secretVersion: SecretV3Raw;
// if there is no new version its for Delete // if there is no new version its for Delete
+4 -2
View File
@@ -84,7 +84,8 @@ export const useUpdateSecretV3 = ({
secretReminderRepeatDays, secretReminderRepeatDays,
secretReminderNote, secretReminderNote,
newSecretName, newSecretName,
skipMultilineEncoding skipMultilineEncoding,
secretMetadata
}) => { }) => {
const { data } = await apiRequest.patch(`/api/v3/secrets/raw/${secretKey}`, { const { data } = await apiRequest.patch(`/api/v3/secrets/raw/${secretKey}`, {
workspaceId, workspaceId,
@@ -97,7 +98,8 @@ export const useUpdateSecretV3 = ({
newSecretName, newSecretName,
secretComment, secretComment,
tagIds, tagIds,
secretValue secretValue,
secretMetadata
}); });
return data; return data;
}, },
+2 -1
View File
@@ -67,7 +67,8 @@ export const mergePersonalSecrets = (rawSecrets: SecretV3Raw[]) => {
updatedAt: el.updatedAt, updatedAt: el.updatedAt,
version: el.version, version: el.version,
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
path: el.secretPath path: el.secretPath,
secretMetadata: el.secretMetadata
}; };
if (el.type === SecretType.Personal) { if (el.type === SecretType.Personal) {
+3
View File
@@ -48,6 +48,7 @@ export type SecretV3RawSanitized = {
overrideAction?: string; overrideAction?: string;
folderId?: string; folderId?: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
secretMetadata?: { key: string; value: string }[];
}; };
export type SecretV3Raw = { export type SecretV3Raw = {
@@ -63,6 +64,7 @@ export type SecretV3Raw = {
secretComment?: string; secretComment?: string;
secretReminderNote?: string; secretReminderNote?: string;
secretReminderRepeatDays?: number; secretReminderRepeatDays?: number;
secretMetadata?: { key: string; value: string }[];
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
metadata?: Record<string, string>; metadata?: Record<string, string>;
tags?: WsTag[]; tags?: WsTag[];
@@ -148,6 +150,7 @@ export type TUpdateSecretsV3DTO = {
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
tagIds?: string[]; tagIds?: string[];
secretMetadata?: { key: string; value: string }[];
}; };
export type TDeleteSecretsV3DTO = { export type TDeleteSecretsV3DTO = {
@@ -32,7 +32,8 @@ const metadataMappings: Record<keyof NonNullable<TIntegrationWithEnv["metadata"]
shouldEnableDelete: "GitHub Secret Deletion Enabled", shouldEnableDelete: "GitHub Secret Deletion Enabled",
octopusDeployScopeValues: "Octopus Deploy Scope Values", octopusDeployScopeValues: "Octopus Deploy Scope Values",
awsIamRole: "AWS IAM Role", awsIamRole: "AWS IAM Role",
region: "Region" region: "Region",
metadataSyncMode: "Metadata Sync Mode"
} as const; } as const;
export const IntegrationSettingsSection = ({ integration }: Props) => { export const IntegrationSettingsSection = ({ integration }: Props) => {
@@ -1,4 +1,4 @@
import { faExclamationTriangle, faInfo } from "@fortawesome/free-solid-svg-icons"; import { faExclamationTriangle, faInfo, faKey } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { import {
@@ -18,7 +18,10 @@ import { CommitType, SecretV3Raw, TSecretApprovalSecChange, WsTag } from "@app/h
export type Props = { export type Props = {
op: CommitType; op: CommitType;
secretVersion?: SecretV3Raw; secretVersion?: SecretV3Raw;
newVersion?: Omit<TSecretApprovalSecChange, "tags"> & { tags?: WsTag[] }; newVersion?: Omit<TSecretApprovalSecChange, "tags"> & {
tags?: WsTag[];
secretMetadata?: { key: string; value: string }[];
};
presentSecretVersionNumber: number; presentSecretVersionNumber: number;
hasMerged?: boolean; hasMerged?: boolean;
conflicts: Array<{ secretId: string; op: CommitType }>; conflicts: Array<{ secretId: string; op: CommitType }>;
@@ -80,11 +83,12 @@ export const SecretApprovalRequestChangeItem = ({
<Table> <Table>
<THead> <THead>
<Tr> <Tr>
{op === CommitType.UPDATE && <Th className="w-12" />} {op === CommitType.UPDATE && <Th className="w-12 shrink-0" />}
<Th className="min-table-row">Secret</Th> <Th className="w-48 shrink-0">Secret</Th>
<Th>Value</Th> <Th className="min-w-0 flex-1">Value</Th>
<Th className="min-table-row">Comment</Th> <Th className="w-24 shrink-0">Comment</Th>
<Th className="min-table-row">Tags</Th> <Th className="w-24 shrink-0">Tags</Th>
<Th className="w-40 shrink-0">Metadata</Th>
</Tr> </Tr>
</THead> </THead>
{op === CommitType.UPDATE ? ( {op === CommitType.UPDATE ? (
@@ -110,6 +114,33 @@ export const SecretApprovalRequestChangeItem = ({
</Tag> </Tag>
))} ))}
</Td> </Td>
<Td>
{secretVersion?.secretMetadata?.length ? (
<div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300">
{secretVersion.secretMetadata?.map((el) => (
<div key={el.key} className="flex items-center">
<Tag
size="xs"
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<div>{el.key}</div>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
</Tag>
</div>
))}
</div>
) : (
<p className="text-sm text-mineshaft-300">-</p>
)}
</Td>
</Tr> </Tr>
<Tr> <Tr>
<Td className="text-green-600">NEW</Td> <Td className="text-green-600">NEW</Td>
@@ -132,6 +163,33 @@ export const SecretApprovalRequestChangeItem = ({
</Tag> </Tag>
))} ))}
</Td> </Td>
<Td>
{newVersion?.secretMetadata?.length ? (
<div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300">
{newVersion.secretMetadata?.map((el) => (
<div key={el.key} className="flex items-center">
<Tag
size="xs"
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<div>{el.key}</div>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
</Tag>
</div>
))}
</div>
) : (
<p className="text-sm text-mineshaft-300">-</p>
)}
</Td>
</Tr> </Tr>
</TBody> </TBody>
) : ( ) : (
@@ -173,6 +231,33 @@ export const SecretApprovalRequestChangeItem = ({
) )
)} )}
</Td> </Td>
<Td>
{newVersion?.secretMetadata?.length ? (
<div className="mt-1 flex flex-wrap gap-2 text-sm text-mineshaft-300">
{newVersion.secretMetadata?.map((el) => (
<div key={el.key} className="flex items-center">
<Tag
size="xs"
className="mr-0 flex items-center rounded-r-none border border-mineshaft-500"
>
<FontAwesomeIcon icon={faKey} size="xs" className="mr-1" />
<div>{el.key}</div>
</Tag>
<Tag
size="xs"
className="flex items-center rounded-l-none border border-mineshaft-500 bg-mineshaft-900 pl-1"
>
<div className="max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap">
{el.value}
</div>
</Tag>
</div>
))}
</div>
) : (
<p className="text-sm text-mineshaft-300">-</p>
)}
</Td>
</Tr> </Tr>
</TBody> </TBody>
)} )}
@@ -8,7 +8,8 @@ import {
faClock, faClock,
faPlus, faPlus,
faShare, faShare,
faTag faTag,
faTrash
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
@@ -26,6 +27,7 @@ import {
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuTrigger, DropdownMenuTrigger,
FormControl, FormControl,
FormLabel,
IconButton, IconButton,
Input, Input,
Switch, Switch,
@@ -87,11 +89,16 @@ export const SecretDetailSidebar = ({
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
const { fields, append, remove } = useFieldArray({ const tagFields = useFieldArray({
control, control,
name: "tags" name: "tags"
}); });
const metadataFormFields = useFieldArray({
control,
name: "secretMetadata"
});
const secretKey = secret?.key || ""; const secretKey = secret?.key || "";
const selectedTags = watch("tags", []) || []; const selectedTags = watch("tags", []) || [];
const selectedTagsGroupById = selectedTags.reduce<Record<string, boolean>>( const selectedTagsGroupById = selectedTags.reduce<Record<string, boolean>>(
@@ -153,10 +160,10 @@ export const SecretDetailSidebar = ({
if (selectedTagsGroupById?.[tag.id]) { if (selectedTagsGroupById?.[tag.id]) {
const tagPos = selectedTags.findIndex(({ id }) => id === tag.id); const tagPos = selectedTags.findIndex(({ id }) => id === tag.id);
if (tagPos !== -1) { if (tagPos !== -1) {
remove(tagPos); tagFields.remove(tagPos);
} }
} else { } else {
append(tag); tagFields.append(tag);
} }
}; };
@@ -277,9 +284,73 @@ export const SecretDetailSidebar = ({
)} )}
/> />
)} )}
<FormControl label="Metadata">
<div className="flex flex-col space-y-2">
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
<div key={metadataFieldId} className="flex items-end space-x-2">
<div className="flex-grow">
{i === 0 && <span className="text-xs text-mineshaft-400">Key</span>}
<Controller
control={control}
name={`secretMetadata.${i}.key`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Input {...field} className="max-h-8" />
</FormControl>
)}
/>
</div>
<div className="flex-grow">
{i === 0 && (
<FormLabel
label="Value"
className="text-xs text-mineshaft-400"
isOptional
/>
)}
<Controller
control={control}
name={`secretMetadata.${i}.value`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Input {...field} className="max-h-8" />
</FormControl>
)}
/>
</div>
<IconButton
ariaLabel="delete key"
className="bottom-0.5 max-h-8"
variant="outline_bg"
onClick={() => metadataFormFields.remove(i)}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
))}
<div className="mt-2">
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
variant="outline_bg"
onClick={() => metadataFormFields.append({ key: "", value: "" })}
>
Add Key
</Button>
</div>
</div>
</FormControl>
<FormControl label="Tags" className=""> <FormControl label="Tags" className="">
<div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2"> <div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2">
{fields.map(({ tagColor, id: formId, slug, id }) => ( {tagFields.fields.map(({ tagColor, id: formId, slug, id }) => (
<Tag <Tag
className="flex w-min items-center space-x-2" className="flex w-min items-center space-x-2"
key={formId} key={formId}
@@ -78,7 +78,8 @@ export const SecretListView = ({
tags, tags,
skipMultilineEncoding, skipMultilineEncoding,
newKey, newKey,
secretId secretId,
secretMetadata
}: Partial<{ }: Partial<{
value: string; value: string;
comment: string; comment: string;
@@ -88,6 +89,7 @@ export const SecretListView = ({
skipMultilineEncoding: boolean; skipMultilineEncoding: boolean;
newKey: string; newKey: string;
secretId: string; secretId: string;
secretMetadata?: { key: string; value: string }[];
}> = {} }> = {}
) => { ) => {
if (operation === "delete") { if (operation === "delete") {
@@ -115,7 +117,8 @@ export const SecretListView = ({
secretReminderRepeatDays: reminderRepeatDays, secretReminderRepeatDays: reminderRepeatDays,
secretReminderNote: reminderNote, secretReminderNote: reminderNote,
skipMultilineEncoding, skipMultilineEncoding,
newSecretName: newKey newSecretName: newKey,
secretMetadata
}); });
return; return;
} }
@@ -138,7 +141,10 @@ export const SecretListView = ({
const handleSaveSecret = useCallback( const handleSaveSecret = useCallback(
async ( async (
orgSecret: SecretV3RawSanitized, orgSecret: SecretV3RawSanitized,
modSecret: Omit<SecretV3RawSanitized, "tags"> & { tags?: { id: string }[] }, modSecret: Omit<SecretV3RawSanitized, "tags"> & {
tags?: { id: string }[];
secretMetadata?: { key: string; value: string }[];
},
cb?: () => void cb?: () => void
) => { ) => {
const { key: oldKey } = orgSecret; const { key: oldKey } = orgSecret;
@@ -151,7 +157,8 @@ export const SecretListView = ({
tags, tags,
comment, comment,
reminderRepeatDays, reminderRepeatDays,
reminderNote reminderNote,
secretMetadata
} = modSecret; } = modSecret;
const hasKeyChanged = oldKey !== key && key; const hasKeyChanged = oldKey !== key && key;
@@ -166,7 +173,8 @@ export const SecretListView = ({
"comment", "comment",
"skipMultilineEncoding", "skipMultilineEncoding",
"reminderRepeatDays", "reminderRepeatDays",
"reminderNote" "reminderNote",
"secretMetadata"
] as const ] as const
).every((el) => orgSecret[el] === modSecret[el]) && isSameTags; ).every((el) => orgSecret[el] === modSecret[el]) && isSameTags;
@@ -199,7 +207,8 @@ export const SecretListView = ({
reminderNote, reminderNote,
secretId: orgSecret.id, secretId: orgSecret.id,
newKey: hasKeyChanged ? key : undefined, newKey: hasKeyChanged ? key : undefined,
skipMultilineEncoding: modSecret.skipMultilineEncoding skipMultilineEncoding: modSecret.skipMultilineEncoding,
secretMetadata
}); });
if (cb) cb(); if (cb) cb();
} }
@@ -47,7 +47,13 @@ export const formSchema = z.object({
.nullable() .nullable()
.optional(), .optional(),
reminderNote: z.string().trim().nullable().optional(), reminderNote: z.string().trim().nullable().optional(),
secretMetadata: z
.object({
key: z.string().trim().min(1),
value: z.string().trim().default("")
})
.array()
.optional(),
tags: z tags: z
.object({ .object({
id: z.string(), id: z.string(),
@@ -34,7 +34,10 @@ import { useWorkspace } from "@app/context";
import { useCreateIntegration } from "@app/hooks/api"; import { useCreateIntegration } from "@app/hooks/api";
import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth";
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
import { IntegrationMappingBehavior } from "@app/hooks/api/integrations/types"; import {
IntegrationMappingBehavior,
IntegrationMetadataSyncMode
} from "@app/hooks/api/integrations/types";
enum TabSections { enum TabSections {
Connection = "connection", Connection = "connection",
@@ -94,6 +97,7 @@ const schema = z
mappingBehavior: z.nativeEnum(IntegrationMappingBehavior), mappingBehavior: z.nativeEnum(IntegrationMappingBehavior),
kmsKeyId: z.string().optional(), kmsKeyId: z.string().optional(),
shouldTag: z.boolean().optional(), shouldTag: z.boolean().optional(),
metadataSyncMode: z.nativeEnum(IntegrationMetadataSyncMode).optional(),
tags: z tags: z
.object({ .object({
key: z.string(), key: z.string(),
@@ -136,6 +140,7 @@ export const AwsSecretManagerConfigurePage = () => {
}); });
const shouldTagState = watch("shouldTag"); const shouldTagState = watch("shouldTag");
const selectedMetadataSyncMode = watch("metadataSyncMode");
const selectedSourceEnvironment = watch("sourceEnvironment"); const selectedSourceEnvironment = watch("sourceEnvironment");
const selectedAWSRegion = watch("awsRegion"); const selectedAWSRegion = watch("awsRegion");
const selectedMappingBehavior = watch("mappingBehavior"); const selectedMappingBehavior = watch("mappingBehavior");
@@ -171,7 +176,8 @@ export const AwsSecretManagerConfigurePage = () => {
tags, tags,
secretPrefix, secretPrefix,
kmsKeyId, kmsKeyId,
mappingBehavior mappingBehavior,
metadataSyncMode
}: TFormSchema) => { }: TFormSchema) => {
try { try {
if (!integrationAuth?.id) return; if (!integrationAuth?.id) return;
@@ -186,7 +192,8 @@ export const AwsSecretManagerConfigurePage = () => {
metadata: { metadata: {
...(shouldTag ...(shouldTag
? { ? {
secretAWSTag: tags secretAWSTag: tags,
metadataSyncMode
} }
: {}), : {}),
...(secretPrefix && { secretPrefix }), ...(secretPrefix && { secretPrefix }),
@@ -339,7 +346,12 @@ export const AwsSecretManagerConfigurePage = () => {
> >
<Select <Select
defaultValue={field.value} defaultValue={field.value}
onValueChange={(e) => onChange(e)} onValueChange={(e) => {
if (e === IntegrationMappingBehavior.MANY_TO_ONE) {
setValue("metadataSyncMode", IntegrationMetadataSyncMode.CUSTOM);
}
onChange(e);
}}
className="w-full border border-mineshaft-500" className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-full" dropdownContainerClassName="max-w-full"
> >
@@ -386,14 +398,25 @@ export const AwsSecretManagerConfigurePage = () => {
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<div className="ml-1 mt-2"> <div className="mb-3 ml-1 mt-2">
<Controller <Controller
control={control} control={control}
name="shouldTag" name="shouldTag"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<Switch <Switch
id="tag-aws" id="tag-aws"
onCheckedChange={(isChecked) => onChange(isChecked)} onCheckedChange={(isChecked) => {
if (
isChecked &&
selectedMappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE
) {
setValue(
"metadataSyncMode",
IntegrationMetadataSyncMode.SECRET_METADATA
);
}
onChange(isChecked);
}}
isChecked={value} isChecked={value}
> >
Tag in AWS Secrets Manager Tag in AWS Secrets Manager
@@ -401,37 +424,76 @@ export const AwsSecretManagerConfigurePage = () => {
)} )}
/> />
</div> </div>
{shouldTagState && ( {shouldTagState &&
<div className="mt-4 flex justify-between"> selectedMappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE && (
<Controller <Controller
control={control} control={control}
name="tags.0.key" name="metadataSyncMode"
render={({ field, fieldState: { error } }) => ( render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl <FormControl
label="Tag Key" label="Tag Sync Mode"
errorText={error?.message} errorText={error?.message}
isError={Boolean(error)} isError={Boolean(error)}
> >
<Input placeholder="managed-by" {...field} /> <Select
defaultValue={field.value}
onValueChange={(e) => {
setValue("tags", []);
onChange(e);
}}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-full"
>
<SelectItem
value={IntegrationMetadataSyncMode.SECRET_METADATA}
className="text-left"
key={`sync-mode-${IntegrationMetadataSyncMode.SECRET_METADATA}`}
>
Secret Metadata
</SelectItem>
<SelectItem
value={IntegrationMetadataSyncMode.CUSTOM}
className="text-left"
key={`sync-mode-${IntegrationMetadataSyncMode.CUSTOM}`}
>
Custom
</SelectItem>
</Select>
</FormControl> </FormControl>
)} )}
/> />
<Controller )}
control={control} {shouldTagState &&
name="tags.0.value" selectedMetadataSyncMode === IntegrationMetadataSyncMode.CUSTOM && (
render={({ field, fieldState: { error } }) => ( <div className="mt-4 flex justify-between">
<FormControl <Controller
label="Tag Value" control={control}
errorText={error?.message} name="tags.0.key"
isError={Boolean(error)} render={({ field, fieldState: { error } }) => (
> <FormControl
<Input placeholder="infisical" {...field} /> label="Tag Key"
</FormControl> errorText={error?.message}
)} isError={Boolean(error)}
/> >
</div> <Input placeholder="managed-by" {...field} />
)} </FormControl>
)}
/>
<Controller
control={control}
name="tags.0.value"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Tag Value"
errorText={error?.message}
isError={Boolean(error)}
>
<Input placeholder="infisical" {...field} />
</FormControl>
)}
/>
</div>
)}
<Controller <Controller
control={control} control={control}
name="secretPrefix" name="secretPrefix"