feat: aws secret manager metadata sync

This commit is contained in:
Sheen Capadngan
2024-12-19 23:54:47 +08:00
parent ef688efc8d
commit 025b4b8761
11 changed files with 162 additions and 58 deletions
+2 -1
View File
@@ -1136,7 +1136,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: {
@@ -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";
@@ -1074,14 +1076,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");
@@ -1129,8 +1131,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({
@@ -1165,9 +1184,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({
@@ -1177,14 +1194,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) {
@@ -1216,7 +1225,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({
@@ -1253,8 +1262,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
})) }))
@@ -1271,7 +1280,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));
@@ -4392,7 +4401,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
+10 -2
View File
@@ -47,6 +47,7 @@ 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 { 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";
@@ -121,7 +122,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
@@ -370,6 +376,7 @@ export const secretQueueFactory = ({
} }
content[secretKey].skipMultilineEncoding = Boolean(secret.skipMultilineEncoding); content[secretKey].skipMultilineEncoding = Boolean(secret.skipMultilineEncoding);
content[secretKey].secretMetadata = secret.secretMetadata;
}) })
); );
@@ -395,7 +402,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
}; };
} }
} }
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,
@@ -89,6 +94,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"
}
@@ -19,7 +19,10 @@ import z from "zod";
import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { SecretPathInput } from "@app/components/v2/SecretPathInput";
import { useCreateIntegration } from "@app/hooks/api"; import { useCreateIntegration } from "@app/hooks/api";
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";
import { import {
Badge, Badge,
@@ -97,6 +100,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(),
@@ -139,6 +143,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
}); });
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 default function AWSSecretManagerCreateIntegrationPage() {
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 default function AWSSecretManagerCreateIntegrationPage() {
metadata: { metadata: {
...(shouldTag ...(shouldTag
? { ? {
secretAWSTag: tags secretAWSTag: tags,
metadataSyncMode
} }
: {}), : {}),
...(secretPrefix && { secretPrefix }), ...(secretPrefix && { secretPrefix }),
@@ -339,7 +346,12 @@ export default function AWSSecretManagerCreateIntegrationPage() {
> >
<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,7 +398,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<div className="mt-2 ml-1"> <div className="mt-2 ml-1 mb-3">
<Controller <Controller
control={control} control={control}
name="shouldTag" name="shouldTag"
@@ -401,37 +413,76 @@ export default function AWSSecretManagerCreateIntegrationPage() {
)} )}
/> />
</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"
@@ -31,7 +31,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) => {