From 93b65a15349de912c112e5caaaa31b7cea8c9798 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 29 Apr 2024 16:49:53 -0700 Subject: [PATCH 1/3] Update impl for AWS SM/PS integrations with KMS --- .../integration-auth-service.ts | 36 ++-- .../integration-sync-secret.ts | 155 +++++++++--------- .../src/hooks/api/integrationAuth/queries.tsx | 49 +++--- .../aws-parameter-store/create.tsx | 54 +++--- .../aws-secret-manager/create.tsx | 54 +++--- 5 files changed, 170 insertions(+), 178 deletions(-) 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..4edb847ca0 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -442,95 +442,99 @@ const syncSecretsAWSParameterStore = async ({ accessId: string | null; accessToken: string; }) => { - if (!accessId) return; + try { + if (!accessId) return; - const config = new AWS.Config({ - region: integration.region as string, - credentials: { - accessKeyId: accessId, - secretAccessKey: accessToken - } - }); + const config = new AWS.Config({ + region: integration.region as string, + credentials: { + accessKeyId: accessId, + secretAccessKey: accessToken + } + }); - const ssm = new AWS.SSM({ - apiVersion: "2014-11-06", - region: integration.region as string - }); - ssm.config.update(config); + const ssm = new AWS.SSM({ + apiVersion: "2014-11-06", + region: integration.region as string + }); + ssm.config.update(config); - const metadata = z.record(z.any()).parse(integration.metadata || {}); + const metadata = z.record(z.any()).parse(integration.metadata || {}); - const params = { - Path: integration.path as string, - Recursive: false, - WithDecryption: true - }; + const params = { + Path: integration.path as string, + Recursive: false, + WithDecryption: true + }; - const parameterList = (await ssm.getParametersByPath(params).promise()).Parameters; + const parameterList = (await ssm.getParametersByPath(params).promise()).Parameters; - const awsParameterStoreSecretsObj = (parameterList || []) - .filter(({ Name }) => Boolean(Name)) - .reduce( - (obj, secret) => ({ - ...obj, - [(secret.Name as string).substring((integration.path as string).length)]: secret - }), - {} as Record - ); - // Identify secrets to create - await Promise.all( - Object.keys(secrets).map(async (key) => { - if (!(key in awsParameterStoreSecretsObj)) { - // case: secret does not exist in AWS parameter store - // -> create secret - if (secrets[key].value) { + const awsParameterStoreSecretsObj = (parameterList || []) + .filter(({ Name }) => Boolean(Name)) + .reduce( + (obj, secret) => ({ + ...obj, + [(secret.Name as string).substring((integration.path as string).length)]: secret + }), + {} as Record + ); + // Identify secrets to create + await Promise.all( + Object.keys(secrets).map(async (key) => { + if (!(key in awsParameterStoreSecretsObj)) { + // case: secret does not exist in AWS parameter store + // -> create secret + if (secrets[key].value) { + await ssm + .putParameter({ + Name: `${integration.path}${key}`, + Type: "SecureString", + Value: secrets[key].value, + ...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId }), + // Overwrite: true, + Tags: metadata.secretAWSTag + ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ + Key: tag.key, + Value: tag.value + })) + : [] + }) + .promise(); + } + // case: secret exists in AWS parameter store + } else if (awsParameterStoreSecretsObj[key].Value !== secrets[key].value) { + // case: secret value doesn't match one in AWS parameter store + // -> update secret await ssm .putParameter({ Name: `${integration.path}${key}`, Type: "SecureString", Value: secrets[key].value, - KeyId: metadata.kmsKeyId ? metadata.kmsKeyId : undefined, - // Overwrite: true, - Tags: metadata.secretAWSTag - ? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ - Key: tag.key, - Value: tag.value - })) - : [] + Overwrite: true + // Tags: metadata.secretAWSTag ? [{ Key: metadata.secretAWSTag.key, Value: metadata.secretAWSTag.value }] : [] }) .promise(); } - // case: secret exists in AWS parameter store - } else if (awsParameterStoreSecretsObj[key].Value !== secrets[key].value) { - // case: secret value doesn't match one in AWS parameter store - // -> update secret - await ssm - .putParameter({ - Name: `${integration.path}${key}`, - Type: "SecureString", - Value: secrets[key].value, - Overwrite: true - // Tags: metadata.secretAWSTag ? [{ Key: metadata.secretAWSTag.key, Value: metadata.secretAWSTag.value }] : [] - }) - .promise(); - } - }) - ); + }) + ); - // Identify secrets to delete - await Promise.all( - Object.keys(awsParameterStoreSecretsObj).map(async (key) => { - if (!(key in secrets)) { - // case: - // -> delete secret - await ssm - .deleteParameter({ - Name: awsParameterStoreSecretsObj[key].Name as string - }) - .promise(); - } - }) - ); + // Identify secrets to delete + await Promise.all( + Object.keys(awsParameterStoreSecretsObj).map(async (key) => { + if (!(key in secrets)) { + // case: + // -> delete secret + await ssm + .deleteParameter({ + Name: awsParameterStoreSecretsObj[key].Name as string + }) + .promise(); + } + }) + ); + } catch (err) { + console.error("syncSecretsAWSPS error: ", err); + } }; /** @@ -572,7 +576,6 @@ const syncSecretsAWSSecretManager = async ({ if (awsSecretManagerSecret?.SecretString) { awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString); } - if (!isEqual(awsSecretManagerSecretObj, secKeyVal)) { await secretsManager.send( new UpdateSecretCommand({ @@ -587,7 +590,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/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() {