diff --git a/backend/src/db/migrations/20240429154610_audit-log-index.ts b/backend/src/db/migrations/20240429154610_audit-log-index.ts new file mode 100644 index 0000000000..40a1cb24d6 --- /dev/null +++ b/backend/src/db/migrations/20240429154610_audit-log-index.ts @@ -0,0 +1,28 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId"); + const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId"); + const doesCreatedAtExist = await knex.schema.hasColumn(TableName.AuditLog, "createdAt"); + if (await knex.schema.hasTable(TableName.AuditLog)) { + await knex.schema.alterTable(TableName.AuditLog, (t) => { + if (doesProjectIdExist && doesCreatedAtExist) t.index(["projectId", "createdAt"]); + if (doesOrgIdExist && doesCreatedAtExist) t.index(["orgId", "createdAt"]); + }); + } +} + +export async function down(knex: Knex): Promise { + const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId"); + const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId"); + const doesCreatedAtExist = await knex.schema.hasColumn(TableName.AuditLog, "createdAt"); + + if (await knex.schema.hasTable(TableName.AuditLog)) { + await knex.schema.alterTable(TableName.AuditLog, (t) => { + if (doesProjectIdExist && doesCreatedAtExist) t.dropIndex(["projectId", "createdAt"]); + if (doesOrgIdExist && doesCreatedAtExist) t.dropIndex(["orgId", "createdAt"]); + }); + } +} diff --git a/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts new file mode 100644 index 0000000000..3feafa5344 --- /dev/null +++ b/backend/src/ee/services/dynamic-secret/providers/aws-iam.ts @@ -0,0 +1,194 @@ +import { + AddUserToGroupCommand, + AttachUserPolicyCommand, + CreateAccessKeyCommand, + CreateUserCommand, + DeleteAccessKeyCommand, + DeleteUserCommand, + DeleteUserPolicyCommand, + DetachUserPolicyCommand, + GetUserCommand, + IAMClient, + ListAccessKeysCommand, + ListAttachedUserPoliciesCommand, + ListGroupsForUserCommand, + ListUserPoliciesCommand, + PutUserPolicyCommand, + RemoveUserFromGroupCommand +} from "@aws-sdk/client-iam"; +import { z } from "zod"; + +import { BadRequestError } from "@app/lib/errors"; +import { alphaNumericNanoId } from "@app/lib/nanoid"; + +import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models"; + +const generateUsername = () => { + return alphaNumericNanoId(32); +}; + +export const AwsIamProvider = (): TDynamicProviderFns => { + const validateProviderInputs = async (inputs: unknown) => { + const providerInputs = await DynamicSecretAwsIamSchema.parseAsync(inputs); + return providerInputs; + }; + + const getClient = async (providerInputs: z.infer) => { + const client = new IAMClient({ + region: providerInputs.region, + credentials: { + accessKeyId: providerInputs.accessKey, + secretAccessKey: providerInputs.secretAccessKey + } + }); + + return client; + }; + + const validateConnection = async (inputs: unknown) => { + const providerInputs = await validateProviderInputs(inputs); + const client = await getClient(providerInputs); + + const isConnected = await client.send(new GetUserCommand({})).then(() => true); + return isConnected; + }; + + const create = async (inputs: unknown) => { + const providerInputs = await validateProviderInputs(inputs); + const client = await getClient(providerInputs); + + const username = generateUsername(); + const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs; + const createUserRes = await client.send( + new CreateUserCommand({ + Path: awsPath, + PermissionsBoundary: permissionBoundaryPolicyArn || undefined, + Tags: [{ Key: "createdBy", Value: "infisical-dynamic-secret" }], + UserName: username + }) + ); + if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" }); + if (userGroups) { + await Promise.all( + userGroups + .split(",") + .filter(Boolean) + .map((group) => + client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group })) + ) + ); + } + if (policyArns) { + await Promise.all( + policyArns + .split(",") + .filter(Boolean) + .map((policyArn) => + client.send(new AttachUserPolicyCommand({ UserName: createUserRes?.User?.UserName, PolicyArn: policyArn })) + ) + ); + } + if (policyDocument) { + await client.send( + new PutUserPolicyCommand({ + UserName: createUserRes.User.UserName, + PolicyName: `infisical-dynamic-policy-${alphaNumericNanoId(4)}`, + PolicyDocument: policyDocument + }) + ); + } + + const createAccessKeyRes = await client.send( + new CreateAccessKeyCommand({ + UserName: createUserRes.User.UserName + }) + ); + if (!createAccessKeyRes.AccessKey) + throw new BadRequestError({ message: "Failed to create AWS IAM User access key" }); + + return { + entityId: username, + data: { + ACCESS_KEY: createAccessKeyRes.AccessKey.AccessKeyId, + SECRET_ACCESS_KEY: createAccessKeyRes.AccessKey.SecretAccessKey, + USERNAME: username + } + }; + }; + + const revoke = async (inputs: unknown, entityId: string) => { + const providerInputs = await validateProviderInputs(inputs); + const client = await getClient(providerInputs); + + const username = entityId; + + // remove user from groups + const userGroups = await client.send(new ListGroupsForUserCommand({ UserName: username })); + await Promise.all( + (userGroups.Groups || []).map(({ GroupName }) => + client.send( + new RemoveUserFromGroupCommand({ + GroupName, + UserName: username + }) + ) + ) + ); + + // remove user access keys + const userAccessKeys = await client.send(new ListAccessKeysCommand({ UserName: username })); + await Promise.all( + (userAccessKeys.AccessKeyMetadata || []).map(({ AccessKeyId }) => + client.send( + new DeleteAccessKeyCommand({ + AccessKeyId, + UserName: username + }) + ) + ) + ); + + // remove user inline policies + const userInlinePolicies = await client.send(new ListUserPoliciesCommand({ UserName: username })); + await Promise.all( + (userInlinePolicies.PolicyNames || []).map((policyName) => + client.send( + new DeleteUserPolicyCommand({ + PolicyName: policyName, + UserName: username + }) + ) + ) + ); + + // remove user attached policies + const userAttachedPolicies = await client.send(new ListAttachedUserPoliciesCommand({ UserName: username })); + await Promise.all( + (userAttachedPolicies.AttachedPolicies || []).map((policy) => + client.send( + new DetachUserPolicyCommand({ + PolicyArn: policy.PolicyArn, + UserName: username + }) + ) + ) + ); + + await client.send(new DeleteUserCommand({ UserName: username })); + return { entityId: username }; + }; + + const renew = async (_inputs: unknown, entityId: string) => { + // do nothing + const username = entityId; + return { entityId: username }; + }; + + return { + validateProviderInputs, + validateConnection, + create, + revoke, + renew + }; +}; diff --git a/backend/src/ee/services/dynamic-secret/providers/index.ts b/backend/src/ee/services/dynamic-secret/providers/index.ts index 34c0495533..beb6c428e3 100644 --- a/backend/src/ee/services/dynamic-secret/providers/index.ts +++ b/backend/src/ee/services/dynamic-secret/providers/index.ts @@ -1,8 +1,10 @@ +import { AwsIamProvider } from "./aws-iam"; import { CassandraProvider } from "./cassandra"; import { DynamicSecretProviders } from "./models"; import { SqlDatabaseProvider } from "./sql-database"; export const buildDynamicSecretProviders = () => ({ [DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider(), - [DynamicSecretProviders.Cassandra]: CassandraProvider() + [DynamicSecretProviders.Cassandra]: CassandraProvider(), + [DynamicSecretProviders.AwsIam]: AwsIamProvider() }); diff --git a/backend/src/ee/services/dynamic-secret/providers/models.ts b/backend/src/ee/services/dynamic-secret/providers/models.ts index edb60d4b23..c11f6ddfb3 100644 --- a/backend/src/ee/services/dynamic-secret/providers/models.ts +++ b/backend/src/ee/services/dynamic-secret/providers/models.ts @@ -8,38 +8,51 @@ export enum SqlProviders { export const DynamicSecretSqlDBSchema = z.object({ client: z.nativeEnum(SqlProviders), - host: z.string().toLowerCase(), + host: z.string().trim().toLowerCase(), port: z.number(), - database: z.string(), - username: z.string(), - password: z.string(), - creationStatement: z.string(), - revocationStatement: z.string(), - renewStatement: z.string().optional(), + database: z.string().trim(), + username: z.string().trim(), + password: z.string().trim(), + creationStatement: z.string().trim(), + revocationStatement: z.string().trim(), + renewStatement: z.string().trim().optional(), ca: z.string().optional() }); export const DynamicSecretCassandraSchema = z.object({ - host: z.string().toLowerCase(), + host: z.string().trim().toLowerCase(), port: z.number(), - localDataCenter: z.string().min(1), - keyspace: z.string().optional(), - username: z.string(), - password: z.string(), - creationStatement: z.string(), - revocationStatement: z.string(), - renewStatement: z.string().optional(), + localDataCenter: z.string().trim().min(1), + keyspace: z.string().trim().optional(), + username: z.string().trim(), + password: z.string().trim(), + creationStatement: z.string().trim(), + revocationStatement: z.string().trim(), + renewStatement: z.string().trim().optional(), ca: z.string().optional() }); +export const DynamicSecretAwsIamSchema = z.object({ + accessKey: z.string().trim().min(1), + secretAccessKey: z.string().trim().min(1), + region: z.string().trim().min(1), + awsPath: z.string().trim().optional(), + permissionBoundaryPolicyArn: z.string().trim().optional(), + policyDocument: z.string().trim().optional(), + userGroups: z.string().trim().optional(), + policyArns: z.string().trim().optional() +}); + export enum DynamicSecretProviders { SqlDatabase = "sql-database", - Cassandra = "cassandra" + Cassandra = "cassandra", + AwsIam = "aws-iam" } export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }), - z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }) + z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }), + z.object({ type: z.literal(DynamicSecretProviders.AwsIam), inputs: DynamicSecretAwsIamSchema }) ]); export type TDynamicProviderFns = { diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index 778589de86..74d881d266 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -566,20 +566,32 @@ export const integrationAuthServiceFactory = ({ } }); const kms = new AWS.KMS(); - const aliases = await kms.listAliases({}).promise(); - const keys = await kms.listKeys({}).promise(); - const response = keys - .Keys!.map((key) => { - const keyAlias = aliases.Aliases!.find((alias) => key.KeyId === alias.TargetKeyId); - if (!keyAlias?.AliasName?.includes("alias/aws/")) { - return { id: String(key.KeyId), alias: String(keyAlias?.AliasName || key.KeyId) }; - } - return { id: "null", alias: "null" }; - }) - .filter((elem) => elem.id !== "null"); - return [...response, { id: "null", alias: "default" }]; + const keyAliases = aliases.Aliases!.filter((alias) => { + if (!alias.TargetKeyId) return false; + + if (integrationAuth.integration === Integrations.AWS_PARAMETER_STORE && alias.AliasName === "alias/aws/ssm") + return true; + + if ( + integrationAuth.integration === Integrations.AWS_SECRET_MANAGER && + alias.AliasName === "alias/aws/secretsmanager" + ) + return true; + + if (alias.AliasName?.includes("alias/aws/")) return false; + return alias.TargetKeyId; + }); + + const keysWithAliases = keyAliases.map((alias) => { + return { + id: alias.TargetKeyId!, + alias: alias.AliasName! + }; + }); + + return keysWithAliases; }; const getQoveryProjects = async ({ diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index de576dc992..1681598479 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -489,7 +489,7 @@ const syncSecretsAWSParameterStore = async ({ Name: `${integration.path}${key}`, Type: "SecureString", Value: secrets[key].value, - KeyId: metadata.kmsKeyId ? metadata.kmsKeyId : undefined, + ...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId }), // Overwrite: true, Tags: metadata.secretAWSTag ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ @@ -572,7 +572,6 @@ const syncSecretsAWSSecretManager = async ({ if (awsSecretManagerSecret?.SecretString) { awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString); } - if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) { await secretsManager.send( new UpdateSecretCommand({ @@ -587,7 +586,7 @@ const syncSecretsAWSSecretManager = async ({ new CreateSecretCommand({ Name: integration.app as string, SecretString: JSON.stringify(secKeyVal), - KmsKeyId: metadata.kmsKeyId ? metadata.kmsKeyId : null, + ...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }), Tags: metadata.secretAWSTag ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value })) : [] diff --git a/docs/documentation/platform/dynamic-secrets/aws-iam.mdx b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx new file mode 100644 index 0000000000..6ec5b48b94 --- /dev/null +++ b/docs/documentation/platform/dynamic-secrets/aws-iam.mdx @@ -0,0 +1,151 @@ +--- +title: "AWS IAM" +description: "How to dynamically generate AWS IAM Users." +--- + +The Infisical AWS IAM dynamic secret allows you to generate AWS IAM Users on demand based on configured AWS policy. + +## Prerequisite + +Infisical needs an initial AWS IAM user with the required permissions to create sub IAM users. This IAM user will be responsible for managing the lifecycle of new IAM users. + + + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iam:AttachUserPolicy", + "iam:CreateAccessKey", + "iam:CreateUser", + "iam:DeleteAccessKey", + "iam:DeleteUser", + "iam:DeleteUserPolicy", + "iam:DetachUserPolicy", + "iam:GetUser", + "iam:ListAccessKeys", + "iam:ListAttachedUserPolicies", + "iam:ListGroupsForUser", + "iam:ListUserPolicies", + "iam:PutUserPolicy", + "iam:AddUserToGroup", + "iam:RemoveUserFromGroup" + ], + "Resource": ["*"] + } + ] +} +``` + +To minimize managing user access you can attach a resource in format + +> arn:aws:iam::\:user/\ + +Replace **\** with your AWS account id and **\** with a path to minimize managing user access. + + + +## Set up Dynamic Secrets with AWS IAM + + + + Navigate to the Secret Overview dashboard and select the environment in which you would like to add a dynamic secret to. + + + ![Add Dynamic Secret Button](../../../images/platform/dynamic-secrets/add-dynamic-secret-button.png) + + + ![Dynamic Secret Modal](../../../images/platform/dynamic-secrets/dynamic-secret-modal-aws-iam.png) + + + + Name by which you want the secret to be referenced + + + + Default time-to-live for a generated secret (it is possible to modify this value when a secret is generate) + + + + Maximum time-to-live for a generated secret + + + + The managing AWS IAM User Access Key + + + + The managing AWS IAM User Secret Key + + + + [IAM AWS Path](https://aws.amazon.com/blogs/security/optimize-aws-administration-with-iam-paths/) to scope created IAM User resource access. + + + + The AWS data center region. + + + + The IAM Policy ARN of the [AWS Permissions Boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) to attach to IAM users created in the role. + + + + The AWS IAM groups that should be assigned to the created users. Multiple values can be provided by separating them with commas + + + + The AWS IAM managed policies that should be attached to the created users. Multiple values can be provided by separating them with commas + + + + The AWS IAM inline policy that should be attached to the created users. Multiple values can be provided by separating them with commas + + + ![Dynamic Secret Setup Modal](../../../images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png) + + + + After submitting the form, you will see a dynamic secret created in the dashboard. + + ![Dynamic Secret](../../../images/platform/dynamic-secrets/dynamic-secret.png) + + + Once you've successfully configured the dynamic secret, you're ready to generate on-demand credentials. + To do this, simply click on the 'Generate' button which appears when hovering over the dynamic secret item. + Alternatively, you can initiate the creation of a new lease by selecting 'New Lease' from the dynamic secret lease list section. + + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-generate.png) + ![Dynamic Secret](/images/platform/dynamic-secrets/dynamic-secret-lease-empty.png) + + When generating these secrets, it's important to specify a Time-to-Live (TTL) duration. This will dictate how long the credentials are valid for. + + ![Provision Lease](/images/platform/dynamic-secrets/provision-lease.png) + + + Ensure that the TTL for the lease fall within the maximum TTL defined when configuring the dynamic secret in step 4. + + + + Once you click the `Submit` button, a new secret lease will be generated and the credentials for it will be shown to you. + + ![Provision Lease](/images/platform/dynamic-secrets/lease-values-aws-iam.png) + + + +## Audit or Revoke Leases +Once you have created one or more leases, you will be able to access them by clicking on the respective dynamic secret item on the dashboard. +This will allow you see the lease details and delete the lease ahead of its expiration time. + +![Provision Lease](/images/platform/dynamic-secrets/lease-data.png) + +## Renew Leases +To extend the life of the generated dynamic secret lease past its initial time to live, simply click on the **Renew** as illustrated below. +![Provision Lease](/images/platform/dynamic-secrets/dynamic-secret-lease-renew.png) + + + Lease renewals cannot exceed the maximum TTL set when configuring the dynamic secret + diff --git a/docs/documentation/platform/secret-versioning.mdx b/docs/documentation/platform/secret-versioning.mdx index 741bbc3089..11afbceef0 100644 --- a/docs/documentation/platform/secret-versioning.mdx +++ b/docs/documentation/platform/secret-versioning.mdx @@ -5,7 +5,7 @@ description: "Learn how secret versioning works in Infisical." Every time a secret change is persformed, a new version of the same secret is created. -Such versions can be accessed visually by opening up the [secret sidebar](/documentation/platform/project#drawer) (as seen below) or [retrived via API](/api-reference/endpoints/secrets/read) +Such versions can be accessed visually by opening up the [secret sidebar](/documentation/platform/project#drawer) (as seen below) or [retrieved via API](/api-reference/endpoints/secrets/read) by specifying the `version` query parameter. ![secret versioning](../../images/platform/secret-versioning.png) diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-modal-aws-iam.png b/docs/images/platform/dynamic-secrets/dynamic-secret-modal-aws-iam.png new file mode 100644 index 0000000000..3ae9155a32 Binary files /dev/null and b/docs/images/platform/dynamic-secrets/dynamic-secret-modal-aws-iam.png differ diff --git a/docs/images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png b/docs/images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png new file mode 100644 index 0000000000..d412109fa3 Binary files /dev/null and b/docs/images/platform/dynamic-secrets/dynamic-secret-setup-modal-aws-iam.png differ diff --git a/docs/images/platform/dynamic-secrets/lease-values-aws-iam.png b/docs/images/platform/dynamic-secrets/lease-values-aws-iam.png new file mode 100644 index 0000000000..4764eceb2a Binary files /dev/null and b/docs/images/platform/dynamic-secrets/lease-values-aws-iam.png differ diff --git a/docs/integrations/cloud/aws-secret-manager.mdx b/docs/integrations/cloud/aws-secret-manager.mdx index 2ab45c6200..6fc83eaff0 100644 --- a/docs/integrations/cloud/aws-secret-manager.mdx +++ b/docs/integrations/cloud/aws-secret-manager.mdx @@ -31,7 +31,9 @@ Prerequisites: "secretsmanager:UpdateSecret", "secretsmanager:TagResource", // if you need to add tags to secrets "kms:ListKeys", // if you need to specify the KMS key - "kms:ListAliases" // if you need to specify the KMS key + "kms:ListAliases", // if you need to specify the KMS key + "kms:Encrypt", // if you need to specify the KMS key + "kms:Decrypt" // if you need to specify the KMS key ], "Resource": "*" } diff --git a/docs/mint.json b/docs/mint.json index b38b5fa522..f74a422a47 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -146,7 +146,8 @@ "documentation/platform/dynamic-secrets/postgresql", "documentation/platform/dynamic-secrets/mysql", "documentation/platform/dynamic-secrets/oracle", - "documentation/platform/dynamic-secrets/cassandra" + "documentation/platform/dynamic-secrets/cassandra", + "documentation/platform/dynamic-secrets/aws-iam" ] }, "documentation/platform/groups" diff --git a/frontend/src/hooks/api/dynamicSecret/types.ts b/frontend/src/hooks/api/dynamicSecret/types.ts index 27c4c5ddf4..a9aab83183 100644 --- a/frontend/src/hooks/api/dynamicSecret/types.ts +++ b/frontend/src/hooks/api/dynamicSecret/types.ts @@ -17,7 +17,8 @@ export type TDynamicSecret = { export enum DynamicSecretProviders { SqlDatabase = "sql-database", - Cassandra = "cassandra" + Cassandra = "cassandra", + AwsIam = "aws-iam" } export enum SqlProviders { @@ -56,6 +57,18 @@ export type TDynamicSecretProvider = renewStatement?: string; ca?: string | undefined; }; + } + | { + type: DynamicSecretProviders.AwsIam; + inputs: { + accessKey: string; + secretAccessKey: string; + region: string; + awsPath?: string; + policyDocument?: string; + userGroups?: string; + policyArns?: string; + }; }; export type TCreateDynamicSecretDTO = { diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index e66dd57005..d800e53130 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -48,10 +48,9 @@ const integrationAuthKeys = { integrationAuthId, region }: { - integrationAuthId: string, - region: string - }) => - [{ integrationAuthId, region }, "integrationAuthAwsKmsKeyIds"] as const, + integrationAuthId: string; + region: string; + }) => [{ integrationAuthId, region }, "integrationAuthAwsKmsKeyIds"] as const, getIntegrationAuthQoveryOrgs: (integrationAuthId: string) => [{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const, getIntegrationAuthQoveryProjects: ({ @@ -226,27 +225,6 @@ const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => { return orgs; }; -const fetchIntegrationAuthAwsKmsKeys = async ({ - integrationAuthId, - region -}: { - integrationAuthId: string; - region: string; -}) => { - const { - data: { kmsKeys } - } = await apiRequest.get<{ kmsKeys: KmsKey[] }>( - `/api/v1/integration-auth/${integrationAuthId}/aws-secrets-manager/kms-keys`, - { - params: { - region - } - } - ); - - return kmsKeys; -}; - const fetchIntegrationAuthQoveryProjects = async ({ integrationAuthId, orgId @@ -586,11 +564,22 @@ export const useGetIntegrationAuthAwsKmsKeys = ({ integrationAuthId, region }), - queryFn: () => - fetchIntegrationAuthAwsKmsKeys({ - integrationAuthId, - region - }), + queryFn: async () => { + if (!region) return []; + + const { + data: { kmsKeys } + } = await apiRequest.get<{ kmsKeys: KmsKey[] }>( + `/api/v1/integration-auth/${integrationAuthId}/aws-secrets-manager/kms-keys`, + { + params: { + region + } + } + ); + + return kmsKeys; + }, enabled: true }); }; diff --git a/frontend/src/pages/integrations/aws-parameter-store/create.tsx b/frontend/src/pages/integrations/aws-parameter-store/create.tsx index 9f52347ce2..cc80d2f29d 100644 --- a/frontend/src/pages/integrations/aws-parameter-store/create.tsx +++ b/frontend/src/pages/integrations/aws-parameter-store/create.tsx @@ -100,19 +100,12 @@ export default function AWSParameterStoreCreateIntegrationPage() { } }, [workspace]); - const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } = useGetIntegrationAuthAwsKmsKeys({ - integrationAuthId: String(integrationAuthId), + integrationAuthId: String(integrationAuthId), region: selectedAWSRegion }); - useEffect(() => { - if (integrationAuthAwsKmsKeys) { - setKmsKeyId(String(integrationAuthAwsKmsKeys?.filter(key => key.alias === "default")[0]?.id)) - } - }, [integrationAuthAwsKmsKeys]) - const isValidAWSParameterStorePath = (awsStorePath: string) => { const pattern = /^\/([\w-]+\/)*[\w-]+\/$/; return pattern.test(awsStorePath) && awsStorePath.length <= 2048; @@ -143,16 +136,15 @@ export default function AWSParameterStoreCreateIntegrationPage() { metadata: { ...(shouldTag ? { - secretAWSTag: [{ - key: tagKey, - value: tagValue - }] + secretAWSTag: [ + { + key: tagKey, + value: tagValue + } + ] } : {}), - ...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ? - { - kmsKeyId - }: {}) + ...(kmsKeyId && { kmsKeyId }) } }); @@ -165,7 +157,10 @@ export default function AWSParameterStoreCreateIntegrationPage() { } }; - return (integrationAuth && workspace && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading) ? ( + return integrationAuth && + workspace && + selectedSourceEnvironment && + !isIntegrationAuthAwsKmsKeysLoading ? (
Set Up AWS Parameter Integration @@ -241,7 +236,10 @@ export default function AWSParameterStoreCreateIntegrationPage() { + setTagKey(e.target.value)} /> - - + setTagValue(e.target.value)} /> @@ -309,7 +303,7 @@ export default function AWSParameterStoreCreateIntegrationPage() { setSelectedAWSRegion(val)} + onValueChange={(val) => { + setSelectedAWSRegion(val); + setKmsKeyId(""); + }} className="w-full border border-mineshaft-500" > {awsRegions.map((awsRegion) => ( @@ -284,20 +282,16 @@ export default function AWSSecretManagerCreateIntegrationPage() {
{shouldTag && (
- - + setTagKey(e.target.value)} /> - - + setTagValue(e.target.value)} /> @@ -308,7 +302,7 @@ export default function AWSSecretManagerCreateIntegrationPage() { + + )} + /> +
+
+ ( + } + isError={Boolean(error?.message)} + errorText={error?.message} + > + + + )} + /> +
+
+ ( + } + isError={Boolean(error?.message)} + errorText={error?.message} + > + + + )} + /> +
+ +
+
+ Configuration +
+
+
+ ( + + + + )} + /> + ( + + + + )} + /> +
+
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + +